diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index c18432f0ce9..c386b35c3ae 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -95,11 +95,10 @@ jobs: optional-dependencies-name: unit-tests - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} name: codecov-unit-tests - file: ./coverage.xml + files: ./coverage.xml flags: linux_unit - name: Upload pytest test results @@ -123,11 +122,10 @@ jobs: optional-dependencies-name: integration-tests - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} name: codecov-integration-tests - file: ./coverage.xml + files: ./coverage.xml flags: linux_integration - name: Upload pytest test results @@ -182,11 +180,10 @@ jobs: .venv\Scripts\Activate.ps1 pytest ${{ env.PYTEST_ARGUMENTS }} -m solvers - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-solvers-tests-windows - file: ./coverage.xml + files: ./coverage.xml flags: windows_system_solvers - name: Upload pytest test results @@ -238,11 +235,10 @@ jobs: source .venv/bin/activate pytest ${{ env.PYTEST_ARGUMENTS }} -m solvers - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-solvers-tests-linux - file: ./coverage.xml + files: ./coverage.xml flags: linux_system_solvers - name: Upload pytest test results @@ -302,11 +298,10 @@ jobs: .venv\Scripts\Activate.ps1 pytest ${{ env.PYTEST_ARGUMENTS }} -n 4 --dist loadfile -m general - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-general-tests-windows - file: ./coverage.xml + files: ./coverage.xml flags: windows_system_general - name: Upload pytest test results @@ -370,11 +365,10 @@ jobs: source .venv/bin/activate pytest ${{ env.PYTEST_ARGUMENTS }} -n 4 --dist loadfile -m general - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-general-tests - file: ./coverage.xml + files: ./coverage.xml flags: linux_system_general - name: Upload pytest test results diff --git a/.github/workflows/manual_draft.yml b/.github/workflows/manual_draft.yml index 0baaee5b7ed..c5f59769bba 100644 --- a/.github/workflows/manual_draft.yml +++ b/.github/workflows/manual_draft.yml @@ -79,11 +79,10 @@ jobs: .venv\Scripts\Activate.ps1 pytest ${{ env.PYTEST_ARGUMENTS }} -m solvers - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-solvers-tests-windows - file: ./coverage.xml + files: ./coverage.xml flags: system,solvers,windows - name: Upload pytest test results @@ -131,11 +130,10 @@ jobs: source .venv/bin/activate pytest ${{ env.PYTEST_ARGUMENTS }} -m solvers - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-solvers-tests-linux - file: ./coverage.xml + files: ./coverage.xml flags: system,solver,linux - name: Upload pytest test results @@ -191,11 +189,10 @@ jobs: .venv\Scripts\Activate.ps1 pytest ${{ env.PYTEST_ARGUMENTS }} -n 4 --dist loadfile -m general - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-general-tests-windows - file: ./coverage.xml + files: ./coverage.xml flags: system,general,windows - name: Upload pytest test results @@ -255,11 +252,10 @@ jobs: source .venv/bin/activate pytest ${{ env.PYTEST_ARGUMENTS }} -n 4 --dist loadfile -m general - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-general-tests - file: ./coverage.xml + files: ./coverage.xml flags: system,solver,linux - name: Upload pytest test results diff --git a/.github/workflows/unit_test_prerelease.yml b/.github/workflows/unit_test_prerelease.yml index 90c85dfef62..118b35d79c6 100644 --- a/.github/workflows/unit_test_prerelease.yml +++ b/.github/workflows/unit_test_prerelease.yml @@ -73,7 +73,7 @@ jobs: Set-Item -Path env:PYTHONMALLOC -Value "malloc" pytest --tx 6*popen --durations=50 --dist loadfile -v --cov=ansys.aedt.core --cov-report=xml --junitxml=junit/test-results.xml --cov-report=html tests - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} if: matrix.python-version == '3.8' diff --git a/doc/source/Resources/pyaedt_settings.yaml b/doc/source/Resources/pyaedt_settings.yaml index 13b4b214f72..4cc5ef18c00 100644 --- a/doc/source/Resources/pyaedt_settings.yaml +++ b/doc/source/Resources/pyaedt_settings.yaml @@ -78,6 +78,8 @@ aedt_env_var: ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE: '1' ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE: '1' ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3: '1' + # Environment variable useful in Linux to skip the dependency check which improves speed + ANS_NODEPCHECK: '1' general: # Enable or disable the lazy load @@ -120,3 +122,5 @@ general: pyaedt_server_path: '' # Remote temp folder remote_rpc_session_temp_folder: '' + # Block figure plot during python script run + block_figure_plot: false diff --git a/doc/source/User_guide/settings.rst b/doc/source/User_guide/settings.rst index 9b0f86c46d2..f9aa638f032 100644 --- a/doc/source/User_guide/settings.rst +++ b/doc/source/User_guide/settings.rst @@ -12,9 +12,12 @@ If the environment variable is not defined, a check is performed to see if a fil ``HOME`` folder for Linux. If such file exists, it is then used to update the default configuration. -Below is the content that can be updated through the YAML file. +Here is an example of YAML file :download:`YAML configuration file <../Resources/pyaedt_settings.yaml>` -:download:`YAML configuration file <../Resources/pyaedt_settings.yaml>` +.. warning:: + In Linux, it is recommended to add the ``ANS_NODEPCHECK`` environment variable for speed reasons. + This variable is commented out in the download file. Using this file without modifying it disables + this option from the default settings behaviour. .. note:: Not all settings from class ``Settings`` can be modified through this file @@ -23,6 +26,7 @@ Below is the content that can be updated through the YAML file. ``Formatter`` and ``time_tick`` which expects a time value, in seconds, since the `epoch `_ as a floating-point number. +Below is the content that can be updated through the YAML file. .. code-block:: yaml @@ -99,6 +103,8 @@ Below is the content that can be updated through the YAML file. ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE: '1' ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE: '1' ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3: '1' + # Environment variable used in Linux to skip the dependency check for speed + # ANS_NODEPCHECK: '1' general: # Enable or disable the lazy load diff --git a/pyproject.toml b/pyproject.toml index bd26f79f2b4..33af53982f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,8 @@ dependencies = [ "pyedb>=0.4.0; python_version == '3.7'", "pyedb>=0.24.0; python_version > '3.7'", "pyedb!=0.28.0; python_version > '3.7'", - "pytomlpp; python_version < '3.12'", + "tomli; python_version < '3.11'", + "tomli-w", "rpyc>=6.0.0,<6.1", "pyyaml", "defusedxml>=0.7,<8.0" @@ -66,7 +67,7 @@ tests = [ "mock>=5.1.0,<5.2", "numpy>=1.20.0,<2", "openpyxl>=3.1.0,<3.3", - "osmnx>=1.1.0,<1.10", + "osmnx>=1.1.0,<2.1", "pandas>=1.1.0,<2.3", "pytest>=7.4.0,<8.4", "pytest-cov>=4.0.0,<6.1", @@ -76,7 +77,7 @@ tests = [ "tables; python_version >= '3.10'", # Never directly imported but required when loading ML related file see #4713 "scikit-learn>=1.0.0,<1.6", - "scikit-rf>=0.30.0,<1.5", + "scikit-rf>=0.30.0,<1.6", "SRTM.py", "utm", ] @@ -105,7 +106,7 @@ all = [ "matplotlib>=3.5.0,<3.10", "numpy>=1.20.0,<2", "openpyxl>=3.1.0,<3.3", - "osmnx>=1.1.0,<1.10", + "osmnx>=1.1.0,<2.1", "pandas>=1.1.0,<2.3", "pyvista[io]>=0.38.0,<0.45", "fast-simplification>=0.1.7", @@ -113,7 +114,7 @@ all = [ "tables; python_version >= '3.10'", # Never directly imported but required when loading ML related file see #4713 "scikit-learn>=1.0.0,<1.6", - "scikit-rf>=0.30.0,<1.5", + "scikit-rf>=0.30.0,<1.6", "SRTM.py", "utm", ] @@ -121,14 +122,14 @@ installer = [ "matplotlib>=3.5.0,<3.10", "numpy>=1.20.0,<2", "openpyxl>=3.1.0,<3.3", - "osmnx>=1.1.0,<1.10", + "osmnx>=1.1.0,<2.1", "pandas>=1.1.0,<2.3", "pyvista[io]>=0.38.0,<0.45", "fast-simplification>=0.1.7", "ansys-tools-visualization-interface; python_version >= '3.10'", # Never directly imported but required when loading ML related file see #4713 "scikit-learn>=1.0.0,<1.6", - "scikit-rf>=0.30.0,<1.5", + "scikit-rf>=0.30.0,<1.6", "SRTM.py", "utm", "jupyterlab>=3.6.0,<4.4", diff --git a/src/ansys/aedt/core/application/__init__.py b/src/ansys/aedt/core/application/__init__.py index 9c4476773da..a83f6a966d8 100644 --- a/src/ansys/aedt/core/application/__init__.py +++ b/src/ansys/aedt/core/application/__init__.py @@ -21,3 +21,36 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from ansys.aedt.core.generic.general_methods import pyaedt_function_handler + + +@pyaedt_function_handler() +def _get_data_model(child_object, level=-1): + import json + + def _fix_dict(p_list, p_out): + for p, val in p_list.items(): + if p == "properties": + for prop in val: + if "value" in prop: + p_out[prop["name"]] = prop["value"] + elif "values" in prop: + p_out[prop["name"]] = prop["values"] + else: + p_out[prop["name"]] = None + elif isinstance(val, dict): + _fix_dict(val, p_out[p]) + elif isinstance(val, list): + p_out[p] = [] + for el in val: + children = {} + _fix_dict(el, children) + p_out[p].append(children) + else: + p_out[p] = val + + input_str = child_object.GetDataModel(level, 1, 1) + props_list = json.loads(input_str) + props = {} + _fix_dict(props_list, props) + return props diff --git a/src/ansys/aedt/core/application/analysis.py b/src/ansys/aedt/core/application/analysis.py index 77789bf3177..6f7460d032e 100644 --- a/src/ansys/aedt/core/application/analysis.py +++ b/src/ansys/aedt/core/application/analysis.py @@ -55,9 +55,9 @@ from ansys.aedt.core.generic.general_methods import open_file from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.settings import settings -from ansys.aedt.core.modules.boundary import MaxwellParameters -from ansys.aedt.core.modules.boundary import NativeComponentObject -from ansys.aedt.core.modules.boundary import NativeComponentPCB +from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentObject +from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentPCB +from ansys.aedt.core.modules.boundary.maxwell_boundary import MaxwellParameters from ansys.aedt.core.modules.design_xploration import OptimizationSetups from ansys.aedt.core.modules.design_xploration import ParametricSetups from ansys.aedt.core.modules.solve_setup import Setup @@ -407,11 +407,9 @@ def nominal_sweep(self): References ---------- - >>> oModule.GelAllSolutionNames >>> oModule.GetSweeps """ - if len(self.existing_analysis_sweeps) > 1: return self.existing_analysis_sweeps[1] else: @@ -1539,14 +1537,16 @@ def get_setup(self, name): setup = SetupSBR(self, setuptype, name, is_new_setup=False) elif self.design_type in ["Q3D Extractor", "2D Extractor", "HFSS"]: setup = SetupHFSS(self, setuptype, name, is_new_setup=False) - if setup.props and setup.props.get("SetupType", "") == "HfssDrivenAuto": + if setup.properties: + if "Auto Solver Setting" in setup.properties: + setup = SetupHFSSAuto(self, 0, name, is_new_setup=False) + elif setup.props and setup.props.get("SetupType", "") == "HfssDrivenAuto": setup = SetupHFSSAuto(self, 0, name, is_new_setup=False) elif self.design_type in ["Maxwell 2D", "Maxwell 3D"]: setup = SetupMaxwell(self, setuptype, name, is_new_setup=False) else: setup = Setup(self, setuptype, name, is_new_setup=False) - if setup.props: - self.active_setup = name + self.active_setup = name return setup @pyaedt_function_handler() diff --git a/src/ansys/aedt/core/application/analysis_3d.py b/src/ansys/aedt/core/application/analysis_3d.py index 5861bf50cff..44f7dc0be11 100644 --- a/src/ansys/aedt/core/application/analysis_3d.py +++ b/src/ansys/aedt/core/application/analysis_3d.py @@ -1256,9 +1256,13 @@ def get_dxf_layers(self, file_path): with open_file(file_path, encoding="utf8") as f: lines = f.readlines() indices = self._find_indices(lines, "AcDbLayerTableRecord\n") + index_offset = 1 + if not indices: + indices = self._find_indices(lines, "LAYER\n") + index_offset = 3 for idx in indices: - if "2" in lines[idx + 1]: - layer_names.append(lines[idx + 2].replace("\n", "")) + if "2" in lines[idx + index_offset]: + layer_names.append(lines[idx + index_offset + 1].replace("\n", "")) return layer_names @pyaedt_function_handler(layers_list="layers") @@ -1295,7 +1299,7 @@ def import_dxf( Whether to join multiple straight line segments to form polylines. The default is ``True``. self_stitch_tolerance : float, optional - Self stitch tolerance value. The default is ``0``. + Self stitch tolerance value. If negative, let importer use its default tolerance. The default is ``0``. scale : float, optional Scaling factor. The default is ``0.001``. The units are ``mm``. defeature_geometry : bool, optional @@ -1330,7 +1334,7 @@ def import_dxf( >>> oEditor.ImportDXF """ - if self.desktop_class.non_graphical: + if self.desktop_class.non_graphical and self.desktop_class.aedt_version_id < "2024.2": self.logger.error("Method is supported only in graphical mode.") return False dxf_layers = self.get_dxf_layers(file_path) @@ -1349,7 +1353,8 @@ def import_dxf( vArg1.append("Scale:="), vArg1.append(scale) vArg1.append("AutoDetectClosed:="), vArg1.append(auto_detect_close) vArg1.append("SelfStitch:="), vArg1.append(self_stitch) - vArg1.append("SelfStitchTolerance:="), vArg1.append(self_stitch_tolerance) + if self_stitch_tolerance >= 0.0: + vArg1.append("SelfStitchTolerance:="), vArg1.append(self_stitch_tolerance) vArg1.append("DefeatureGeometry:="), vArg1.append(defeature_geometry) vArg1.append("DefeatureDistance:="), vArg1.append(defeature_distance) vArg1.append("RoundCoordinates:="), vArg1.append(round_coordinates) @@ -1475,21 +1480,15 @@ def import_gds_3d(self, input_file, mapping_layers, units="um", import_method=1) def _find_indices(self, list_to_check, item_to_find): # type: (list, str|int) -> list """Given a list, returns the list of indices for all occurrences of a given element. - Parameters ---------- list_to_check: list List to check. item_to_find: str, int Element to search for in the list. - Returns ------- list Indices of the occurrences of a given element. """ - indices = [] - for idx, value in enumerate(list_to_check): - if value == item_to_find: - indices.append(idx) - return indices + return [idx for idx, value in enumerate(list_to_check) if value == item_to_find] diff --git a/src/ansys/aedt/core/application/analysis_3d_layout.py b/src/ansys/aedt/core/application/analysis_3d_layout.py index dfae909eb90..83040e8dedf 100644 --- a/src/ansys/aedt/core/application/analysis_3d_layout.py +++ b/src/ansys/aedt/core/application/analysis_3d_layout.py @@ -147,7 +147,7 @@ def post(self): Returns ------- - :class:`ansys.aedt.core.visualization.post.post_common_3d.PostProcessor3D` + :class:`ansys.aedt.core.visualization.post.post_3dlayout.PostProcessor3DLayout` PostProcessor object. """ if self._post is None and self._odesign: diff --git a/src/ansys/aedt/core/application/analysis_nexxim.py b/src/ansys/aedt/core/application/analysis_nexxim.py index 08f9d93a8a1..ace967c2427 100644 --- a/src/ansys/aedt/core/application/analysis_nexxim.py +++ b/src/ansys/aedt/core/application/analysis_nexxim.py @@ -26,14 +26,14 @@ from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.modeler.circuits.object_3d_circuit import CircuitComponent -from ansys.aedt.core.modules.boundary import CurrentSinSource -from ansys.aedt.core.modules.boundary import Excitations -from ansys.aedt.core.modules.boundary import PowerIQSource -from ansys.aedt.core.modules.boundary import PowerSinSource -from ansys.aedt.core.modules.boundary import Sources -from ansys.aedt.core.modules.boundary import VoltageDCSource -from ansys.aedt.core.modules.boundary import VoltageFrequencyDependentSource -from ansys.aedt.core.modules.boundary import VoltageSinSource +from ansys.aedt.core.modules.boundary.circuit_boundary import CurrentSinSource +from ansys.aedt.core.modules.boundary.circuit_boundary import Excitations +from ansys.aedt.core.modules.boundary.circuit_boundary import PowerIQSource +from ansys.aedt.core.modules.boundary.circuit_boundary import PowerSinSource +from ansys.aedt.core.modules.boundary.circuit_boundary import Sources +from ansys.aedt.core.modules.boundary.circuit_boundary import VoltageDCSource +from ansys.aedt.core.modules.boundary.circuit_boundary import VoltageFrequencyDependentSource +from ansys.aedt.core.modules.boundary.circuit_boundary import VoltageSinSource from ansys.aedt.core.modules.setup_templates import SetupKeys from ansys.aedt.core.modules.solve_setup import SetupCircuit diff --git a/src/ansys/aedt/core/application/design.py b/src/ansys/aedt/core/application/design.py index e35aa9c6795..e56b0ae77e7 100644 --- a/src/ansys/aedt/core/application/design.py +++ b/src/ansys/aedt/core/application/design.py @@ -77,9 +77,9 @@ from ansys.aedt.core.generic.general_methods import settings from ansys.aedt.core.generic.general_methods import write_csv from ansys.aedt.core.generic.load_aedt_file import load_entire_aedt_file -from ansys.aedt.core.modules.boundary import BoundaryObject -from ansys.aedt.core.modules.boundary import MaxwellParameters -from ansys.aedt.core.modules.boundary import NetworkObject +from ansys.aedt.core.modules.boundary.circuit_boundary import NetworkObject +from ansys.aedt.core.modules.boundary.common import BoundaryObject +from ansys.aedt.core.modules.boundary.maxwell_boundary import MaxwellParameters if sys.version_info.major > 2: import base64 @@ -376,6 +376,12 @@ def boundaries(self): current_excitation_types = ee[1::2] ff = [i.split(":")[0] for i in ee] bb.extend(ff) + for i in set(current_excitation_types): + if "GetExcitationsOfType" in self.oboundary.__dir__(): + ports = list(self.oboundary.GetExcitationsOfType(i)) + for p in ports: + bb.append(p) + bb.append(i) elif ( self.oboundary and "Excitations" in self.get_oo_name(self.odesign) @@ -431,6 +437,7 @@ def boundaries(self): del self._boundaries[k] for boundary, boundarytype in zip(current_boundaries, current_types): if boundary in self._boundaries: + self._boundaries[boundary]._initialize_bynary_tree() continue if boundarytype == "MaxwellParameters": maxwell_parameter_type = self.get_oo_property_value(self.odesign, f"Parameters\\{boundary}", "Type") @@ -1498,10 +1505,8 @@ def export_profile(self, setup, variation="", output_file=None): References ---------- - >>> oDesign.ExportProfile """ - if not output_file: output_file = os.path.join(self.working_directory, generate_unique_name("Profile") + ".prof") if not variation: @@ -1577,7 +1582,6 @@ def add_info_message(self, text, level=None): >>> hfss.logger.info("Global info message") >>> hfss.logger.project_logger.info("Project info message") >>> hfss.logger.design_logger.info("Design info message") - """ warnings.warn( "`add_info_message` is deprecated. Use `logger.design_logger.info` instead.", @@ -1620,7 +1624,6 @@ def add_warning_message(self, text, level=None): >>> hfss.logger.warning("Global warning message", "Global") >>> hfss.logger.project_logger.warning("Project warning message", "Project") >>> hfss.logger.design_logger.warning("Design warning message") - """ warnings.warn( "`add_warning_message` is deprecated. Use `logger.design_logger.warning` instead.", @@ -1664,7 +1667,6 @@ def add_error_message(self, text, level=None): >>> hfss.logger.error("Global error message", "Global") >>> hfss.logger.project_logger.error("Project error message", "Project") >>> hfss.logger.design_logger.error("Design error message") - """ warnings.warn( "`add_error_message` is deprecated. Use `logger.design_logger.error` instead.", @@ -1757,6 +1759,7 @@ def set_registry_key(self, name, value): Full name of the AEDT registry key. value : str, int Value for the AEDT registry key. + Returns ------- bool @@ -1764,7 +1767,6 @@ def set_registry_key(self, name, value): References ---------- - >>> oDesktop.SetRegistryString >>> oDesktop.SetRegistryInt """ @@ -3801,7 +3803,7 @@ def save_project(self, file_name=None, overwrite=True, refresh_ids=False): self.oproject.Save() if refresh_ids: self.modeler.refresh_all_ids() - self.modeler._refresh_all_ids_from_aedt_file() + self.modeler._refresh_all_ids_wrapper() self.mesh._refresh_mesh_operations() self._project_name = None self._project_path = None diff --git a/src/ansys/aedt/core/application/variables.py b/src/ansys/aedt/core/application/variables.py index 5eea6526c6a..e3e73e487f2 100644 --- a/src/ansys/aedt/core/application/variables.py +++ b/src/ansys/aedt/core/application/variables.py @@ -1004,6 +1004,7 @@ def set_variable( circuit_parameter : bool, optional Whether to define a parameter in a circuit design or a local parameter. The default is ``True``, in which case a circuit variable is created as a parameter default. + Returns ------- bool @@ -1011,7 +1012,6 @@ def set_variable( References ---------- - >>> oProject.ChangeProperty >>> oDesign.ChangeProperty @@ -1044,7 +1044,6 @@ def set_variable( creating the variable if it does not exist. >>> aedtapp.variable_manager.set_variable["$p1"] == "30mm" - """ if name in self._independent_variables: del self._independent_variables[name] @@ -1354,7 +1353,7 @@ def _find_used_variable_history(self, history, var_name): """ used = False - for _, v in history.props.items(): + for _, v in history.properties.items(): if isinstance(v, str) and var_name in re.findall("[a-zA-Z0-9_]+", v): return True for el in history.children.values(): diff --git a/src/ansys/aedt/core/circuit.py b/src/ansys/aedt/core/circuit.py index c6a8586be6c..beb3fbb4b97 100644 --- a/src/ansys/aedt/core/circuit.py +++ b/src/ansys/aedt/core/circuit.py @@ -47,13 +47,13 @@ from ansys.aedt.core.generic.general_methods import read_configuration_file from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.hfss3dlayout import Hfss3dLayout -from ansys.aedt.core.modules.boundary import CurrentSinSource -from ansys.aedt.core.modules.boundary import PowerIQSource -from ansys.aedt.core.modules.boundary import PowerSinSource -from ansys.aedt.core.modules.boundary import Sources -from ansys.aedt.core.modules.boundary import VoltageDCSource -from ansys.aedt.core.modules.boundary import VoltageFrequencyDependentSource -from ansys.aedt.core.modules.boundary import VoltageSinSource +from ansys.aedt.core.modules.boundary.circuit_boundary import CurrentSinSource +from ansys.aedt.core.modules.boundary.circuit_boundary import PowerIQSource +from ansys.aedt.core.modules.boundary.circuit_boundary import PowerSinSource +from ansys.aedt.core.modules.boundary.circuit_boundary import Sources +from ansys.aedt.core.modules.boundary.circuit_boundary import VoltageDCSource +from ansys.aedt.core.modules.boundary.circuit_boundary import VoltageFrequencyDependentSource +from ansys.aedt.core.modules.boundary.circuit_boundary import VoltageSinSource from ansys.aedt.core.modules.circuit_templates import SourceKeys @@ -981,8 +981,7 @@ def create_touchstone_report( differential_pairs=False, subdesign_id=None, ): - """ - Create a Touchstone plot. + """Create a Touchstone plot. Parameters ---------- @@ -998,6 +997,7 @@ def create_touchstone_report( Whether the plot is on differential pairs traces. The default is ``False``. subdesign_id : int, optional Specify a subdesign ID to export a Touchstone file of this subdesign. The default value is ``None``. + Returns ------- bool @@ -1005,7 +1005,6 @@ def create_touchstone_report( References ---------- - >>> oModule.CreateReport """ if not solution: diff --git a/src/ansys/aedt/core/filtersolutions_core/export_to_aedt.py b/src/ansys/aedt/core/filtersolutions_core/export_to_aedt.py index 61750324238..0d4322adeea 100644 --- a/src/ansys/aedt/core/filtersolutions_core/export_to_aedt.py +++ b/src/ansys/aedt/core/filtersolutions_core/export_to_aedt.py @@ -1661,6 +1661,7 @@ def modelithics_capacitor_add_family(self, modelithics_capacitor) -> str: def modelithics_capacitor_remove_family(self, modelithics_capacitor) -> str: """Remove a specified ``Modelithics`` capacitor family from the capacitor family list. + Parameters ---------- modelithics_capacitor : str @@ -1694,8 +1695,10 @@ def modelithics_resistor_list(self, row_index) -> str: @property def modelithics_resistor_selection(self) -> str: """Selected ``Modelithics`` resistor family from the loaded list. + The Modelithics resistor family selection allows you to choose a specific resistor model from the Modelithics library. + Returns ------- str diff --git a/src/ansys/aedt/core/generic/configurations.py b/src/ansys/aedt/core/generic/configurations.py index 5ccd1131b47..ed36cf50666 100644 --- a/src/ansys/aedt/core/generic/configurations.py +++ b/src/ansys/aedt/core/generic/configurations.py @@ -43,10 +43,10 @@ from ansys.aedt.core.modeler.cad.components_3d import UserDefinedComponent from ansys.aedt.core.modeler.cad.modeler import CoordinateSystem from ansys.aedt.core.modeler.geometry_operators import GeometryOperators -from ansys.aedt.core.modules.boundary import BoundaryObject -from ansys.aedt.core.modules.boundary import BoundaryProps -from ansys.aedt.core.modules.boundary import NativeComponentObject -from ansys.aedt.core.modules.boundary import NativeComponentPCB +from ansys.aedt.core.modules.boundary.common import BoundaryObject +from ansys.aedt.core.modules.boundary.common import BoundaryProps +from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentObject +from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentPCB from ansys.aedt.core.modules.design_xploration import SetupOpti from ansys.aedt.core.modules.design_xploration import SetupParam from ansys.aedt.core.modules.material_lib import Material @@ -1714,8 +1714,7 @@ def _export_mesh_operations(self, dict_out): @pyaedt_function_handler() def update_monitor(self, m_case, m_object, m_quantity, m_name): - """ - Generic method for inserting monitor object + """Generic method for inserting monitor object Parameters ---------- @@ -1727,6 +1726,7 @@ def update_monitor(self, m_case, m_object, m_quantity, m_name): Name or list of names of the quantity being monitored. m_name : str Name of the monitor object. + Returns ------- bool diff --git a/src/ansys/aedt/core/generic/data_handlers.py b/src/ansys/aedt/core/generic/data_handlers.py index adc72547b78..095c7482c0c 100644 --- a/src/ansys/aedt/core/generic/data_handlers.py +++ b/src/ansys/aedt/core/generic/data_handlers.py @@ -205,61 +205,6 @@ def _arg2dict(arg, dict_out): raise ValueError("Incorrect data argument format") -@pyaedt_function_handler() -def create_list_for_csharp(input_list, return_strings=False): - """ - - Parameters - ---------- - input_list : - - return_strings : - (Default value = False) - - Returns - ------- - - """ - from ansys.aedt.core.generic.clr_module import Double - from ansys.aedt.core.generic.clr_module import List - - if return_strings: - col = List[str]() - else: - col = List[Double]() - - for el in input_list: - if return_strings: - col.Add(str(el)) - else: - col.Add(el) - return col - - -@pyaedt_function_handler() -def create_table_for_csharp(input_list_of_list, return_strings=True): - """ - - Parameters - ---------- - input_list_of_list : - - return_strings : - (Default value = True) - - Returns - ------- - - """ - from ansys.aedt.core.generic.clr_module import List - - new_table = List[List[str]]() - for col in input_list_of_list: - newcol = create_list_for_csharp(col, return_strings) - new_table.Add(newcol) - return new_table - - @pyaedt_function_handler() def format_decimals(el): """ diff --git a/src/ansys/aedt/core/generic/general_methods.py b/src/ansys/aedt/core/generic/general_methods.py index dc7d593af7a..fcb8ac29873 100644 --- a/src/ansys/aedt/core/generic/general_methods.py +++ b/src/ansys/aedt/core/generic/general_methods.py @@ -514,11 +514,10 @@ def read_toml(file_path): # pragma: no cover dict Parsed TOML file as a dictionary. """ - current_python_version = sys.version_info[:2] - if current_python_version < (3, 12): - import pytomlpp as tomllib - else: + try: import tomllib + except (ImportError, ModuleNotFoundError): + import tomli as tomllib with open_file(file_path, "rb") as fb: return tomllib.load(fb) @@ -1298,11 +1297,7 @@ def is_digit(c): @pyaedt_function_handler() def _create_toml_file(input_dict, full_toml_path): - current_python_version = sys.version_info[:2] - if current_python_version < (3, 12): - import pytomlpp as tomllib - else: - import tomllib + import tomli_w if not os.path.exists(os.path.dirname(full_toml_path)): os.makedirs(os.path.dirname(full_toml_path)) @@ -1322,8 +1317,8 @@ def _dict_toml(d): return new_dict new_dict = _dict_toml(input_dict) - with open_file(full_toml_path, "w") as fp: - tomllib.dump(new_dict, fp) + with open_file(full_toml_path, "wb") as fp: + tomli_w.dump(new_dict, fp) settings.logger.info(f"{full_toml_path} correctly created.") return True diff --git a/src/ansys/aedt/core/generic/settings.py b/src/ansys/aedt/core/generic/settings.py index 194b9c16b08..96e3825903b 100644 --- a/src/ansys/aedt/core/generic/settings.py +++ b/src/ansys/aedt/core/generic/settings.py @@ -100,6 +100,7 @@ "remote_rpc_service_manager_port", "pyaedt_server_path", "remote_rpc_session_temp_folder", + "block_figure_plot", ] ALLOWED_AEDT_ENV_VAR_SETTINGS = [ "ANSYSEM_FEATURE_F335896_MECHANICAL_STRUCTURAL_SOLN_TYPE_ENABLE", @@ -112,6 +113,7 @@ "ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE", "ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE", "ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3", + "ANS_NODEPCHECK", ] @@ -133,7 +135,7 @@ class _InnerProjectSettings: # pragma: no cover time_stamp: Union[int, float] = 0 -class Settings(object): # pragma: no cover +class Settings(object): """Manages all PyAEDT environment variables and global settings.""" def __init__(self): @@ -209,6 +211,7 @@ def __init__(self): self.__remote_api: bool = False self.__time_tick = time.time() self.__pyaedt_server_path = "" + self.__block_figure_plot = False # Load local settings if YAML configuration file exists. pyaedt_settings_path = os.environ.get("PYAEDT_LOCAL_SETTINGS_PATH", "") @@ -218,7 +221,6 @@ def __init__(self): else: pyaedt_settings_path = os.path.join(os.environ["APPDATA"], "pyaedt_settings.yaml") self.load_yaml_configuration(pyaedt_settings_path) - self.__block_figure_plot = False # ########################## Logging properties ########################## @@ -286,7 +288,8 @@ def global_log_file_name(self): @global_log_file_name.setter def global_log_file_name(self, value): - self.__global_log_file_name = value + if value is not None: + self.__global_log_file_name = value @property def enable_debug_methods_argument_logger(self): @@ -472,7 +475,8 @@ def lsf_ui(self): @lsf_ui.setter def lsf_ui(self, value): - self.__lsf_ui = int(value) + if value is not None: + self.__lsf_ui = int(value) @property def lsf_timeout(self): @@ -513,7 +517,7 @@ def aedt_environment_variables(self): @aedt_environment_variables.setter def aedt_environment_variables(self, value): - self._aedt_environment_variables = value + self.__aedt_environment_variables = value # ##################################### General properties #################################### @@ -643,9 +647,10 @@ def aedt_version(self): @aedt_version.setter def aedt_version(self, value): - self.__aedt_version = value - if self.__aedt_version >= "2023.1": - self.disable_bounding_box_sat = True + if value is not None: + self.__aedt_version = value + if self.__aedt_version >= "2023.1": + self.disable_bounding_box_sat = True @property def aedt_install_dir(self): @@ -679,8 +684,9 @@ def edb_dll_path(self): @edb_dll_path.setter def edb_dll_path(self, value): - if os.path.exists(value): - self.__edb_dll_path = value + if value is not None: + if os.path.exists(value): + self.__edb_dll_path = value @property def enable_pandas_output(self): @@ -764,24 +770,37 @@ def filter_settings_with_raise(settings: dict, allowed_keys: List[str]): pairs = [ ("log", ALLOWED_LOG_SETTINGS), ("lsf", ALLOWED_LSF_SETTINGS), - ("aedt_env_var", ALLOWED_AEDT_ENV_VAR_SETTINGS), ("general", ALLOWED_GENERAL_SETTINGS), ] for setting_type, allowed_settings_key in pairs: settings = local_settings.get(setting_type, {}) + print(setting_type, allowed_settings_key) if raise_on_wrong_key: for key, value in filter_settings_with_raise(settings, allowed_settings_key): setattr(self, key, value) else: for key, value in filter_settings(settings, allowed_settings_key): setattr(self, key, value) + # NOTE: Handle env var differently as they are loaded at once + setting_type = "aedt_env_var" + settings = local_settings.get(setting_type, {}) + if settings: + if raise_on_wrong_key and any(key not in ALLOWED_AEDT_ENV_VAR_SETTINGS for key in settings.keys()): + raise KeyError("An environment variable key is not part of the allowed keys.") + self.aedt_environment_variables = settings def writte_yaml_configuration(self, path: str): """Write the current settings into a YAML configuration file.""" import yaml - if os.path.exists(path): - yaml.safe_dump(settings, path) + data = {} + data["log"] = {key: getattr(self, key) for key in ALLOWED_LOG_SETTINGS} + data["lsf"] = {key: getattr(self, key) for key in ALLOWED_LSF_SETTINGS} + data["aedt_env_var"] = getattr(self, "aedt_environment_variables") + data["general"] = {key: getattr(self, key) for key in ALLOWED_GENERAL_SETTINGS} + + with open(path, "w") as file: + yaml.safe_dump(data, file, sort_keys=False) settings = Settings() diff --git a/src/ansys/aedt/core/hfss.py b/src/ansys/aedt/core/hfss.py index 95a0539c398..9dc8dc09b1d 100644 --- a/src/ansys/aedt/core/hfss.py +++ b/src/ansys/aedt/core/hfss.py @@ -22,7 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""This module contains these classes: ``Hfss`` and ``BoundaryType``.""" +"""This module contains the ``Hfss`` class.""" from __future__ import absolute_import # noreorder @@ -49,10 +49,10 @@ from ansys.aedt.core.modeler.cad.component_array import ComponentArray from ansys.aedt.core.modeler.cad.components_3d import UserDefinedComponent from ansys.aedt.core.modeler.geometry_operators import GeometryOperators -from ansys.aedt.core.modules.boundary import BoundaryObject -from ansys.aedt.core.modules.boundary import FarFieldSetup -from ansys.aedt.core.modules.boundary import NativeComponentObject -from ansys.aedt.core.modules.boundary import NearFieldSetup +from ansys.aedt.core.modules.boundary.common import BoundaryObject +from ansys.aedt.core.modules.boundary.hfss_boundary import FarFieldSetup +from ansys.aedt.core.modules.boundary.hfss_boundary import NearFieldSetup +from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentObject from ansys.aedt.core.modules.setup_templates import SetupKeys @@ -249,10 +249,17 @@ def field_setups(self): Returns ------- List of :class:`ansys.aedt.core.modules.boundary.FarFieldSetup` and - :class:`ansys.aedt.core.modules.boundary.NearFieldSetup` + :class:`ansys.aedt.core.modules.hfss_boundary.NearFieldSetup` """ - if not self._field_setups: - self._field_setups = self._get_rad_fields() + for field in self.field_setup_names: + obj_field = self.odesign.GetChildObject("Radiation").GetChildObject(field) + type_field = obj_field.GetPropValue("Type") + if type_field == "Infinite Sphere": + self._field_setups.append(FarFieldSetup(self, field, {}, "FarFieldSphere")) + else: + self._field_setups.append(NearFieldSetup(self, field, {}, f"NearField{type_field}")) + # if not self._field_setups: + # self._field_setups = self._get_rad_fields() return self._field_setups @property @@ -372,31 +379,6 @@ def _get_unique_source_name(self, source_name, root_name): source_name = generate_unique_name(source_name) return source_name - @pyaedt_function_handler() - def _get_rad_fields(self): - if not self.design_properties: - return [] - fields = [] - if self.design_properties.get("RadField"): - if self.design_properties["RadField"].get("FarFieldSetups"): - for val in self.design_properties["RadField"]["FarFieldSetups"]: - p = self.design_properties["RadField"]["FarFieldSetups"][val] - if isinstance(p, dict) and p.get("Type") == "Infinite Sphere": - fields.append(FarFieldSetup(self, val, p, "FarFieldSphere")) - if self.design_properties["RadField"].get("NearFieldSetups"): - for val in self.design_properties["RadField"]["NearFieldSetups"]: - p = self.design_properties["RadField"]["NearFieldSetups"][val] - if isinstance(p, dict): - if p["Type"] == "Near Rectangle": - fields.append(NearFieldSetup(self, val, p, "NearFieldRectangle")) - elif p["Type"] == "Near Line": - fields.append(NearFieldSetup(self, val, p, "NearFieldLine")) - elif p["Type"] == "Near Box": - fields.append(NearFieldSetup(self, val, p, "NearFieldBox")) - elif p["Type"] == "Near Sphere": - fields.append(NearFieldSetup(self, val, p, "NearFieldSphere")) - return fields - @pyaedt_function_handler() def _create_boundary(self, name, props, boundary_type): """Create a boundary. @@ -1482,7 +1464,7 @@ def create_sbr_antenna( Returns ------- - :class:`ansys.aedt.core.modules.boundary.NativeComponentObject` + :class:`ansys.aedt.core.modules.layout_boundary.NativeComponentObject` NativeComponentObject object. References @@ -1608,7 +1590,7 @@ def create_sbr_file_based_antenna( Returns ------- - :class:`ansys.aedt.core.modules.boundary.NativeComponentObject` + :class:`ansys.aedt.core.modules.layout_boundary.NativeComponentObject` References ---------- @@ -5139,7 +5121,7 @@ def insert_infinite_sphere( Returns ------- - :class:`ansys.aedt.core.modules.boundary.FarFieldSetup` + :class:`ansys.aedt.core.modules.hfss_boundary.FarFieldSetup` """ if not self.oradfield: self.logger.error("Radiation Field not available in this solution.") @@ -5231,7 +5213,7 @@ def insert_near_field_sphere( Returns ------- - :class:`ansys.aedt.core.modules.boundary.NearFieldSetup` + :class:`ansys.aedt.core.modules.hfss_boundary.NearFieldSetup` """ if not self.oradfield: self.logger.error("Radiation Field not available in this solution.") @@ -5308,7 +5290,7 @@ def insert_near_field_box( Returns ------- - :class:`ansys.aedt.core.modules.boundary.NearFieldSetup` + :class:`ansys.aedt.core.modules.hfss_boundary.NearFieldSetup` """ if not self.oradfield: self.logger.error("Radiation Field not available in this solution.") @@ -5377,7 +5359,7 @@ def insert_near_field_rectangle( Returns ------- - :class:`ansys.aedt.core.modules.boundary.NearFieldSetup` + :class:`ansys.aedt.core.modules.hfss_boundary.NearFieldSetup` """ if not self.oradfield: self.logger.error("Radiation Field not available in this solution.") @@ -5432,7 +5414,7 @@ def insert_near_field_line( Returns ------- - :class:`ansys.aedt.core.modules.boundary.NearFieldSetup` + :class:`ansys.aedt.core.modules.hfss_boundary.NearFieldSetup` """ if not self.oradfield: self.logger.error("Radiation Field not available in this solution.") diff --git a/src/ansys/aedt/core/hfss3dlayout.py b/src/ansys/aedt/core/hfss3dlayout.py index 307ffd1f82a..4f0b9e62211 100644 --- a/src/ansys/aedt/core/hfss3dlayout.py +++ b/src/ansys/aedt/core/hfss3dlayout.py @@ -41,7 +41,7 @@ from ansys.aedt.core.generic.general_methods import tech_to_control_file from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.modeler.pcb.object_3d_layout import Line3dLayout # noqa: F401 -from ansys.aedt.core.modules.boundary import BoundaryObject3dLayout +from ansys.aedt.core.modules.boundary.layout_boundary import BoundaryObject3dLayout class Hfss3dLayout(FieldAnalysis3DLayout, ScatteringMethods): @@ -254,7 +254,7 @@ def create_edge_port( Returns ------- - :class:`ansys.aedt.core.modules.boundary.BoundaryObject3dLayout` + :class:`ansys.aedt.core.modules.layout_boundary.BoundaryObject3dLayout` Port objcet port when successful, ``False`` when failed. References @@ -354,7 +354,7 @@ def create_wave_port( Returns ------- - :class:`ansys.aedt.core.modules.boundary.BoundaryObject3dLayout` + :class:`ansys.aedt.core.modules.layout_boundary.BoundaryObject3dLayout` Port objcet port when successful, ``False`` when failed. References @@ -395,7 +395,7 @@ def create_wave_port_from_two_conductors(self, assignment=None, edge_numbers=Non Returns ------- - :class:`ansys.aedt.core.modules.boundary.BoundaryObject3dLayout` + :class:`ansys.aedt.core.modules.layout_boundary.BoundaryObject3dLayout` Port objcet port when successful, ``False`` when failed. References @@ -473,7 +473,7 @@ def create_ports_on_component_by_nets( Returns ------- - list of :class:`ansys.aedt.core.modules.boundary.BoundaryObject3dLayout` + list of :class:`ansys.aedt.core.modules.layout_boundary.BoundaryObject3dLayout` Port Objects when successful. References @@ -550,7 +550,7 @@ def create_differential_port(self, via_signal, via_reference, name, deembed=True Returns ------- - :class:`ansys.aedt.core.modules.boundary.BoundaryObject3dLayout` + :class:`ansys.aedt.core.modules.layout_boundary.BoundaryObject3dLayout` Port Object when successful, ``False`` when failed. References @@ -599,7 +599,7 @@ def create_coax_port(self, via, radial_extent=0.1, layer=None, alignment="lower" Returns ------- - :class:`ansys.aedt.core.modules.boundary.BoundaryObject3dLayout` + :class:`ansys.aedt.core.modules.layout_boundary.BoundaryObject3dLayout` Port Object when successful, ``False`` when failed. References @@ -652,7 +652,7 @@ def create_pin_port(self, name, x=0, y=0, rotation=0, top_layer=None, bottom_lay Returns ------- - :class:`ansys.aedt.core.modules.boundary.BoundaryObject3dLayout` + :class:`ansys.aedt.core.modules.layout_boundary.BoundaryObject3dLayout` ``True`` when successful, ``False`` when failed. @@ -1071,6 +1071,7 @@ def set_meshing_settings(self, mesh_method="Phi", enable_intersections_check=Tru ``True``. use_alternative_fallback : bool, optional Whether to enable the alternative fall back mesh method. The default is ``True``. + Returns ------- bool @@ -1078,7 +1079,6 @@ def set_meshing_settings(self, mesh_method="Phi", enable_intersections_check=Tru References ---------- - >>> oDesign.DesignOptions """ settings = [] @@ -1459,6 +1459,7 @@ def import_gds( close_active_project : bool, optional Whether to close the active project after loading the GDS file. The default is ''False``. + Returns ------- bool @@ -1466,7 +1467,6 @@ def import_gds( References ---------- - >>> oModule.ImportGDSII """ return self._import_cad(input_file, "gds", output_dir, control_file, set_as_active, close_active_project) @@ -1493,6 +1493,7 @@ def import_dxf( close_active_project : bool, optional Whether to close the active project after loading the DXF file. The default is ''False``. + Returns ------- bool @@ -1500,7 +1501,6 @@ def import_dxf( References ---------- - >>> oModule.ImportDXF """ return self._import_cad(input_file, "dxf", output_dir, control_file, set_as_active, close_active_project) @@ -1525,6 +1525,7 @@ def import_gerber( close_active_project : bool, optional Whether to close the active project after loading the Gerber zip file file. The default is ''False``. + Returns ------- bool @@ -1532,7 +1533,6 @@ def import_gerber( References ---------- - >>> oModule.ImportGerber """ return self._import_cad(input_file, "gerber", output_dir, control_file, set_as_active, close_active_project) @@ -1590,6 +1590,7 @@ def import_awr( close_active_project : bool, optional Whether to close the active project after loading the AWR Microwave Office file. The default is ''False``. + Returns ------- bool @@ -1597,7 +1598,6 @@ def import_awr( References ---------- - >>> oModule.ImportAWRMicrowaveOffice """ return self._import_cad(input_file, "awr", output_dir, control_file, set_as_active, close_active_project) @@ -1622,6 +1622,7 @@ def import_ipc2581( close_active_project : bool, optional Whether to close the active project after loading the IPC2581 file. The default is ''False``. + Returns ------- bool @@ -1629,7 +1630,6 @@ def import_ipc2581( References ---------- - >>> oModule.ImportAWRMicrowaveOffice """ return self._import_cad(input_file, "ipc2581", output_dir, control_file, set_as_active, close_active_project) @@ -1654,6 +1654,7 @@ def import_odb( close_active_project : bool, optional Whether to close the active project after loading the ODB++ file. The default is ''False``. + Returns ------- bool @@ -1661,7 +1662,6 @@ def import_odb( References ---------- - >>> oModule.ImportAWRMicrowaveOffice """ return self._import_cad(input_file, "odb++", output_dir, control_file, set_as_active, close_active_project) @@ -2128,12 +2128,14 @@ def _update_port_info(self, port): @pyaedt_function_handler() def get_model_from_mesh_results(self, binary=True): """Get the path for the parasolid file in the result folder. + The parasolid file is generated after the mesh is created in 3D Layout. Parameters ---------- binary : str, optional Either if retrieve binary format of parasoli or not. + Returns ------- str @@ -2321,6 +2323,7 @@ def get_dcir_solution_data(self, setup, show="RL", category="Loop_Resistance"): category : str, optional Name of the element. Options are ``"Voltage"`, ``"Current"`, ``"Power"``, ``"Loop_Resistance"``, ``"Path_Resistance"``, ``"Resistance"``, ``"Inductance"``, ``"X"``, ``"Y"``, ``"Limit"`` and ``"IR Drop"``. + Returns ------- from ansys.aedt.core.modules.solutions.SolutionData @@ -2344,6 +2347,7 @@ def get_dcir_element_data_loop_resistance(self, setup): ---------- setup : str Name of the setup. + Returns ------- pandas.Dataframe @@ -2383,6 +2387,7 @@ def get_dcir_element_data_current_source(self, setup): ---------- setup : str Name of the setup. + Returns ------- pandas.Dataframe @@ -2416,6 +2421,7 @@ def get_dcir_element_data_via(self, setup): ---------- setup : str Name of the setup. + Returns ------- pandas.Dataframe diff --git a/src/ansys/aedt/core/icepak.py b/src/ansys/aedt/core/icepak.py index f3adda12068..f65e6f34ed7 100644 --- a/src/ansys/aedt/core/icepak.py +++ b/src/ansys/aedt/core/icepak.py @@ -48,18 +48,18 @@ from ansys.aedt.core.modeler.cad.elements_3d import FacePrimitive from ansys.aedt.core.modeler.geometry_operators import GeometryOperators from ansys.aedt.core.modeler.geometry_operators import GeometryOperators as go -from ansys.aedt.core.modules.boundary import BoundaryDictionary -from ansys.aedt.core.modules.boundary import BoundaryObject -from ansys.aedt.core.modules.boundary import ExponentialDictionary -from ansys.aedt.core.modules.boundary import LinearDictionary -from ansys.aedt.core.modules.boundary import NativeComponentObject -from ansys.aedt.core.modules.boundary import NativeComponentPCB -from ansys.aedt.core.modules.boundary import NetworkObject -from ansys.aedt.core.modules.boundary import PieceWiseLinearDictionary -from ansys.aedt.core.modules.boundary import PowerLawDictionary -from ansys.aedt.core.modules.boundary import SinusoidalDictionary -from ansys.aedt.core.modules.boundary import SquareWaveDictionary -from ansys.aedt.core.modules.boundary import _create_boundary +from ansys.aedt.core.modules.boundary.circuit_boundary import NetworkObject +from ansys.aedt.core.modules.boundary.common import BoundaryObject +from ansys.aedt.core.modules.boundary.icepak_boundary import BoundaryDictionary +from ansys.aedt.core.modules.boundary.icepak_boundary import ExponentialDictionary +from ansys.aedt.core.modules.boundary.icepak_boundary import LinearDictionary +from ansys.aedt.core.modules.boundary.icepak_boundary import PieceWiseLinearDictionary +from ansys.aedt.core.modules.boundary.icepak_boundary import PowerLawDictionary +from ansys.aedt.core.modules.boundary.icepak_boundary import SinusoidalDictionary +from ansys.aedt.core.modules.boundary.icepak_boundary import SquareWaveDictionary +from ansys.aedt.core.modules.boundary.icepak_boundary import _create_boundary +from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentObject +from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentPCB from ansys.aedt.core.modules.setup_templates import SetupKeys from ansys.aedt.core.visualization.post.monitor_icepak import Monitor @@ -1699,6 +1699,7 @@ def edit_design_settings( skip_intersection_checks : bool, optional Whether to skip intersection checks during validation check. The default value is ``False``. + Returns ------- bool @@ -1706,7 +1707,6 @@ def edit_design_settings( References ---------- - >>> oDesign.SetDesignSettings """ # diff --git a/src/ansys/aedt/core/maxwell.py b/src/ansys/aedt/core/maxwell.py index 1822002afeb..2c692d79067 100644 --- a/src/ansys/aedt/core/maxwell.py +++ b/src/ansys/aedt/core/maxwell.py @@ -43,8 +43,8 @@ from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.modeler.cad.elements_3d import FacePrimitive from ansys.aedt.core.modeler.geometry_operators import GeometryOperators -from ansys.aedt.core.modules.boundary import BoundaryObject -from ansys.aedt.core.modules.boundary import MaxwellParameters +from ansys.aedt.core.modules.boundary.common import BoundaryObject +from ansys.aedt.core.modules.boundary.maxwell_boundary import MaxwellParameters from ansys.aedt.core.modules.setup_templates import SetupKeys @@ -166,6 +166,7 @@ def apply_skew( Only available if skew_type is ``User Defined``. The length of this list must be equal to number_of_slices. The default value is ``None``. + Returns ------- bool @@ -2444,7 +2445,6 @@ def assign_impedance( >>> impedance_assignment = m3d.assign_impedance(assignment=shield_faces,impedance="ShieldImpedance") >>> m3d.release_desktop(True, True) """ - if self.solution_type in [ "EddyCurrent", "Transient", @@ -2546,7 +2546,6 @@ def _create_boundary(self, name, props, boundary_type): Boundary object. """ - bound = BoundaryObject(self, name, props, boundary_type) result = bound.create() if result: @@ -2625,7 +2624,6 @@ def assign_master_slave( References ---------- - >>> oModule.AssignIndependent >>> oModule.AssignDependent """ @@ -2721,12 +2719,10 @@ def assign_flux_tangential(self, assignment, flux_name=None): References ---------- - >>> oModule.AssignFluxTangential Examples -------- - Create a box and assign a flux tangential boundary to one of its faces. >>> from ansys.aedt.core import Maxwell3d @@ -2783,12 +2779,10 @@ def assign_layout_force( References ---------- - >>> oModule.AssignLayoutForce Examples -------- - Create a dictionary to give as an input to assign_layout_force method. >>> nets_layers = {"": ["PWR","TOP","UNNAMED_000","UNNAMED_002"], >>> "GND": ["LYR_1","LYR_2","UNNAMED_006"]} @@ -2800,7 +2794,6 @@ def assign_layout_force( >>> m3d.assign_layout_force(net_layers=nets_layers,component_name="LC1_1") >>> m3d.release_desktop(True, True) """ - for key in net_layers.keys(): if not isinstance(net_layers[key], list): net_layers[key] = list(net_layers[key]) @@ -2882,7 +2875,6 @@ def assign_tangential_h_field( References ---------- - >>> oModule.AssignTangentialHField """ if self.solution_type not in ["EddyCurrent", "Magnetostatic"]: @@ -2941,7 +2933,6 @@ def assign_zero_tangential_h_field(self, assignment, boundary=None): References ---------- - >>> oModule.AssignZeroTangentialHField """ if self.solution_type not in ["EddyCurrent"]: @@ -3062,9 +3053,14 @@ def assign_resistive_sheet( if not name: boundary = generate_unique_name("ResistiveSheet") - props = { - "Faces": assignment, - } + listobj = self.modeler.convert_to_selections(assignment, True) + + props = {"Objects": [], "Faces": []} + for sel in listobj: + if isinstance(sel, str): + props["Objects"].append(sel) + elif isinstance(sel, int): + props["Faces"].append(sel) if self.solution_type in ["EddyCurrent", "Transient"]: props["Resistance"] = resistance @@ -3165,7 +3161,6 @@ class Maxwell2d(Maxwell, FieldAnalysis3D, object): ``designname`` in a project named ``projectname``. >>> m2d = Maxwell2d(projectname,designname) - """ @property # for legacy purposes @@ -3270,7 +3265,6 @@ def generate_design_data(self, line_filter=None, object_filter=None): ------- bool ``True`` when successful, ``False`` when failed. - """ def convert(obj): diff --git a/src/ansys/aedt/core/mechanical.py b/src/ansys/aedt/core/mechanical.py index 2f212fa4862..95bb740cd21 100644 --- a/src/ansys/aedt/core/mechanical.py +++ b/src/ansys/aedt/core/mechanical.py @@ -29,7 +29,7 @@ from ansys.aedt.core.application.analysis_3d import FieldAnalysis3D from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import pyaedt_function_handler -from ansys.aedt.core.modules.boundary import BoundaryObject +from ansys.aedt.core.modules.boundary.common import BoundaryObject from ansys.aedt.core.modules.setup_templates import SetupKeys @@ -500,7 +500,6 @@ def assign_frictionless_support(self, assignment, name=""): >>> oModule.AssignFrictionlessSupport """ - if not (self.solution_type == "Structural" or "Modal" in self.solution_type): self.logger.error("This method works only in Mechanical Structural analysis.") return False diff --git a/src/ansys/aedt/core/modeler/advanced_cad/actors.py b/src/ansys/aedt/core/modeler/advanced_cad/actors.py index f1a9dfabf13..fe150bdce75 100644 --- a/src/ansys/aedt/core/modeler/advanced_cad/actors.py +++ b/src/ansys/aedt/core/modeler/advanced_cad/actors.py @@ -219,6 +219,7 @@ def insert(self, app, motion=True): app : ansys.aedt.core.Hfss motion : bool Whether the bird is in motion. The default is ``True``. + Returns ------- bool diff --git a/src/ansys/aedt/core/modeler/advanced_cad/oms.py b/src/ansys/aedt/core/modeler/advanced_cad/oms.py index 045bc42630d..fe3bf32d690 100644 --- a/src/ansys/aedt/core/modeler/advanced_cad/oms.py +++ b/src/ansys/aedt/core/modeler/advanced_cad/oms.py @@ -128,7 +128,12 @@ def generate_buildings(self, center_lat_lon, terrain_mesh, max_radius=500): dict Info of generated stl file. """ - gdf = ox.geometries.geometries_from_point(center_lat_lon, tags={"building": True}, dist=max_radius) + # TODO: Remove compatibility with <2.0 when releasing pyaedt v1.0 ? + try: + gdf = ox.geometries.geometries_from_point(center_lat_lon, tags={"building": True}, dist=max_radius) + # NOTE: Handle breaking changes introduced in osmn>=v2.0 + except AttributeError: + gdf = ox.features.features_from_point(center_lat_lon, tags={"building": True}, dist=max_radius) utm_center = utm.from_latlon(center_lat_lon[0], center_lat_lon[1]) center_offset_x = utm_center[0] center_offset_y = utm_center[1] @@ -137,7 +142,12 @@ def generate_buildings(self, center_lat_lon, terrain_mesh, max_radius=500): logger.info("No Buildings Exists in Selected Geometry") return {"file_name": None, "mesh": None} else: - gdf_proj = ox.project_gdf(gdf) + # TODO: Remove compatibility with <2.0 when releasing pyaedt v1.0 ? + try: + gdf_proj = ox.project_gdf(gdf) + # NOTE: Handle breaking changes introduced in osmn>=v2.0 + except AttributeError: + gdf_proj = ox.projection.project_gdf(gdf) geo = gdf_proj["geometry"] try: @@ -277,9 +287,14 @@ def create_roads(self, center_lat_lon, terrain_mesh, max_radius=1000, z_offset=0 dict Info of generated stl file. """ - graph = ox.graph_from_point( - center_lat_lon, dist=max_radius, simplify=False, network_type="all", clean_periphery=True - ) + # TODO: Remove compatibility with <2.0 when releasing pyaedt v1.0 ? + try: + graph = ox.graph_from_point( + center_lat_lon, dist=max_radius, simplify=False, network_type="all", clean_periphery=True + ) + # NOTE: Handle breaking changes introduced in osmn>=v2.0 + except TypeError: + graph = ox.graph_from_point(center_lat_lon, dist=max_radius, simplify=False, network_type="all") g_projected = ox.project_graph(graph) diff --git a/src/ansys/aedt/core/modeler/advanced_cad/stackup_3d.py b/src/ansys/aedt/core/modeler/advanced_cad/stackup_3d.py index fa6dc15ec9e..cfee2391c4b 100644 --- a/src/ansys/aedt/core/modeler/advanced_cad/stackup_3d.py +++ b/src/ansys/aedt/core/modeler/advanced_cad/stackup_3d.py @@ -616,7 +616,6 @@ def duplicate_parametrize_material( :class:`ansys.aedt.core.modules.material.Material` Material object. """ - if isinstance(material_name, Material): # Make sure material_name is of type str. material_name = material_name.name if isinstance(cloned_material_name, Material): # Make sure cloned_material_name is of type str. @@ -671,7 +670,6 @@ def add_patch( Examples -------- - >>> from ansys.aedt.core import Hfss >>> from ansys.aedt.core.modeler.advanced_cad.stackup_3d import Stackup3D >>> hfss = Hfss() @@ -681,7 +679,6 @@ def add_patch( >>> top = my_stackup.add_signal_layer("top") >>> my_patch = top.add_patch(frequency=None, patch_width=51, patch_name="MLPatch") >>> my_stackup.resize_around_element(my_patch) - """ if not patch_name: patch_name = generate_unique_name(f"{self._name}_patch", n=3) diff --git a/src/ansys/aedt/core/modeler/cad/elements_3d.py b/src/ansys/aedt/core/modeler/cad/elements_3d.py index ef457b915c5..85030f3110a 100644 --- a/src/ansys/aedt/core/modeler/cad/elements_3d.py +++ b/src/ansys/aedt/core/modeler/cad/elements_3d.py @@ -1418,7 +1418,10 @@ def __init__(self, node, child_object, first_level=False, get_child_obj_arg=None if i == "OperandPart_" + saved_root_name or i == "OperandPart_" + saved_root_name.split("_")[0]: continue elif not i.startswith("OperandPart_"): - self.children[i] = BinaryTreeNode(i, self.child_object.GetChildObject(i), root_name=saved_root_name) + try: + self.children[i] = BinaryTreeNode(i, self.child_object.GetChildObject(i), root_name=saved_root_name) + except Exception: # nosec + pass else: names = self.child_object.GetChildObject(i).GetChildNames() for name in names: @@ -1433,36 +1436,37 @@ def __init__(self, node, child_object, first_level=False, get_child_obj_arg=None del self.children[name] @property - def props(self): + def properties(self): """Properties data. Returns ------- :class:``ansys.aedt.coree.modeler.cad.elements_3d.HistoryProps`` """ - if self._props is None: - self._props = {} - if settings.aedt_version >= "2024.2": - try: - props = self._get_data_model() - for p in self.child_object.GetPropNames(): - if p in props: - self._props[p] = props[p] - else: - self._props[p] = None - except Exception: - for p in self.child_object.GetPropNames(): - try: - self._props[p] = self.child_object.GetPropValue(p) - except Exception: - self._props[p] = None - else: + self._props = {} + if settings.aedt_version >= "2024.2": + try: + from ansys.aedt.core.application import _get_data_model + + props = _get_data_model(self.child_object) + for p in self.child_object.GetPropNames(): + if p in props: + self._props[p] = props[p] + else: + self._props[p] = None + except Exception: for p in self.child_object.GetPropNames(): try: self._props[p] = self.child_object.GetPropValue(p) except Exception: self._props[p] = None - self._props = HistoryProps(self, self._props) + else: + for p in self.child_object.GetPropNames(): + try: + self._props[p] = self.child_object.GetPropValue(p) + except Exception: + self._props[p] = None + self._props = HistoryProps(self, self._props) return self._props @property @@ -1473,22 +1477,7 @@ def command(self): ------- str """ - return self.props.get("Command", "") - - def _get_data_model(self): - import ast - - input_str = self.child_object.GetDataModel(-1, 1, 1).replace("false", "False").replace("true", "True") - props_list = ast.literal_eval(input_str) - props = {} - for prop in props_list["properties"]: - if "value" in prop: - props[prop["name"]] = prop["value"] - elif "values" in prop: - props[prop["name"]] = prop["values"] - else: - props[prop["name"]] = None - return props + return self.properties.get("Command", "") def update_property(self, prop_name, prop_value): """Update the property of the binary tree node. @@ -1516,7 +1505,7 @@ def _jsonalize_tree(self, binary_tree_node): childrend_dict = {} for _, node in binary_tree_node.children.items(): childrend_dict.update(self._jsonalize_tree(node)) - return {binary_tree_node.node: {"Props": binary_tree_node.props, "Children": childrend_dict}} + return {binary_tree_node.node: {"Props": binary_tree_node.properties, "Children": childrend_dict}} @pyaedt_function_handler def jsonalize_tree(self): @@ -1531,7 +1520,7 @@ def jsonalize_tree(self): @pyaedt_function_handler def _suppress(self, node, app, suppress): - if not node.command.startswith("Duplicate") and "Suppress Command" in node.props: + if not node.command.startswith("Duplicate") and "Suppress Command" in node.properties: app.oeditor.ChangeProperty( [ "NAME:AllTabs", diff --git a/src/ansys/aedt/core/modeler/cad/object_3d.py b/src/ansys/aedt/core/modeler/cad/object_3d.py index 0872efde3ca..fdd3c6f98c8 100644 --- a/src/ansys/aedt/core/modeler/cad/object_3d.py +++ b/src/ansys/aedt/core/modeler/cad/object_3d.py @@ -358,11 +358,12 @@ def get_touching_faces(self, assignment): ---------- assignment : str, :class:`Object3d` Object to check. + Returns ------- list - list of objects and faces touching.""" - + list of objects and faces touching. + """ _names = [] if isinstance(assignment, Object3d): assignment = assignment.name diff --git a/src/ansys/aedt/core/modeler/cad/polylines.py b/src/ansys/aedt/core/modeler/cad/polylines.py index 914c1269376..82283513241 100644 --- a/src/ansys/aedt/core/modeler/cad/polylines.py +++ b/src/ansys/aedt/core/modeler/cad/polylines.py @@ -420,47 +420,47 @@ def _convert_points(p_in, dest_unit): if h_segments: for i, c in enumerate(h_segments.values()): # evaluate the number of points in the segment - attrb = list(c.props.keys()) + attrb = list(c.properties.keys()) n_points = 0 for j in range(1, len(attrb) + 1): if "Point" + str(j) in attrb: n_points += 1 # get the segment type - s_type = c.props["Segment Type"] + s_type = c.properties["Segment Type"] if i == 0: # append the first point only for the first segment if s_type != "Center Point Arc": p = [ - c.props["Point1/X"], - c.props["Point1/Y"], - c.props["Point1/Z"], + c.properties["Point1/X"], + c.properties["Point1/Y"], + c.properties["Point1/Z"], ] points.append(_convert_points(p, self._primitives.model_units)) else: p = [ - c.props["Start Point/X"], - c.props["Start Point/Y"], - c.props["Start Point/Z"], + c.properties["Start Point/X"], + c.properties["Start Point/Y"], + c.properties["Start Point/Z"], ] points.append(_convert_points(p, self._primitives.model_units)) if s_type == "Line": segments.append(PolylineSegment("Line")) p = [ - c.props["Point2/X"], - c.props["Point2/Y"], - c.props["Point2/Z"], + c.properties["Point2/X"], + c.properties["Point2/Y"], + c.properties["Point2/Z"], ] points.append(_convert_points(p, self._primitives.model_units)) elif s_type == "3 Point Arc": segments.append(PolylineSegment("Arc")) p2 = [ - c.props["Point2/X"], - c.props["Point2/Y"], - c.props["Point2/Z"], + c.properties["Point2/X"], + c.properties["Point2/Y"], + c.properties["Point2/Z"], ] p3 = [ - c.props["Point3/X"], - c.props["Point3/Y"], - c.props["Point3/Z"], + c.properties["Point3/X"], + c.properties["Point3/Y"], + c.properties["Point3/Z"], ] points.append(_convert_points(p2, self._primitives.model_units)) @@ -470,27 +470,27 @@ def _convert_points(p_in, dest_unit): for p in range(2, n_points + 1): point_attr = "Point" + str(p) p2 = [ - c.props[f"{point_attr}/X"], - c.props[f"{point_attr}/Y"], - c.props[f"{point_attr}/Z"], + c.properties[f"{point_attr}/X"], + c.properties[f"{point_attr}/Y"], + c.properties[f"{point_attr}/Z"], ] points.append(_convert_points(p2, self._primitives.model_units)) elif s_type == "Center Point Arc": p2 = [ - c.props["Start Point/X"], - c.props["Start Point/Y"], - c.props["Start Point/Z"], + c.properties["Start Point/X"], + c.properties["Start Point/Y"], + c.properties["Start Point/Z"], ] p3 = [ - c.props["Center Point/X"], - c.props["Center Point/Y"], - c.props["Center Point/Z"], + c.properties["Center Point/X"], + c.properties["Center Point/Y"], + c.properties["Center Point/Z"], ] start = _convert_points(p2, self._primitives.model_units) center = _convert_points(p3, self._primitives.model_units) - plane = c.props["Plane"] - angle = c.props["Angle"] + plane = c.properties["Plane"] + angle = c.properties["Angle"] arc_seg = PolylineSegment("AngularArc", arc_angle=angle, arc_center=center, arc_plane=plane) segments.append(arc_seg) self._evaluate_arc_angle_extra_points(arc_seg, start) @@ -498,8 +498,8 @@ def _convert_points(p_in, dest_unit): # perform validation if history: - nn_segments = int(history.props["Number of curves"]) - nn_points = int(history.props["Number of points"]) + nn_segments = int(history.properties["Number of curves"]) + nn_points = int(history.properties["Number of points"]) else: nn_segments = None nn_points = None diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index 3d38d7364f3..16f4e0af589 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -90,7 +90,7 @@ def _parse_objs(self): if self.__obj_type == "o": self.__parent.logger.info("Parsing design objects. This operation can take time") self.__parent.logger.reset_timer() - self.__parent._refresh_all_ids_from_aedt_file() + self.__parent._refresh_all_ids_wrapper() self.__parent.add_new_solids() self.__parent.cleanup_solids() self.__parent.logger.info_timer("3D Modeler objects parsed.") @@ -261,6 +261,7 @@ def __init__(self, app, is3d=True): self._unclassified = [] self._all_object_names = [] self._model_units = None + self.rescale_model = False self._object_names_to_ids = {} self.objects = Objects(self, "o") self.user_defined_components = Objects(self, "u") @@ -403,9 +404,8 @@ def oeditor(self): References ---------- - - >>> oEditor = oDesign.SetActiveEditor("3D Modeler")""" - + >>> oEditor = oDesign.SetActiveEditor("3D Modeler") + """ return self._app.oeditor @property @@ -423,11 +423,21 @@ def materials(self): def model_units(self): """Model units as a string. For example, ``"mm"``. + This property allows you to get or set the model units. When setting the model units, + you can specify whether to rescale the model by adjusting the ``rescale_model`` attribute. + References ---------- - >>> oEditor.GetModelUnits >>> oEditor.SetModelUnits + + Examples + -------- + >>> from ansys.aedt.core import hfss + >>> hfss = Hfss() + >>> hfss.modeler.model_units = "cm" + >>> hfss.modeler.rescale_model = True + >>> hfss.modeler.model_units = "mm" """ if not self._model_units: self._model_units = self.oeditor.GetModelUnits() @@ -436,7 +446,7 @@ def model_units(self): @model_units.setter def model_units(self, units): assert units in AEDT_UNITS["Length"], f"Invalid units string {units}." - self.oeditor.SetModelUnits(["NAME:Units Parameter", "Units:=", units, "Rescale:=", False]) + self.oeditor.SetModelUnits(["NAME:Units Parameter", "Units:=", units, "Rescale:=", self.rescale_model]) self._model_units = units @property @@ -445,7 +455,6 @@ def selections(self): References ---------- - >>> oEditor.GetSelections """ return self.oeditor.GetSelections() @@ -456,7 +465,6 @@ def obounding_box(self): References ---------- - >>> oEditor.GetModelBoundingBox """ return self.oeditor.GetModelBoundingBox() @@ -472,7 +480,6 @@ def dimension(self): References ---------- - >>> oDesign.Is2D """ try: @@ -492,7 +499,6 @@ def design_type(self): References ---------- - >>> oDesign.GetDesignType """ return self._app.design_type @@ -503,9 +509,10 @@ def geometry_mode(self): References ---------- - >>> oDesign.GetGeometryMode""" - return self._odesign.GetGeometryMode() + if "GetGeometryMode" in dir(self._odesign): + return self._odesign.GetGeometryMode() + return @property def solid_bodies(self): @@ -521,7 +528,6 @@ def solid_bodies(self): References ---------- - >>> oEditor.GetObjectsInGroup """ if self.dimension == "3D": @@ -816,7 +822,7 @@ def refresh(self): self.user_defined_components = Objects(self, "u") self._refresh_object_types() if not settings.objects_lazy_load: - self._refresh_all_ids_from_aedt_file() + self._refresh_all_ids_wrapper() self.refresh_all_ids() @pyaedt_function_handler() @@ -847,8 +853,46 @@ def _create_point(self, name): return point + @pyaedt_function_handler() + def _refresh_all_ids_wrapper(self): + if settings.aedt_version >= "2025.1": + return self._refresh_all_ids_from_data_model() + else: + return self._refresh_all_ids_from_aedt_file() + + @pyaedt_function_handler() + def _refresh_all_ids_from_data_model(self): + self._app.logger.info("Refreshing objects from Data Model") + from ansys.aedt.core.application import _get_data_model + + dm = _get_data_model(self.oeditor, 2) + + for attribs in dm.get("children", []): + if attribs["type"] == "Part": + pid = 0 + is_polyline = False + try: + if attribs["children"][0]["Command"] == "CreatePolyline": + is_polyline = True + except Exception: + is_polyline = False + + o = self._create_object(name=attribs["Name"], pid=pid, use_cached=True, is_polyline=is_polyline) + o._part_coordinate_system = attribs["Orientation"] + o._model = attribs["Model"] + o._wireframe = attribs["Display Wireframe"] + o._m_groupName = attribs["Model"] + o._color = (attribs["Color/Red"], attribs["Color/Green"], attribs["Color/Blue"]) + o._material_name = attribs.get("Material", None) + o._surface_material = attribs.get("Surface Material", None) + o._solve_inside = attribs.get("Solve Inside", False) + o._is_updated = True + # pid+=1 + return len(self.objects) + @pyaedt_function_handler() def _refresh_all_ids_from_aedt_file(self): + self._app.logger.info("Refreshing objects from AEDT file") dp = copy.deepcopy(self._app.design_properties) if not dp or "ModelSetup" not in dp: @@ -960,7 +1004,7 @@ def cleanup_solids(self): new_object_id_dict = {} all_objects = self.object_names - all_unclassified = self.unclassified_names + all_unclassified = self._unclassified all_objs = all_objects + all_unclassified if sorted(all_objs) != sorted(list(self._object_names_to_ids.keys())): for old_id, obj in self.objects.items(): @@ -1412,6 +1456,7 @@ def cover_faces(self, assignment): ---------- assignment : str, int Sheet object to cover. + Returns ------- bool @@ -1419,7 +1464,6 @@ def cover_faces(self, assignment): References ---------- - >>> oEditor.CoverLines """ obj_to_cover = self.convert_to_selections(assignment, False) @@ -1586,9 +1630,7 @@ def create_face_coordinate_system( Returns ------- :class:`ansys.aedt.core.modeler.Modeler.FaceCoordinateSystem` - """ - if name: cs_names = [i.name for i in self.coordinate_systems] if name in cs_names: # pragma: no cover diff --git a/src/ansys/aedt/core/modeler/cad/primitives_3d.py b/src/ansys/aedt/core/modeler/cad/primitives_3d.py index f96c19ecada..ca4d9cd8d3f 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives_3d.py +++ b/src/ansys/aedt/core/modeler/cad/primitives_3d.py @@ -36,7 +36,6 @@ import os from ansys.aedt.core import Edb -from ansys.aedt.core import Icepak from ansys.aedt.core.generic import load_aedt_file from ansys.aedt.core.generic.desktop_sessions import _edb_sessions from ansys.aedt.core.generic.general_methods import generate_unique_name @@ -1467,6 +1466,7 @@ def _create_reference_cs_from_3dcomp(self, assignment, password): @staticmethod def __create_temp_project(app): """Create temporary project with a duplicated design.""" + from ansys.aedt.core import Icepak temp_proj_name = generate_unique_project_name() ipkapp_temp = Icepak(project=os.path.join(app.toolkit_directory, temp_proj_name)) ipkapp_temp.delete_design(ipkapp_temp.design_name) diff --git a/src/ansys/aedt/core/modeler/calculators.py b/src/ansys/aedt/core/modeler/calculators.py index 03730723bf0..94fe8241ba9 100644 --- a/src/ansys/aedt/core/modeler/calculators.py +++ b/src/ansys/aedt/core/modeler/calculators.py @@ -349,7 +349,6 @@ def find_waveguide(self, freq, units="GHz"): # pragma: no cover str Waveguide name. """ - freq = constants.unit_converter(freq, "Frequency", units, "GHz") op_freq = freq * 0.8 diff --git a/src/ansys/aedt/core/modeler/circuits/primitives_circuit.py b/src/ansys/aedt/core/modeler/circuits/primitives_circuit.py index 065d001a95f..df09847340c 100644 --- a/src/ansys/aedt/core/modeler/circuits/primitives_circuit.py +++ b/src/ansys/aedt/core/modeler/circuits/primitives_circuit.py @@ -777,7 +777,7 @@ def create_component( name : str, optional Name of the instance. The default is ``None.`` component_library : str, optional - Name of the component library. The default is ``"Resistors"``. + Name of the component library. The default is ``""``. component_name : str, optional Name of component in the library. The default is ``"RES"``. location : list of float, optional @@ -988,13 +988,13 @@ def create_symbol(self, name, pins): return True @pyaedt_function_handler() - def enable_use_instance_name(self, component_library="Resistors", component_name="RES_"): + def enable_use_instance_name(self, component_library="", component_name="RES_"): """Enable the use of the instance name. Parameters ---------- component_library : str, optional - Name of the component library. The default is ``"Resistors"``. + Name of the component library. The default is ``""``. component_name : str, optional Name of the component. The default is ``"RES_"``. diff --git a/src/ansys/aedt/core/modeler/circuits/primitives_emit.py b/src/ansys/aedt/core/modeler/circuits/primitives_emit.py index 7c4ee9507f9..cea656139d5 100644 --- a/src/ansys/aedt/core/modeler/circuits/primitives_emit.py +++ b/src/ansys/aedt/core/modeler/circuits/primitives_emit.py @@ -396,6 +396,7 @@ def move_and_connect_to(self, component): @pyaedt_function_handler() def port_names(self): """Get the names of the component's ports. + Returns ------- List @@ -403,7 +404,6 @@ def port_names(self): References ---------- - >>> oEditor.GetComponentPorts """ return self.oeditor.GetComponentPorts(self.name) diff --git a/src/ansys/aedt/core/modeler/circuits/primitives_nexxim.py b/src/ansys/aedt/core/modeler/circuits/primitives_nexxim.py index 4179ed83b9b..a176e257dcb 100644 --- a/src/ansys/aedt/core/modeler/circuits/primitives_nexxim.py +++ b/src/ansys/aedt/core/modeler/circuits/primitives_nexxim.py @@ -39,7 +39,7 @@ from ansys.aedt.core.modeler.circuits.object_3d_circuit import CircuitComponent from ansys.aedt.core.modeler.circuits.primitives_circuit import CircuitComponents from ansys.aedt.core.modeler.circuits.primitives_circuit import ComponentCatalog -from ansys.aedt.core.modules.boundary import Excitations +from ansys.aedt.core.modules.boundary.circuit_boundary import Excitations class NexximComponents(CircuitComponents): @@ -49,9 +49,9 @@ class NexximComponents(CircuitComponents): ---------- modeler : :class:`ansys.aedt.core.modeler.schematic.ModelerNexxim` Inherited parent object. + Examples -------- - >>> from ansys.aedt.core import Circuit >>> aedtapp = Circuit() >>> prim = aedtapp.modeler.schematic @@ -70,6 +70,7 @@ def tab_name(self): @pyaedt_function_handler() def __getitem__(self, partname): """Get the object ID if the part name is an integer or the object name if it is a string. + Parameters ---------- partname : int or str diff --git a/src/ansys/aedt/core/modeler/pcb/object_3d_layout.py b/src/ansys/aedt/core/modeler/pcb/object_3d_layout.py index 12c85725348..5fbfb894923 100644 --- a/src/ansys/aedt/core/modeler/pcb/object_3d_layout.py +++ b/src/ansys/aedt/core/modeler/pcb/object_3d_layout.py @@ -265,6 +265,7 @@ def create_clearance_on_component(self, extra_soldermask_clearance=5e-3): ---------- extra_soldermask_clearance : float, optional Extra Soldermask value in model units to be applied on component bounding box. + Returns ------- bool diff --git a/src/ansys/aedt/core/modules/boundary.py b/src/ansys/aedt/core/modules/boundary.py deleted file mode 100644 index fa472e800b7..00000000000 --- a/src/ansys/aedt/core/modules/boundary.py +++ /dev/null @@ -1,6006 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -This module contains these classes: ``BoundaryCommon`` and ``BoundaryObject``. -""" - -from abc import abstractmethod -import copy -import re - -from ansys.aedt.core.application.variables import decompose_variable_value -from ansys.aedt.core.generic.constants import CATEGORIESQ3D -from ansys.aedt.core.generic.data_handlers import _dict2arg -from ansys.aedt.core.generic.data_handlers import random_string -from ansys.aedt.core.generic.general_methods import GrpcApiError -from ansys.aedt.core.generic.general_methods import PropsManager -from ansys.aedt.core.generic.general_methods import _dim_arg -from ansys.aedt.core.generic.general_methods import filter_tuple -from ansys.aedt.core.generic.general_methods import generate_unique_name -from ansys.aedt.core.generic.general_methods import pyaedt_function_handler -from ansys.aedt.core.modeler.cad.elements_3d import EdgePrimitive -from ansys.aedt.core.modeler.cad.elements_3d import FacePrimitive -from ansys.aedt.core.modeler.cad.elements_3d import VertexPrimitive -from ansys.aedt.core.modules.circuit_templates import SourceKeys - - -class BoundaryProps(dict): - """AEDT Boundary Component Internal Parameters.""" - - def __setitem__(self, key, value): - dict.__setitem__(self, key, value) - if self._pyaedt_boundary.auto_update: - if key in ["Edges", "Faces", "Objects"]: - res = self._pyaedt_boundary.update_assignment() - else: - res = self._pyaedt_boundary.update() - if not res: - self._pyaedt_boundary._app.logger.warning("Update of %s Failed. Check needed arguments", key) - - def __init__(self, boundary, props): - dict.__init__(self) - if props: - for key, value in props.items(): - if isinstance(value, dict): - dict.__setitem__(self, key, BoundaryProps(boundary, value)) - elif isinstance(value, list): - list_els = [] - for el in value: - if isinstance(el, dict): - list_els.append(BoundaryProps(boundary, el)) - else: - list_els.append(el) - dict.__setitem__(self, key, list_els) - else: - dict.__setitem__(self, key, value) - self._pyaedt_boundary = boundary - - def _setitem_without_update(self, key, value): - dict.__setitem__(self, key, value) - - -class BoundaryCommon(PropsManager): - """ """ - - @pyaedt_function_handler() - def _get_args(self, props=None): - """Retrieve boundary properties. - - Parameters - ---------- - props : dict, optional - The default is ``None``. - - Returns - ------- - dict - Dictionary of boundary properties. - - """ - if not props: - props = self.props - arg = ["NAME:" + self.name] - _dict2arg(props, arg) - return arg - - @pyaedt_function_handler() - def delete(self): - """Delete the boundary. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - if self.type == "Matrix" or self.type == "Force" or self.type == "Torque": - self._app.o_maxwell_parameters.DeleteParameters([self.name]) - else: - self._app.oboundary.DeleteBoundaries([self.name]) - if self.name in self._app.excitation_objects.keys(): - self._app.excitation_objects.pop(self.name) - self._app.boundaries - return True - - def _get_boundary_data(self, ds): - try: - if "MaxwellParameterSetup" in self._app.design_properties: - param = "MaxwellParameters" - setup = "MaxwellParameterSetup" - if isinstance(self._app.design_properties[setup][param][ds], dict): - return [ - self._app.design_properties["MaxwellParameterSetup"]["MaxwellParameters"][ds], - self._app.design_properties["MaxwellParameterSetup"]["MaxwellParameters"][ds][ - "MaxwellParameterType" - ], - ] - except Exception: - self._app.logger.debug( - "An error occurred while getting boundary data for MaxwellParameterSetup." - ) # pragma: no cover - try: - if ( - "ModelSetup" in self._app.design_properties - and "MotionSetupList" in self._app.design_properties["ModelSetup"] - ): - motion_list = "MotionSetupList" - setup = "ModelSetup" - # check moving part - if isinstance(self._app.design_properties[setup][motion_list][ds], dict): - return [ - self._app.design_properties["ModelSetup"]["MotionSetupList"][ds], - self._app.design_properties["ModelSetup"]["MotionSetupList"][ds]["MotionType"], - ] - except Exception: - self._app.logger.debug("An error occurred while getting boundary data for ModelSetup.") # pragma: no cover - try: - if ds in self._app.design_properties["BoundarySetup"]["Boundaries"]: - if ( - self._app.design_properties["BoundarySetup"]["Boundaries"][ds]["BoundType"] == "Network" - and self._app.design_type == "Icepak" - ): - return [self._app.design_properties["BoundarySetup"]["Boundaries"][ds], ""] - else: - return [ - self._app.design_properties["BoundarySetup"]["Boundaries"][ds], - self._app.design_properties["BoundarySetup"]["Boundaries"][ds]["BoundType"], - ] - except Exception: - self._app.logger.debug( - "An error occurred while getting boundary data for BoundarySetup." - ) # pragma: no cover - return [] - - -class NativeComponentObject(BoundaryCommon, object): - """Manages Native Component data and execution. - - Parameters - ---------- - app : object - An AEDT application from ``ansys.aedt.core.application``. - component_type : str - Type of the component. - component_name : str - Name of the component. - props : dict - Properties of the boundary. - - Examples - -------- - in this example the par_beam returned object is a ``ansys.aedt.core.modules.boundary.NativeComponentObject`` - >>> from ansys.aedt.core import Hfss - >>> hfss = Hfss(solution_type="SBR+") - >>> ffd_file ="path/to/ffdfile.ffd" - >>> par_beam = hfss.create_sbr_file_based_antenna(ffd_file) - >>> par_beam.native_properties["Size"] = "0.1mm" - >>> par_beam.update() - >>> par_beam.delete() - """ - - def __init__(self, app, component_type, component_name, props): - self.auto_update = False - self._app = app - self._name = component_name - - self.props = BoundaryProps( - self, - { - "TargetCS": "Global", - "SubmodelDefinitionName": self.name, - "ComponentPriorityLists": {}, - "NextUniqueID": 0, - "MoveBackwards": False, - "DatasetType": "ComponentDatasetType", - "DatasetDefinitions": {}, - "BasicComponentInfo": { - "ComponentName": self.name, - "Company": "", - "Company URL": "", - "Model Number": "", - "Help URL": "", - "Version": "1.0", - "Notes": "", - "IconType": "", - }, - "GeometryDefinitionParameters": {"VariableOrders": {}}, - "DesignDefinitionParameters": {"VariableOrders": {}}, - "MaterialDefinitionParameters": {"VariableOrders": {}}, - "DefReferenceCSID": 1, - "MapInstanceParameters": "DesignVariable", - "UniqueDefinitionIdentifier": "89d26167-fb77-480e-a7ab-" - + random_string(12, char_set="abcdef0123456789"), - "OriginFilePath": "", - "IsLocal": False, - "ChecksumString": "", - "ChecksumHistory": [], - "VersionHistory": [], - "NativeComponentDefinitionProvider": {"Type": component_type}, - "InstanceParameters": {"GeometryParameters": "", "MaterialParameters": "", "DesignParameters": ""}, - }, - ) - if props: - self._update_props(self.props, props) - self.native_properties = self.props["NativeComponentDefinitionProvider"] - self.auto_update = True - - @property - def name(self): - """Name of the object. - - Returns - ------- - str - Name of the object. - - """ - return self._name - - @name.setter - def name(self, component_name): - if component_name != self._name: - if component_name not in self._app.native_component_names: - self.object_properties.props["Name"] = component_name - self._app.native_components.update({component_name: self}) - del self._app.native_components[self._name] - del self._app.modeler.user_defined_components[self._name] - self._name = component_name - else: # pragma: no cover - self._app._logger.warning("Name %s already assigned in the design", component_name) - - @property - def definition_name(self): - """Definition name of the native component. - - Returns - ------- - str - Name of the native component. - - """ - definition_name = None - if self.props and "SubmodelDefinitionName" in self.props: - definition_name = self.props["SubmodelDefinitionName"] - return definition_name - - @property - def targetcs(self): - """Native Component Coordinate System. - - Returns - ------- - str - Native Component Coordinate System. - """ - if "TargetCS" in list(self.props.keys()): - return self.props["TargetCS"] - else: - return "Global" - - @targetcs.setter - def targetcs(self, cs): - self.props["TargetCS"] = cs - - @property - def object_properties(self): - """Object-oriented properties. - - Returns - ------- - class:`ansys.aedt.core.modeler.cad.elements_3d.BinaryTreeNode` - - """ - - from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode - - child_object = self._app.get_oo_object(self._app.oeditor, self.name) - if child_object: - return BinaryTreeNode(self.name, child_object, False) - return False - - def _update_props(self, d, u): - for k, v in u.items(): - if isinstance(v, dict): - if k not in d: - d[k] = {} - d[k] = self._update_props(d[k], v) - else: - d[k] = v - return d - - @pyaedt_function_handler() - def _get_args(self, props=None): - if props is None: - props = self.props - arg = ["NAME:InsertNativeComponentData"] - _dict2arg(props, arg) - return arg - - @pyaedt_function_handler() - def create(self): - """Create a Native Component in AEDT. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - try: - names = [i for i in self._app.excitations] - except GrpcApiError: # pragma: no cover - names = [] - self._name = self._app.modeler.oeditor.InsertNativeComponent(self._get_args()) - try: - a = [i for i in self._app.excitations if i not in names] - self.excitation_name = a[0].split(":")[0] - except (GrpcApiError, IndexError): - self.excitation_name = self.name - return True - - @pyaedt_function_handler() - def update(self): - """Update the Native Component in AEDT. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - - self.update_props = {} - self.update_props["DefinitionName"] = self.props["SubmodelDefinitionName"] - self.update_props["GeometryDefinitionParameters"] = self.props["GeometryDefinitionParameters"] - self.update_props["DesignDefinitionParameters"] = self.props["DesignDefinitionParameters"] - self.update_props["MaterialDefinitionParameters"] = self.props["MaterialDefinitionParameters"] - self.update_props["NextUniqueID"] = self.props["NextUniqueID"] - self.update_props["MoveBackwards"] = self.props["MoveBackwards"] - self.update_props["DatasetType"] = self.props["DatasetType"] - self.update_props["DatasetDefinitions"] = self.props["DatasetDefinitions"] - self.update_props["NativeComponentDefinitionProvider"] = self.props["NativeComponentDefinitionProvider"] - self.update_props["ComponentName"] = self.props["BasicComponentInfo"]["ComponentName"] - self.update_props["Company"] = self.props["BasicComponentInfo"]["Company"] - self.update_props["Model Number"] = self.props["BasicComponentInfo"]["Model Number"] - self.update_props["Help URL"] = self.props["BasicComponentInfo"]["Help URL"] - self.update_props["Version"] = self.props["BasicComponentInfo"]["Version"] - self.update_props["Notes"] = self.props["BasicComponentInfo"]["Notes"] - self.update_props["IconType"] = self.props["BasicComponentInfo"]["IconType"] - self._app.modeler.oeditor.EditNativeComponentDefinition(self._get_args(self.update_props)) - - return True - - @pyaedt_function_handler() - def delete(self): - """Delete the Native Component in AEDT. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - self._app.modeler.oeditor.Delete(["NAME:Selections", "Selections:=", self.name]) - for el in self._app._native_components: - if el.name == self.name: - self._app._native_components.remove(el) - del self._app.modeler.user_defined_components[self.name] - self._app.modeler.cleanup_objects() - return True - - -def disable_auto_update(func): - """Decorator used to disable automatic update.""" - - def wrapper(self, *args, **kwargs): - """Inner wrapper function.""" - obj = self - if not hasattr(self, "auto_update"): - obj = self.pcb - auto_update = obj.auto_update - obj.auto_update = False - out = func(self, *args, **kwargs) - if auto_update: - obj.update() - obj.auto_update = auto_update - return out - - return wrapper - - -class PCBSettingsPackageParts(object): - """Handle package part settings of the PCB component. - - Parameters - ---------- - pcb_obj : :class:`pyaedt.modules.Boundary.NativeComponentPCB` - Inherited pcb object. - app : :class:`pyaedt.Icepak` - Inherited application object. - """ - - def __init__(self, pcb_obj, app): - self._app = app - self.pcb = pcb_obj - self._solderbumps_map = {"Lumped": "SbLumped", "Cylinders": "SbCylinder", "Boxes": "SbBlock"} - - def __eq__(self, other): - if isinstance(other, str): - return other == "Package" - elif isinstance(other, self.__class__): - return self.__dict__ == other.__dict__ - else: - return False - - def __ne__(self, other): - return not self.__eq__(other) - - @pyaedt_function_handler() - @disable_auto_update - def set_solderballs_modeling(self, modeling=None): - """Set how to model solderballs. - - Parameters - ---------- - modeling : str, optional - Method for modeling solderballs located below the stackup. The default is - ``None``, in which case they are not modeled. Options for modeling are - ``"Boxes"``, ``"Cylinders"``, and ``"Lumped"``. - - Returns - ------- - bool - ``True`` if successful, ``False`` otherwise. - """ - update_properties = { - "CreateBottomSolderballs": modeling is not None, - "BottomSolderballsModelType": self._solderbumps_map[modeling], - } - - self.pcb.props["NativeComponentDefinitionProvider"].update(update_properties) - return True - - @pyaedt_function_handler() - @disable_auto_update - def set_connectors_modeling( - self, - modeling=None, - solderbumps_modeling="Boxes", - bondwire_material="Au-Typical", - bondwire_diameter="0.05mm", - ): - """Set how to model connectors. - - Parameters - ---------- - modeling : str, optional - Method for modeling connectors located above the stackup. The default is - ``None``, in which case they are not modeled. Options for modeling are - ``"Bondwire"`` and ``"Solderbump"``. - solderbumps_modeling : str, optional - Method for modeling solderbumps if ``modeling="Solderbump"``. - The default is ```"Boxes"``. Options are ``"Boxes"``, ``"Cylinders"``, - and ``"Lumped"``. - bondwire_material : str, optional - Bondwire material if ``modeling="Bondwire"``. The default is - ``"Au-Typical"``. - bondwire_diameter : str, optional - Bondwires diameter if ``modeling="Bondwire". - The default is ``"0.05mm"``. - - Returns - ------- - bool - ``True`` if successful, ``False`` otherwise. - """ - valid_connectors = ["Solderbump", "Bondwire"] - if modeling is not None and modeling not in valid_connectors: - self._app.logger.error( - f"{modeling} option is not supported. Use one of the following: {', '.join(valid_connectors)}" - ) - return False - if bondwire_material not in self._app.materials.mat_names_aedt: - self._app.logger.error(f"{bondwire_material} material is not present in the library.") - return False - if self._solderbumps_map.get(solderbumps_modeling, None) is None: - self._app.logger.error( - f"Solderbumps modeling option {solderbumps_modeling} is not valid. " - f"Available options are: {', '.join(list(self._solderbumps_map.keys()))}." - ) - return False - - update_properties = { - "CreateTopSolderballs": modeling is not None, - "TopConnectorType": modeling, - "TopSolderballsModelType": self._solderbumps_map[solderbumps_modeling], - "BondwireMaterial": bondwire_material, - "BondwireDiameter": bondwire_diameter, - } - - self.pcb.props["NativeComponentDefinitionProvider"].update(update_properties) - return True - - def __repr__(self): - return "Package" - - -class PCBSettingsDeviceParts(object): - """Handle device part settings of the PCB component. - - Parameters - ---------- - pcb_obj : :class:`pyaedt.modules.Boundary.NativeComponentPCB` - Inherited pcb object. - app : :class:`pyaedt.Icepak` - Inherited application object. - """ - - def __init__(self, pcb_obj, app): - self._app = app - self.pcb = pcb_obj - self._filter_map2name = {"Cap": "Capacitors", "Ind": "Inductors", "Res": "Resistors"} - - def __eq__(self, other): - if isinstance(other, str): - return other == "Device" - elif isinstance(other, self.__class__): - return self.__dict__ == other.__dict__ - else: - return False - - def __ne__(self, other): - return not self.__eq__(other) - - def __repr__(self): - return "Device" - - @property - @pyaedt_function_handler() - def simplify_parts(self): - """Get whether parts are simplified as cuboid.""" - return self.pcb.props["NativeComponentDefinitionProvider"]["ModelDeviceAsRect"] - - @simplify_parts.setter - @pyaedt_function_handler() - def simplify_parts(self, value): - """Set whether parts are simplified as cuboid. - - Parameters - ---------- - value : bool - Whether parts are simplified as cuboid. - """ - self.pcb.props["NativeComponentDefinitionProvider"]["ModelDeviceAsRect"] = value - - @property - @pyaedt_function_handler() - def surface_material(self): - """Surface material to apply to parts.""" - return self.pcb.props["NativeComponentDefinitionProvider"]["DeviceSurfaceMaterial"] - - @surface_material.setter - @pyaedt_function_handler() - def surface_material(self, value): - """Set surface material to apply to parts. - - Parameters - ---------- - value : str - Surface material to apply to parts. - """ - self.pcb.props["NativeComponentDefinitionProvider"]["DeviceSurfaceMaterial"] = value - - @property - @pyaedt_function_handler() - def footprint_filter(self): - """Minimum component footprint for filtering.""" - if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return None - if self._app.settings.aedt_version < "2024.2": - return None - return self.filters.get("FootPrint", {}).get("Value", None) - - @footprint_filter.setter - @pyaedt_function_handler() - @disable_auto_update - def footprint_filter(self, minimum_footprint): - """Set minimum component footprint for filtering. - - Parameters - ---------- - minimum_footprint : str - Value with unit of the minimum component footprint for filtering. - """ - if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return - if self._app.settings.aedt_version < "2024.2": - return - new_filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) - if "FootPrint" in new_filters: - new_filters.remove("FootPrint") - if minimum_footprint is not None: - new_filters.append("FootPrint") - self.pcb.props["NativeComponentDefinitionProvider"]["FootPrint"] = minimum_footprint - self.pcb.props["NativeComponentDefinitionProvider"]["Filters"] = new_filters - - @property - @pyaedt_function_handler() - def power_filter(self): - """Minimum component power for filtering.""" - if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return None - return self.filters.get("Power", {}).get("Value") - - @power_filter.setter - @pyaedt_function_handler() - @disable_auto_update - def power_filter(self, minimum_power): - """Set minimum component power for filtering. - - Parameters - ---------- - minimum_power : str - Value with unit of the minimum component power for filtering. - """ - if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return - new_filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) - if "Power" in new_filters: - new_filters.remove("Power") - if minimum_power is not None: - new_filters.append("Power") - self.pcb.props["NativeComponentDefinitionProvider"]["PowerVal"] = minimum_power - self.pcb.props["NativeComponentDefinitionProvider"]["Filters"] = new_filters - - @property - @pyaedt_function_handler() - def type_filters(self): - """Types of component that are filtered.""" - if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return None - return self.filters.get("Types") - - @type_filters.setter - @pyaedt_function_handler() - @disable_auto_update - def type_filters(self, object_type): - """Set types of component to filter. - - Parameters - ---------- - object_type : str or list - Types of object to filter. Options are ``"Capacitors"``, ``"Inductors"``, and ``"Resistors"``. - """ - if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return - if not isinstance(object_type, list): - object_type = [object_type] - if not all(o in self._filter_map2name.values() for o in object_type): - self._app.logger.error( - f"Accepted elements of the list are: {', '.join(list(self._filter_map2name.values()))}" - ) - else: - new_filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) - map2arg = {v: k for k, v in self._filter_map2name.items()} - for f in self._filter_map2name.keys(): - if f in new_filters: - new_filters.remove(f) - new_filters += [map2arg[o] for o in object_type] - self.pcb.props["NativeComponentDefinitionProvider"]["Filters"] = new_filters - - @property - @pyaedt_function_handler() - def height_filter(self): - """Minimum component height for filtering.""" - if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return None - return self.filters.get("Height", {}).get("Value", None) - - @height_filter.setter - @pyaedt_function_handler() - @disable_auto_update - def height_filter(self, minimum_height): - """Set minimum component height for filtering and whether to filter 2D objects. - - Parameters - ---------- - minimum_height : str - Value with unit of the minimum component power for filtering. - """ - if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return - new_filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) - if "Height" in new_filters: - new_filters.remove("Height") - if minimum_height is not None: - new_filters.append("Height") - self.pcb.props["NativeComponentDefinitionProvider"]["HeightVal"] = minimum_height - self.pcb.props["NativeComponentDefinitionProvider"]["Filters"] = new_filters - - @property - @pyaedt_function_handler() - def objects_2d_filter(self): - """Whether 2d objects are filtered.""" - if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return None - return self.filters.get("Exclude2DObjects", False) - - @objects_2d_filter.setter - @pyaedt_function_handler() - @disable_auto_update - def objects_2d_filter(self, filter): - """Set whether 2d objects are filtered. - - Parameters - ---------- - filter : bool - Whether 2d objects are filtered - """ - if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return - new_filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) - if "HeightExclude2D" in new_filters: - new_filters.remove("HeightExclude2D") - if filter: - new_filters.append("HeightExclude2D") - self.pcb.props["NativeComponentDefinitionProvider"]["Filters"] = new_filters - - @property - @pyaedt_function_handler() - def filters(self): - """All active filters.""" - if self.pcb.props["NativeComponentDefinitionProvider"].get("PartsChoice", None) != 1: - self._app.logger.error( - "Device parts modeling is not active. No filtering or override option is available." - ) - return None - out_filters = {"Type": {"Capacitors": False, "Inductors": False, "Resistors": False}} - filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) - filter_map2type = { - "Cap": "Type", - "FootPrint": "FootPrint", - "Height": "Height", - "HeightExclude2D": None, - "Ind": "Type", - "Power": "Power", - "Res": "Type", - } - filter_map2val = {"FootPrint": "FootPrint", "Height": "HeightVal", "Power": "PowerVal"} - for f in filters: - if filter_map2type[f] == "Type": - out_filters["Type"][self._filter_map2name[f]] = True - elif filter_map2type[f] is not None: - out_filters[f] = {"Value": filter_map2val[f]} - if "HeightExclude2D" in filters: - out_filters["Exclude2DObjects"] = True - return out_filters - - @property - @pyaedt_function_handler() - def overridden_components(self): - """All overridden components.""" - override_component = ( - self.pcb.props["NativeComponentDefinitionProvider"] - .get("instanceOverridesMap", {}) - .get("oneOverrideBlk", []) - ) - return {o["overrideName"]: o["overrideProps"] for o in override_component} - - @pyaedt_function_handler() - def _override_common( - self, - map_name, - package=None, - part=None, - reference_designator=None, - filter_component=False, - power=None, - r_jb=None, - r_jc=None, - height=None, - ): - override_component = ( - self.pcb.props["NativeComponentDefinitionProvider"] - .get(map_name, {}) # "instanceOverridesMap" - .get("oneOverrideBlk", []) - ) - if map_name == "instanceOverridesMap": - for o in override_component: - if o["overrideName"] == reference_designator: - override_component.remove(o) - elif map_name == "definitionOverridesMap": # pragma: no cover - for o in override_component: - if o["overridePartNumberName"] == part: - override_component.remove(o) - new_filter = {} - if filter_component or any(override_val is not None for override_val in [power, r_jb, r_jc, height]): - if map_name == "instanceOverridesMap": - new_filter.update({"overrideName": reference_designator}) - elif map_name == "definitionOverridesMap": # pragma: no cover - new_filter.update({"overridePartNumberName": part, "overrideGeometryName": package}) - new_filter.update( - { - "overrideProps": { - "isFiltered": filter_component, - "isOverridePower": power is not None, - "isOverrideThetaJb": r_jb is not None, - "isOverrideThetaJc": r_jc is not None, - "isOverrideHeight": height is not None, - "powerOverride": power if power is not None else "nan", - "thetaJbOverride": r_jb if r_jb is not None else "nan", - "thetaJcOverride": r_jc if r_jc is not None else "nan", - "heightOverride": height if height is not None else "nan", - }, - } - ) - override_component.append(new_filter) - self.pcb.props["NativeComponentDefinitionProvider"][map_name] = {"oneOverrideBlk": override_component} - return True - - @pyaedt_function_handler() - @disable_auto_update - def override_definition(self, package, part, filter_component=False, power=None, r_jb=None, r_jc=None, height=None): - """Set component override. - - Parameters - ---------- - package : str - Package name of the definition to override. - part : str - Part name of the definition to override. - filter_component : bool, optional - Whether to filter out the component. The default is ``False``. - power : str, optional - Override component power. Default is ``None``, in which case the power is not overridden. - r_jb : str, optional - Override component r_jb value. Default is ``None``, in which case the resistance is not overridden. - r_jc : str, optional - Override component r_jc value. Default is ``None``, in which case the resistance is not overridden. - height : str, optional - Override component height value. Default is ``None``, in which case the height is not overridden. - - Returns - ------- - bool - ``True`` if successful, ``False`` otherwise. - """ - if self._app.settings.aedt_version < "2024.2": - self._app.logger.error( - "This method is available only with AEDT 2024 R2 or later. Use 'override_instance()' method instead." - ) - return False - return self._override_common( # pragma : no cover - "definitionOverridesMap", - package=package, - part=part, - filter_component=filter_component, - power=power, - r_jb=r_jb, - r_jc=r_jc, - height=height, - ) - - @pyaedt_function_handler() - @disable_auto_update - def override_instance( - self, reference_designator, filter_component=False, power=None, r_jb=None, r_jc=None, height=None - ): - """Set instance override. - - Parameters - ---------- - reference_designator : str - Reference designator of the instance to override. - filter_component : bool, optional - Whether to filter out the component. The default is ``False``. - power : str, optional - Override component power. The default is ``None``, in which case the power is not overridden. - r_jb : str, optional - Override component r_jb value. The default is ``None``, in which case the resistance is not overridden. - r_jc : str, optional - Override component r_jc value. The default is ``None``, in which case the resistance is not overridden. - height : str, optional - Override component height value. The default is ``None``, in which case the height is not overridden. - - Returns - ------- - bool - ``True`` if successful, ``False`` otherwise. - """ - return self._override_common( - "instanceOverridesMap", - reference_designator=reference_designator, - filter_component=filter_component, - power=power, - r_jb=r_jb, - r_jc=r_jc, - height=height, - ) - - -class NativeComponentPCB(NativeComponentObject, object): - """Manages native component PCB data and execution. - - Parameters - ---------- - app : object - AEDT application from the ``pyaedt.application`` class. - component_type : str - Type of the component. - component_name : str - Name of the component. - props : dict - Properties of the boundary. - """ - - def __init__(self, app, component_type, component_name, props): - NativeComponentObject.__init__(self, app, component_type, component_name, props) - - @pyaedt_function_handler() - @disable_auto_update - def set_resolution(self, resolution): - """Set metal fraction mapping resolution. - - Parameters - ------- - resolution : int - Resolution level. Accepted variables between 1 and 5. - - Returns - ------- - bool - True if successful, else False. - """ - if resolution < 1 or resolution > 5: - self._app.logger.error("Valid resolution values are between 1 and 5.") - return False - self.props["NativeComponentDefinitionProvider"]["Resolution"] = resolution - self.props["NativeComponentDefinitionProvider"]["CustomResolution"] = False - return True - - @pyaedt_function_handler() - @disable_auto_update - def set_custom_resolution(self, row, col): - """Set custom metal fraction mapping resolution. - - Parameters - ---------- - row : int - Resolution level in rows direction. - col : int - Resolution level in columns direction. - - Returns - ------- - bool - True if successful, else False. - """ - self.props["NativeComponentDefinitionProvider"]["CustomResolutionRow"] = row - self.props["NativeComponentDefinitionProvider"]["CustomResolutionCol"] = col - self.props["NativeComponentDefinitionProvider"]["CustomResolution"] = True - return True - - @property - def power(self): - """Power dissipation assigned to the PCB.""" - return self.props["NativeComponentDefinitionProvider"].get("Power", "0W") - - @pyaedt_function_handler() - @disable_auto_update - def set_high_side_radiation( - self, - enabled, - surface_material="Steel-oxidised-surface", - radiate_to_ref_temperature=False, - view_factor=1, - ref_temperature="AmbientTemp", - ): - """Set high side radiation properties. - - Parameters - ---------- - enabled : bool - Whether high side radiation is enabled. - surface_material : str, optional - Surface material to apply. Default is ``"Steel-oxidised-surface"``. - radiate_to_ref_temperature : bool, optional - Whether to radiate to a reference temperature instead of objects in the model. - Default is ``False``. - view_factor : float, optional - View factor to use for radiation computation if ``radiate_to_ref_temperature`` - is set to ``True``. Default is 1. - ref_temperature : str, optional - Reference temperature to use for radiation computation if - ``radiate_to_ref_temperature`` is set to True. Default is ``"AmbientTemp"``. - - Returns - ------- - bool - ``True`` if successful, else ``False``. - """ - high_rad = { - "Radiate": enabled, - "RadiateTo - High": "RefTemperature - High" if radiate_to_ref_temperature else "AllObjects - High", - "Surface Material - High": surface_material, - } - if radiate_to_ref_temperature: - high_rad["Ref. Temperature - High"] = (ref_temperature,) - high_rad["View Factor - High"] = view_factor - self.props["NativeComponentDefinitionProvider"]["HighSide"] = high_rad - return True - - @pyaedt_function_handler() - @disable_auto_update - def set_low_side_radiation( - self, - enabled, - surface_material="Steel-oxidised-surface", - radiate_to_ref_temperature=False, - view_factor=1, - ref_temperature="AmbientTemp", - ): - """Set low side radiation properties. - - Parameters - ---------- - enabled : bool - Whether high side radiation is enabled. - surface_material : str, optional - Surface material to apply. Default is ``"Steel-oxidised-surface"``. - radiate_to_ref_temperature : bool, optional - Whether to radiate to a reference temperature instead of objects in the model. - Default is ``False``. - view_factor : float, optional - View factor to use for radiation computation if ``radiate_to_ref_temperature`` - is set to True. Default is 1. - ref_temperature : str, optional - Reference temperature to use for radiation computation if - ``radiate_to_ref_temperature`` is set to ``True``. Default is ``"AmbientTemp"``. - - Returns - ------- - bool - ``True`` if successful, else ``False``. - """ - low_side = { - "Radiate": enabled, - "RadiateTo": "RefTemperature - High" if radiate_to_ref_temperature else "AllObjects", - "Surface Material": surface_material, - } - if radiate_to_ref_temperature: - low_side["Ref. Temperature"] = (ref_temperature,) - low_side["View Factor"] = view_factor - self.props["NativeComponentDefinitionProvider"]["LowSide"] = low_side - return True - - @power.setter - @disable_auto_update - def power(self, value): - """Assign power dissipation to the PCB. - - Parameters - ---------- - value : str - Power to apply to the PCB. - """ - self.props["NativeComponentDefinitionProvider"]["Power"] = value - - @property - def force_source_solve(self): - """Force source solution option.""" - return self.props["NativeComponentDefinitionProvider"].get("DefnLink", {}).get("ForceSourceToSolve", False) - - @force_source_solve.setter - @disable_auto_update - def force_source_solve(self, val): - """Set Whether to force source solution. - - Parameters - ---------- - value : bool - Whether to force source solution. - """ - if not isinstance(val, bool): - self._app.logger.error("Only Boolean value can be accepted.") - return - return self.props["NativeComponentDefinitionProvider"]["DefnLink"].update({"ForceSourceToSolve": val}) - - @property - def preserve_partner_solution(self): - """Preserve parner solution option.""" - return self.props["NativeComponentDefinitionProvider"].get("DefnLink", {}).get("PreservePartnerSoln", False) - - @preserve_partner_solution.setter - @disable_auto_update - def preserve_partner_solution(self, val): - """Set Whether to preserve partner solution. - - Parameters - ---------- - val : bool - Whether to preserve partner solution. - """ - if not isinstance(val, bool): - self._app.logger.error("Only boolean can be accepted.") - return - return self.props["NativeComponentDefinitionProvider"]["DefnLink"].update({"PreservePartnerSoln": val}) - - @property - def included_parts(self): - """Parts options.""" - p = self.props["NativeComponentDefinitionProvider"].get("PartsChoice", 0) - if p == 0: - return None - elif p == 1: - return PCBSettingsDeviceParts(self, self._app) - elif p == 2: - return PCBSettingsPackageParts(self, self._app) - - @included_parts.setter - @disable_auto_update - def included_parts(self, value): - """Set PCB parts incusion option. - - Parameters - ---------- - value : str or int - Valid options are ``"None"``, ``"Device"``, and ``"Package"`` (or 0, 1, and 2 respectivaly) - """ - if value is None: - value = "None" - part_map = {"None": 0, "Device": 1, "Package": 2} - if not isinstance(value, int): - value = part_map.get(value, None) - if value is not None: - self.props["NativeComponentDefinitionProvider"]["PartsChoice"] = value - else: - self._app.logger.error( - 'Invalid part choice. Valid options are "None", "Device", and "Package" (or 0, 1, and 2 respectively).' - ) - - @pyaedt_function_handler() - @disable_auto_update - def set_low_side_radiation( - self, - enabled, - surface_material="Steel-oxidised-surface", - radiate_to_ref_temperature=False, - view_factor=1, - ref_temperature="AmbientTemp", - ): - """Set low side radiation properties. - - Parameters - ---------- - enabled : bool - Whether high side radiation is enabled. - surface_material : str, optional - Surface material to apply. Default is ``"Steel-oxidised-surface"``. - radiate_to_ref_temperature : bool, optional - Whether to radiate to a reference temperature instead of objects in the model. - Default is ``False``. - view_factor : float, optional - View factor to use for radiation computation if ``radiate_to_ref_temperature`` - is set to True. Default is 1. - ref_temperature : str, optional - Reference temperature to use for radiation computation if - ``radiate_to_ref_temperature`` is set to ``True``. Default is ``"AmbientTemp"``. - - Returns - ------- - bool - ``True`` if successful, else ``False``. - """ - low_side = { - "Radiate": enabled, - "RadiateTo": "RefTemperature - High" if radiate_to_ref_temperature else "AllObjects", - "Surface Material": surface_material, - } - if radiate_to_ref_temperature: - low_side["Ref. Temperature"] = (ref_temperature,) - low_side["View Factor"] = view_factor - self.props["NativeComponentDefinitionProvider"]["LowSide"] = low_side - return True - - @power.setter - @disable_auto_update - def power(self, value): - """Assign power dissipation to the PCB. - - Parameters - ---------- - value : str - Power to apply to the PCB. - """ - self.props["NativeComponentDefinitionProvider"]["Power"] = value - - @property - def force_source_solve(self): - """Force source solution option.""" - return self.props["NativeComponentDefinitionProvider"].get("DefnLink", {}).get("ForceSourceToSolve", False) - - @force_source_solve.setter - @disable_auto_update - def force_source_solve(self, val): - """Set Whether to force source solution. - - Parameters - ---------- - value : bool - Whether to force source solution. - """ - if not isinstance(val, bool): - self._app.logger.error("Only Boolean value can be accepted.") - return - return self.props["NativeComponentDefinitionProvider"]["DefnLink"].update({"ForceSourceToSolve": val}) - - @property - def preserve_partner_solution(self): - """Preserve parner solution option.""" - return self.props["NativeComponentDefinitionProvider"].get("DefnLink", {}).get("PreservePartnerSoln", False) - - @preserve_partner_solution.setter - @disable_auto_update - def preserve_partner_solution(self, val): - """Set Whether to preserve partner solution. - - Parameters - ---------- - val : bool - Whether to preserve partner solution. - """ - if not isinstance(val, bool): - self._app.logger.error("Only boolean can be accepted.") - return - return self.props["NativeComponentDefinitionProvider"]["DefnLink"].update({"PreservePartnerSoln": val}) - - @property - def included_parts(self): - """Parts options.""" - p = self.props["NativeComponentDefinitionProvider"].get("PartsChoice", 0) - if p == 0: - return None - elif p == 1: - return PCBSettingsDeviceParts(self, self._app) - elif p == 2: - return PCBSettingsPackageParts(self, self._app) - - @included_parts.setter - @disable_auto_update - def included_parts(self, value): - """Set PCB parts incusion option. - - Parameters - ---------- - value : str or int - Valid options are ``"None"``, ``"Device"``, and ``"Package"`` (or 0, 1, and 2 respectivaly) - """ - if value is None: - value = "None" - part_map = {"None": 0, "Device": 1, "Package": 2} - if not isinstance(value, int): - value = part_map.get(value, None) - if value is not None: - self.props["NativeComponentDefinitionProvider"]["PartsChoice"] = value - else: - self._app.logger.error( - 'Invalid part choice. Valid options are "None", "Device", and "Package" (or 0, 1, and 2 respectively).' - ) - - @pyaedt_function_handler() - def identify_extent_poly(self): - """Get polygon that defines board extent. - - Returns - ------- - str - Name of the polygon to include. - """ - from ansys.aedt.core import Hfss3dLayout - - prj = self.props["NativeComponentDefinitionProvider"]["DefnLink"]["Project"] - if prj == "This Project*": - prj = self._app.project_name - layout = Hfss3dLayout(project=prj, design=self.props["NativeComponentDefinitionProvider"]["DefnLink"]["Design"]) - layer = [o for o in layout.modeler.stackup.drawing_layers if o.type == "outline"][0] - outlines = [p for p in layout.modeler.polygons.values() if p.placement_layer == layer.name] - if len(outlines) > 1: - self._app.logger.info( - f"{outlines[0].name} automatically selected as ``extent_polygon``, " - f"pass ``extent_polygon`` argument explixitly to select a different one. " - f"Available choices are: {', '.join([o.name for o in outlines])}" - ) - elif len(outlines) == 0: - self._app.logger.error("No polygon found in the Outline layer.") - return False - return outlines[0].name - - @property - def board_cutout_material(self): - """Material applied to cutout regions.""" - return self.props["NativeComponentDefinitionProvider"].get("BoardCutoutMaterial", "air ") - - @property - def via_holes_material(self): - """Material applied to via hole regions.""" - return self.props["NativeComponentDefinitionProvider"].get("ViaHoleMaterial", "copper") - - @board_cutout_material.setter - @disable_auto_update - def board_cutout_material(self, value): - """Set material to apply to cutout regions. - - Parameters - ---------- - value : str - Material to apply to cutout regions. - """ - self.props["NativeComponentDefinitionProvider"]["BoardCutoutMaterial"] = value - - @via_holes_material.setter - @disable_auto_update - def via_holes_material(self, value): - """Set material to apply to via hole regions. - - Parameters - ---------- - value : str - Material to apply to via hole regions. - """ - self.props["NativeComponentDefinitionProvider"]["ViaHoleMaterial"] = value - - @pyaedt_function_handler() - @disable_auto_update - def set_board_extents(self, extent_type=None, extent_polygon=None): - """Set board extent. - - Parameters - ---------- - extent_type : str, optional - Extent definition of the PCB. Default is ``None`` in which case the 3D Layout extent - will be used. Other possible options are: ``"Bounding Box"`` or ``"Polygon"``. - extent_polygon : str, optional - Polygon name to use in the extent definition of the PCB. Default is ``None``. This - argument is mandatory if ``extent_type`` is ``"Polygon"``. - - Returns - ------- - bool - ``True`` if successful. ``False`` otherwise. - """ - if extent_type is None: - self.props["NativeComponentDefinitionProvider"]["Use3DLayoutExtents"] = True - else: - allowed_extent_types = ["Bounding Box", "Polygon"] - if extent_type not in allowed_extent_types: - self._app.logger.error( - f"Accepted argument for ``extent_type`` are:" - f" {', '.join(allowed_extent_types)}. {extent_type} provided" - ) - return False - self.props["NativeComponentDefinitionProvider"]["ExtentsType"] = extent_type - if extent_type == "Polygon": - if extent_polygon is None: - extent_polygon = self.identify_extent_poly() - if not extent_polygon: - return False - self.props["NativeComponentDefinitionProvider"]["OutlinePolygon"] = extent_polygon - return True - - -class BoundaryObject(BoundaryCommon, object): - """Manages boundary data and execution. - - Parameters - ---------- - app : object - An AEDT application from ``ansys.aedt.core.application``. - name : str - Name of the boundary. - props : dict, optional - Properties of the boundary. - boundarytype : str, optional - Type of the boundary. - - Examples - -------- - - Create a cylinder at the XY working plane and assign a copper coating of 0.2 mm to it. The Coating is a boundary - operation and coat will return a ``ansys.aedt.core.modules.boundary.BoundaryObject`` - - >>> from ansys.aedt.core import Hfss - >>> hfss =Hfss() - >>> origin = hfss.modeler.Position(0, 0, 0) - >>> inner = hfss.modeler.create_cylinder(hfss.PLANE.XY,origin,3,200,0,"inner") - >>> inner_id = hfss.modeler.get_obj_id("inner",) - >>> coat = hfss.assign_coating([inner_id],"copper",use_thickness=True,thickness="0.2mm") - """ - - def __init__(self, app, name, props=None, boundarytype=None, auto_update=True): - self.auto_update = False - self._app = app - self._name = name - self._props = None - if props: - self._props = BoundaryProps(self, props) - self._type = boundarytype - self._boundary_name = self.name - self.auto_update = auto_update - - @property - def object_properties(self): - """Object-oriented properties. - - Returns - ------- - class:`ansys.aedt.core.modeler.cad.elements_3d.BinaryTreeNode` - - """ - from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode - - child_object = None - design_childs = self._app.get_oo_name(self._app.odesign) - - if "Thermal" in design_childs: - cc = self._app.get_oo_object(self._app.odesign, "Thermal") - cc_names = self._app.get_oo_name(cc) - if self.name in cc_names: - child_object = cc_names - if child_object: - return BinaryTreeNode(self.name, child_object, False) - elif "Boundaries" in design_childs: - cc = self._app.get_oo_object(self._app.odesign, "Boundaries") - if self.name in cc.GetChildNames(): - child_object = cc.GetChildObject(self.name) - elif "Excitations" in design_childs and self.name in self._app.get_oo_name( - self._app.odesign, "Excitations" - ): - child_object = self._app.get_oo_object(self._app.odesign, "Excitations").GetChildObject(self.name) - elif self._app.design_type in ["Maxwell 3D", "Maxwell 2D"] and "Model" in design_childs: - model = self._app.get_oo_object(self._app.odesign, "Model") - if self.name in model.GetChildNames(): - child_object = model.GetChildObject(self.name) - elif "Excitations" in design_childs and self._app.get_oo_name(self._app.odesign, "Excitations"): - for port in self._app.get_oo_name(self._app.odesign, "Excitations"): - terminals = self._app.get_oo_name(self._app.odesign, f"Excitations\\{port}") - if self.name in terminals: - child_object = self._app.get_oo_object(self._app.odesign, f"Excitations\\{port}\\{self.name}") - elif "Conductors" in design_childs and self._app.get_oo_name(self._app.odesign, "Conductors"): - for port in self._app.get_oo_name(self._app.odesign, "Conductors"): - if self.name == port: - child_object = self._app.get_oo_object(self._app.odesign, f"Conductors\\{port}") - - if child_object: - return BinaryTreeNode(self.name, child_object, False) - - return False - - @property - def props(self): - """Boundary data. - - Returns - ------- - :class:BoundaryProps - """ - if self._props: - return self._props - props = self._get_boundary_data(self.name) - - if props: - self._props = BoundaryProps(self, props[0]) - self._type = props[1] - return self._props - - @property - def type(self): - """Boundary type. - - Returns - ------- - str - Returns the type of the boundary. - """ - if not self._type: - if self.available_properties: - if "Type" in self.available_properties: - self._type = self.props["Type"] - elif "BoundType" in self.available_properties: - self._type = self.props["BoundType"] - elif self.object_properties and self.object_properties.props["Type"]: - self._type = self.object_properties.props["Type"] - - if self._app.design_type == "Icepak" and self._type == "Source": - return "SourceIcepak" - else: - return self._type - - @type.setter - def type(self, value): - self._type = value - - @property - def name(self): - """Boundary Name.""" - return self._name - - @name.setter - def name(self, value): - self._name = value - self.update() - - @pyaedt_function_handler() - def _get_args(self, props=None): - """Retrieve arguments. - - Parameters - ---------- - props : - The default is ``None``. - - Returns - ------- - list - List of boundary properties. - - """ - if props is None: - props = self.props - arg = ["NAME:" + self.name] - _dict2arg(props, arg) - return arg - - @pyaedt_function_handler() - def create(self): - """Create a boundary. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - bound_type = self.type - if bound_type == "Perfect E": - self._app.oboundary.AssignPerfectE(self._get_args()) - elif bound_type == "Perfect H": - self._app.oboundary.AssignPerfectH(self._get_args()) - elif bound_type == "Aperture": - self._app.oboundary.AssignAperture(self._get_args()) - elif bound_type == "Radiation": - self._app.oboundary.AssignRadiation(self._get_args()) - elif bound_type == "FE-BI": - self._app.oboundary.AssignFEBI(self._get_args()) - elif bound_type == "Finite Conductivity": - self._app.oboundary.AssignFiniteCond(self._get_args()) - elif bound_type == "Lumped RLC": - self._app.oboundary.AssignLumpedRLC(self._get_args()) - elif bound_type == "Impedance": - self._app.oboundary.AssignImpedance(self._get_args()) - elif bound_type == "Layered Impedance": - self._app.oboundary.AssignLayeredImp(self._get_args()) - elif bound_type == "Anisotropic Impedance": - self._app.oboundary.AssignAnisotropicImpedance(self._get_args()) - elif bound_type == "Primary": - self._app.oboundary.AssignPrimary(self._get_args()) - elif bound_type == "Secondary": - self._app.oboundary.AssignSecondary(self._get_args()) - elif bound_type == "Lattice Pair": - self._app.oboundary.AssignLatticePair(self._get_args()) - elif bound_type == "HalfSpace": - self._app.oboundary.AssignHalfSpace(self._get_args()) - elif bound_type == "Multipaction SEE": - self._app.oboundary.AssignMultipactionSEE(self._get_args()) - elif bound_type == "Fresnel": - self._app.oboundary.AssignFresnel(self._get_args()) - elif bound_type == "Symmetry": - self._app.oboundary.AssignSymmetry(self._get_args()) - elif bound_type == "Zero Tangential H Field": - self._app.oboundary.AssignZeroTangentialHField(self._get_args()) - elif bound_type == "Zero Integrated Tangential H Field": - self._app.oboundary.AssignIntegratedZeroTangentialHField(self._get_args()) - elif bound_type == "Tangential H Field": - self._app.oboundary.AssignTangentialHField(self._get_args()) - elif bound_type == "Insulating": - self._app.oboundary.AssignInsulating(self._get_args()) - elif bound_type == "Independent": - self._app.oboundary.AssignIndependent(self._get_args()) - elif bound_type == "Dependent": - self._app.oboundary.AssignDependent(self._get_args()) - elif bound_type == "Band": - self._app.omodelsetup.AssignBand(self._get_args()) - elif bound_type == "InfiniteGround": - self._app.oboundary.AssignInfiniteGround(self._get_args()) - elif bound_type == "ThinConductor": - self._app.oboundary.AssignThinConductor(self._get_args()) - elif bound_type == "Stationary Wall": - self._app.oboundary.AssignStationaryWallBoundary(self._get_args()) - elif bound_type == "Symmetry Wall": - self._app.oboundary.AssignSymmetryWallBoundary(self._get_args()) - elif bound_type == "Recirculating": - self._app.oboundary.AssignRecircBoundary(self._get_args()) - elif bound_type == "Resistance": - self._app.oboundary.AssignResistanceBoundary(self._get_args()) - elif bound_type == "Conducting Plate": - self._app.oboundary.AssignConductingPlateBoundary(self._get_args()) - elif bound_type == "Adiabatic Plate": - self._app.oboundary.AssignAdiabaticPlateBoundary(self._get_args()) - elif bound_type == "Network": - self._app.oboundary.AssignNetworkBoundary(self._get_args()) - elif bound_type == "Grille": - self._app.oboundary.AssignGrilleBoundary(self._get_args()) - elif bound_type == "Block": - self._app.oboundary.AssignBlockBoundary(self._get_args()) - elif bound_type == "Blower": - self._app.oboundary.AssignBlowerBoundary(self._get_args()) - elif bound_type == "SourceIcepak": - self._app.oboundary.AssignSourceBoundary(self._get_args()) - elif bound_type == "Opening": - self._app.oboundary.AssignOpeningBoundary(self._get_args()) - elif bound_type == "EMLoss": - self._app.oboundary.AssignEMLoss(self._get_args()) - elif bound_type == "ThermalCondition": - self._app.oboundary.AssignThermalCondition(self._get_args()) - elif bound_type == "Convection": - self._app.oboundary.AssignConvection(self._get_args()) - elif bound_type == "HeatFlux": - self._app.oboundary.AssignHeatFlux(self._get_args()) - elif bound_type == "HeatGeneration": - self._app.oboundary.AssignHeatGeneration(self._get_args()) - elif bound_type == "Temperature": - self._app.oboundary.AssignTemperature(self._get_args()) - elif bound_type == "RotatingFluid": - self._app.oboundary.AssignRotatingFluid(self._get_args()) - elif bound_type == "Frictionless": - self._app.oboundary.AssignFrictionlessSupport(self._get_args()) - elif bound_type == "FixedSupport": - self._app.oboundary.AssignFixedSupport(self._get_args()) - elif bound_type == "Voltage": - self._app.oboundary.AssignVoltage(self._get_args()) - elif bound_type == "VoltageDrop": - self._app.oboundary.AssignVoltageDrop(self._get_args()) - elif bound_type == "Floating": - self._app.oboundary.AssignFloating(self._get_args()) - elif bound_type == "Current": - self._app.oboundary.AssignCurrent(self._get_args()) - elif bound_type == "CurrentDensity": - self._app.oboundary.AssignCurrentDensity(self._get_args()) - elif bound_type == "CurrentDensityGroup": - self._app.oboundary.AssignCurrentDensityGroup(self._get_args()[2], self._get_args()[3]) - elif bound_type == "CurrentDensityTerminal": - self._app.oboundary.AssignCurrentDensityTerminal(self._get_args()) - elif bound_type == "CurrentDensityTerminalGroup": - self._app.oboundary.AssignCurrentDensityTerminalGroup(self._get_args()[2], self._get_args()[3]) - elif bound_type == "Balloon": - self._app.oboundary.AssignBalloon(self._get_args()) - elif bound_type == "Winding" or bound_type == "Winding Group": - self._app.oboundary.AssignWindingGroup(self._get_args()) - elif bound_type == "Vector Potential": - self._app.oboundary.AssignVectorPotential(self._get_args()) - elif bound_type == "CoilTerminal" or bound_type == "Coil Terminal": - self._app.oboundary.AssignCoilTerminal(self._get_args()) - elif bound_type == "Coil": - self._app.oboundary.AssignCoil(self._get_args()) - elif bound_type == "Source": - self._app.oboundary.AssignSource(self._get_args()) - elif bound_type == "Sink": - self._app.oboundary.AssignSink(self._get_args()) - elif bound_type == "SignalNet": - self._app.oboundary.AssignSignalNet(self._get_args()) - elif bound_type == "GroundNet": - self._app.oboundary.AssignGroundNet(self._get_args()) - elif bound_type == "FloatingNet": - self._app.oboundary.AssignFloatingNet(self._get_args()) - elif bound_type == "SignalLine": - self._app.oboundary.AssignSingleSignalLine(self._get_args()) - elif bound_type == "ReferenceGround": - self._app.oboundary.AssignSingleReferenceGround(self._get_args()) - elif bound_type == "Circuit Port": - self._app.oboundary.AssignCircuitPort(self._get_args()) - elif bound_type == "Lumped Port": - self._app.oboundary.AssignLumpedPort(self._get_args()) - elif bound_type == "Wave Port": - self._app.oboundary.AssignWavePort(self._get_args()) - elif bound_type == "Floquet Port": - self._app.oboundary.AssignFloquetPort(self._get_args()) - elif bound_type == "AutoIdentify": - # Build reference conductor argument as a list of strings - # ref_cond_arg should be a list. - ref_cond_arg = ["NAME:ReferenceConductors"] + self.props["ReferenceConductors"] - self._app.oboundary.AutoIdentifyPorts( - ["NAME:Faces", self.props["Faces"]], - self.props["IsWavePort"], - ref_cond_arg, - self.name, - self.props["RenormalizeModes"], - ) - elif bound_type == "SBRTxRxSettings": - self._app.oboundary.SetSBRTxRxSettings(self._get_args()) - elif bound_type == "EndConnection": - self._app.oboundary.AssignEndConnection(self._get_args()) - elif bound_type == "Hybrid": - self._app.oboundary.AssignHybridRegion(self._get_args()) - elif bound_type == "FluxTangential": - self._app.oboundary.AssignFluxTangential(self._get_args()) - elif bound_type == "Plane Incident Wave": - self._app.oboundary.AssignPlaneWave(self._get_args()) - elif bound_type == "ResistiveSheet": - self._app.oboundary.AssignResistiveSheet(self._get_args()) - else: - return False - return True - - @pyaedt_function_handler() - def update(self): - """Update the boundary. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - bound_type = self.type - if bound_type == "Perfect E": - self._app.oboundary.EditPerfectE(self._boundary_name, self._get_args()) - elif bound_type == "Perfect H": - self._app.oboundary.EditPerfectH(self._boundary_name, self._get_args()) - elif bound_type == "Aperture": - self._app.oboundary.EditAperture(self._boundary_name, self._get_args()) - elif bound_type == "Radiation": - self._app.oboundary.EditRadiation(self._boundary_name, self._get_args()) - elif bound_type == "Finite Conductivity": - self._app.oboundary.EditFiniteCond(self._boundary_name, self._get_args()) - elif bound_type == "Lumped RLC": - self._app.oboundary.EditLumpedRLC(self._boundary_name, self._get_args()) - elif bound_type == "Impedance": - self._app.oboundary.EditImpedance(self._boundary_name, self._get_args()) - elif bound_type == "Layered Impedance": - self._app.oboundary.EditLayeredImpedance(self._boundary_name, self._get_args()) - elif bound_type == "Anisotropic Impedance": - self._app.oboundary.EditAssignAnisotropicImpedance( - self._boundary_name, self._get_args() - ) # pragma: no cover - elif bound_type == "Primary": - self._app.oboundary.EditPrimary(self._boundary_name, self._get_args()) - elif bound_type == "Secondary": - self._app.oboundary.EditSecondary(self._boundary_name, self._get_args()) - elif bound_type == "Lattice Pair": - self._app.oboundary.EditLatticePair(self._boundary_name, self._get_args()) - elif bound_type == "HalfSpace": - self._app.oboundary.EditHalfSpace(self._boundary_name, self._get_args()) - elif bound_type == "Multipaction SEE": - self._app.oboundary.EditMultipactionSEE(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Fresnel": - self._app.oboundary.EditFresnel(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Symmetry": - self._app.oboundary.EditSymmetry(self._boundary_name, self._get_args()) - elif bound_type == "Zero Tangential H Field": - self._app.oboundary.EditZeroTangentialHField(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Zero Integrated Tangential H Field": - self._app.oboundary.EditIntegratedZeroTangentialHField( - self._boundary_name, self._get_args() - ) # pragma: no cover - elif bound_type == "Tangential H Field": - self._app.oboundary.EditTangentialHField(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Insulating": - self._app.oboundary.EditInsulating(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Independent": - self._app.oboundary.EditIndependent(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Dependent": - self._app.oboundary.EditDependent(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Band": - self._app.omodelsetup.EditMotionSetup(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "InfiniteGround": - self._app.oboundary.EditInfiniteGround(self._boundary_name, self._get_args()) - elif bound_type == "ThinConductor": - self._app.oboundary.EditThinConductor(self._boundary_name, self._get_args()) - elif bound_type == "Stationary Wall": - self._app.oboundary.EditStationaryWallBoundary(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Symmetry Wall": - self._app.oboundary.EditSymmetryWallBoundary(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Recirculating": - self._app.oboundary.EditRecircBoundary(self._boundary_name, self._get_args()) - elif bound_type == "Resistance": - self._app.oboundary.EditResistanceBoundary(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Conducting Plate": - self._app.oboundary.EditConductingPlateBoundary(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Adiabatic Plate": - self._app.oboundary.EditAdiabaticPlateBoundary(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Network": - self._app.oboundary.EditNetworkBoundary(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Grille": - self._app.oboundary.EditGrilleBoundary(self._boundary_name, self._get_args()) - elif bound_type == "Opening": - self._app.oboundary.EditOpeningBoundary(self._boundary_name, self._get_args()) - elif bound_type == "EMLoss": - self._app.oboundary.EditEMLoss(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Block": - self._app.oboundary.EditBlockBoundary(self._boundary_name, self._get_args()) - elif bound_type == "Blower": - self._app.oboundary.EditBlowerBoundary(self._boundary_name, self._get_args()) - elif bound_type == "SourceIcepak": - self._app.oboundary.EditSourceBoundary(self._boundary_name, self._get_args()) - elif bound_type == "HeatFlux": - self._app.oboundary.EditHeatFlux(self._boundary_name, self._get_args()) - elif bound_type == "HeatGeneration": - self._app.oboundary.EditHeatGeneration(self._boundary_name, self._get_args()) - elif bound_type == "Voltage": - self._app.oboundary.EditVoltage(self._boundary_name, self._get_args()) - elif bound_type == "VoltageDrop": - self._app.oboundary.EditVoltageDrop(self._boundary_name, self._get_args()) - elif bound_type == "Current": - self._app.oboundary.EditCurrent(self._boundary_name, self._get_args()) - elif bound_type == "CurrentDensity": - self._app.oboundary.AssignCurrentDensity(self._get_args()) - elif bound_type == "CurrentDensityGroup": - self._app.oboundary.AssignCurrentDensityGroup(self._get_args()[2], self._get_args()[3]) - elif bound_type == "CurrentDensityTerminal": - self._app.oboundary.AssignCurrentDensityTerminal(self._get_args()) - elif bound_type == "CurrentDensityTerminalGroup": - self._app.oboundary.AssignCurrentDensityTerminalGroup(self._get_args()[2], self._get_args()[3]) - elif bound_type == "Winding" or bound_type == "Winding Group": - self._app.oboundary.EditWindingGroup(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Vector Potential": - self._app.oboundary.EditVectorPotential(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "CoilTerminal" or bound_type == "Coil Terminal": - self._app.oboundary.EditCoilTerminal(self._boundary_name, self._get_args()) - elif bound_type == "Coil": - self._app.oboundary.EditCoil(self._boundary_name, self._get_args()) - elif bound_type == "Source": - self._app.oboundary.EditTerminal(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "Sink": - self._app.oboundary.EditTerminal(self._boundary_name, self._get_args()) - elif bound_type == "SignalNet" or bound_type == "GroundNet" or bound_type == "FloatingNet": - self._app.oboundary.EditTerminal(self._boundary_name, self._get_args()) - elif bound_type in "Circuit Port": - self._app.oboundary.EditCircuitPort(self._boundary_name, self._get_args()) - elif bound_type in "Lumped Port": - self._app.oboundary.EditLumpedPort(self._boundary_name, self._get_args()) - elif bound_type in "Wave Port": - self._app.oboundary.EditWavePort(self._boundary_name, self._get_args()) - elif bound_type == "SetSBRTxRxSettings": - self._app.oboundary.SetSBRTxRxSettings(self._get_args()) # pragma: no cover - elif bound_type == "Floquet Port": - self._app.oboundary.EditFloquetPort(self._boundary_name, self._get_args()) # pragma: no cover - elif bound_type == "End Connection": - self._app.oboundary.EditEndConnection(self._boundary_name, self._get_args()) - elif bound_type == "Hybrid": - self._app.oboundary.EditHybridRegion(self._boundary_name, self._get_args()) - elif bound_type == "Terminal": - self._app.oboundary.EditTerminal(self._boundary_name, self._get_args()) - elif bound_type == "Plane Incident Wave": - self._app.oboundary.EditIncidentWave(self._boundary_name, self._get_args()) - else: - return False # pragma: no cover - - self._app._boundaries[self.name] = self._app._boundaries.pop(self._boundary_name) - self._boundary_name = self.name - - return True - - @pyaedt_function_handler() - def update_assignment(self): - """Update the boundary assignment. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - - out = ["Name:" + self.name] - - if "Faces" in self.props: - faces = self.props["Faces"] - faces_out = [] - if not isinstance(faces, list): - faces = [faces] - for f in faces: - if isinstance(f, (EdgePrimitive, FacePrimitive, VertexPrimitive)): - faces_out.append(f.id) - else: - faces_out.append(f) - out += ["Faces:=", faces_out] - - if "Objects" in self.props: - pr = [] - for el in self.props["Objects"]: - try: - pr.append(self._app.modeler[el].name) - except (KeyError, AttributeError): - pass - out += ["Objects:=", pr] - - if len(out) == 1: - return False - - self._app.oboundary.ReassignBoundary(out) - - return True - - -class MaxwellParameters(BoundaryCommon, object): - """Manages parameters data and execution. - - Parameters - ---------- - app : :class:`ansys.aedt.core.maxwell.Maxwell3d`, :class:`ansys.aedt.core.maxwell.Maxwell2d` - Either ``Maxwell3d`` or ``Maxwell2d`` application. - name : str - Name of the boundary. - props : dict, optional - Properties of the boundary. - boundarytype : str, optional - Type of the boundary. - - Examples - -------- - - Create a matrix in Maxwell3D return a ``ansys.aedt.core.modules.boundary.BoundaryObject`` - - >>> from ansys.aedt.core import Maxwell2d - >>> maxwell_2d = Maxwell2d() - >>> coil1 = maxwell_2d.modeler.create_rectangle([8.5,1.5, 0],[8, 3],True,"Coil_1","vacuum") - >>> coil2 = maxwell_2d.modeler.create_rectangle([8.5,1.5, 0],[8, 3],True,"Coil_2","vacuum") - >>> maxwell_2d.assign_matrix(["Coil_1", "Coil_2"]) - """ - - def __init__(self, app, name, props=None, boundarytype=None): - self.auto_update = False - self._app = app - self._name = name - self._props = None - if props: - self._props = BoundaryProps(self, props) - self.type = boundarytype - self._boundary_name = self.name - self.auto_update = True - self.__reduced_matrices = None - self.matrix_assignment = None - - @property - def reduced_matrices(self): - """List of reduced matrix groups for the parent matrix. - - Returns - ------- - dict - Dictionary of reduced matrices where the key is the name of the parent matrix - and the values are in a list of reduced matrix groups. - """ - if self._app.solution_type == "EddyCurrent": - self.__reduced_matrices = {} - cc = self._app.odesign.GetChildObject("Parameters") - parents = cc.GetChildNames() - if self.name in parents: - parent_object = self._app.odesign.GetChildObject("Parameters").GetChildObject(self.name) - parent_type = parent_object.GetPropValue("Type") - if parent_type == "Matrix": - self.matrix_assignment = parent_object.GetPropValue("Selection").split(",") - child_names = parent_object.GetChildNames() - self.__reduced_matrices = [] - for r in child_names: - self.__reduced_matrices.append(MaxwellMatrix(self._app, self.name, r)) - return self.__reduced_matrices - - @property - def object_properties(self): - """Object-oriented properties. - - Returns - ------- - class:`ansys.aedt.core.modeler.cad.elements_3d.BinaryTreeNode` - - """ - - from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode - - cc = self._app.odesign.GetChildObject("Parameters") - child_object = None - if self.name in cc.GetChildNames(): - child_object = self._app.odesign.GetChildObject("Parameters").GetChildObject(self.name) - elif self.name in self._app.odesign.GetChildObject("Parameters").GetChildNames(): - child_object = self._app.odesign.GetChildObject("Parameters").GetChildObject(self.name) - if child_object: - return BinaryTreeNode(self.name, child_object, False) - return False - - @property - def props(self): - """Maxwell parameter data. - - Returns - ------- - :class:BoundaryProps - """ - if self._props: - return self._props - props = self._get_boundary_data(self.name) - - if props: - self._props = BoundaryProps(self, props[0]) - self._type = props[1] - return self._props - - @property - def name(self): - """Boundary name.""" - return self._name - - @name.setter - def name(self, value): - self._name = value - self.update() - - @pyaedt_function_handler() - def _get_args(self, props=None): - """Retrieve arguments. - - Parameters - ---------- - props : - The default is ``None``. - - Returns - ------- - list - List of boundary properties. - - """ - if props is None: - props = self.props - arg = ["NAME:" + self.name] - _dict2arg(props, arg) - return arg - - @pyaedt_function_handler() - def create(self): - """Create a boundary. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - if self.type == "Matrix": - self._app.o_maxwell_parameters.AssignMatrix(self._get_args()) - elif self.type == "Torque": - self._app.o_maxwell_parameters.AssignTorque(self._get_args()) - elif self.type == "Force": - self._app.o_maxwell_parameters.AssignForce(self._get_args()) - elif self.type == "LayoutForce": - self._app.o_maxwell_parameters.AssignLayoutForce(self._get_args()) - else: - return False - return True - - @pyaedt_function_handler() - def update(self): - """Update the boundary. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - if self.type == "Matrix": - self._app.o_maxwell_parameters.EditMatrix(self._boundary_name, self._get_args()) - elif self.type == "Force": - self._app.o_maxwell_parameters.EditForce(self._boundary_name, self._get_args()) - elif self.type == "Torque": - self._app.o_maxwell_parameters.EditTorque(self._boundary_name, self._get_args()) - else: - return False - self._boundary_name = self.name - return True - - @pyaedt_function_handler() - def _create_matrix_reduction(self, red_type, sources, matrix_name=None, join_name=None): - if not self._app.solution_type == "EddyCurrent": - self._app.logger.error("Matrix reduction is possible only in Eddy current solvers.") - return False, False - if not matrix_name: - matrix_name = generate_unique_name("ReducedMatrix", n=3) - if not join_name: - join_name = generate_unique_name("Join" + red_type, n=3) - try: - self._app.o_maxwell_parameters.AddReduceOp( - self.name, - matrix_name, - ["NAME:" + join_name, "Type:=", "Join in " + red_type, "Sources:=", ",".join(sources)], - ) - return matrix_name, join_name - except Exception: - self._app.logger.error("Failed to create Matrix Reduction") - return False, False - - @pyaedt_function_handler() - def join_series(self, sources, matrix_name=None, join_name=None): - """ - - Parameters - ---------- - sources : list - Sources to be included in matrix reduction. - matrix_name : str, optional - name of the string to create. - join_name : str, optional - Name of the Join operation. - - Returns - ------- - (str, str) - Matrix name and Joint name. - - """ - return self._create_matrix_reduction( - red_type="Series", sources=sources, matrix_name=matrix_name, join_name=join_name - ) - - @pyaedt_function_handler() - def join_parallel(self, sources, matrix_name=None, join_name=None): - """ - - Parameters - ---------- - sources : list - Sources to be included in matrix reduction. - matrix_name : str, optional - name of the string to create. - join_name : str, optional - Name of the Join operation. - - Returns - ------- - (str, str) - Matrix name and Joint name. - - """ - return self._create_matrix_reduction( - red_type="Parallel", sources=sources, matrix_name=matrix_name, join_name=join_name - ) - - -class MaxwellMatrix(object): - def __init__(self, app, parent_name, reduced_name): - self._app = app - self.parent_matrix = parent_name - self.name = reduced_name - self.__sources = None - - @property - def sources(self): - """List of matrix sources.""" - if self._app.solution_type == "EddyCurrent": - sources = ( - self._app.odesign.GetChildObject("Parameters") - .GetChildObject(self.parent_matrix) - .GetChildObject(self.name) - .GetChildNames() - ) - self.__sources = {} - for s in sources: - excitations = ( - self._app.odesign.GetChildObject("Parameters") - .GetChildObject(self.parent_matrix) - .GetChildObject(self.name) - .GetChildObject(s) - .GetPropValue("Source") - ) - self.__sources[s] = excitations - return self.__sources - - @pyaedt_function_handler() - def update(self, old_source, source_type, new_source=None, new_excitations=None): - """Update the reduced matrix. - - Parameters - ---------- - old_source : str - Original name of the source to update. - source_type : str - Source type, which can be ``Series`` or ``Parallel``. - new_source : str, optional - New name of the source to update. - The default value is the old source name. - new_excitations : str, optional - List of excitations to include in the matrix reduction. - The default values are excitations included in the source to update. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - """ - if old_source not in self.sources.keys(): - self._app.logger.error("Source does not exist.") - return False - else: - new_excitations = self.sources[old_source] if not new_excitations else new_excitations - if source_type.lower() not in ["series", "parallel"]: - self._app.logger.error("Join type not valid.") - return False - if not new_source: - new_source = old_source - args = ["NAME:" + new_source, "Type:=", "Join in " + source_type, "Sources:=", new_excitations] - self._app.o_maxwell_parameters.EditReduceOp(self.parent_matrix, self.name, old_source, args) - return True - - @pyaedt_function_handler() - def delete(self, source): - """Delete a specified source in a reduced matrix. - - Parameters - ---------- - source : string - Name of the source to delete. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - """ - if source not in self.sources.keys(): - self._app.logger.error("Invalid source name.") - return False - self._app.o_maxwell_parameters.DeleteReduceOp(self.parent_matrix, self.name, source) - return True - - -class FieldSetup(BoundaryCommon, object): - """Manages far field and near field component data and execution. - - Examples - -------- - In this example the sphere1 returned object is a ``ansys.aedt.core.modules.boundary.FarFieldSetup`` - >>> from ansys.aedt.core import Hfss - >>> hfss = Hfss() - >>> sphere1 = hfss.insert_infinite_sphere() - >>> sphere1.props["ThetaStart"] = "-90deg" - >>> sphere1.props["ThetaStop"] = "90deg" - >>> sphere1.props["ThetaStep"] = "2deg" - >>> sphere1.delete() - """ - - def __init__(self, app, component_name, props, component_type): - self.auto_update = False - self._app = app - self.type = component_type - self._name = component_name - self.props = BoundaryProps(self, props) - self.auto_update = True - - @property - def name(self): - """Variable name.""" - return self._name - - @name.setter - def name(self, value): - self._app.oradfield.RenameSetup(self._name, value) - self._name = value - - @pyaedt_function_handler() - def _get_args(self, props=None): - if props is None: - props = self.props - arg = ["NAME:" + self.name] - _dict2arg(props, arg) - return arg - - @pyaedt_function_handler() - def create(self): - """Create a Field Setup Component in HFSS. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - - if self.type == "FarFieldSphere": - self._app.oradfield.InsertInfiniteSphereSetup(self._get_args()) - elif self.type == "NearFieldBox": - self._app.oradfield.InsertBoxSetup(self._get_args()) - elif self.type == "NearFieldSphere": - self._app.oradfield.InsertSphereSetup(self._get_args()) - elif self.type == "NearFieldRectangle": - self._app.oradfield.InsertRectangleSetup(self._get_args()) - elif self.type == "NearFieldLine": - self._app.oradfield.InsertLineSetup(self._get_args()) - elif self.type == "AntennaOverlay": - self._app.oradfield.AddAntennaOverlay(self._get_args()) - elif self.type == "FieldSourceGroup": - self._app.oradfield.AddRadFieldSourceGroup(self._get_args()) - return True - - @pyaedt_function_handler() - def update(self): - """Update the Field Setup in AEDT. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - - if self.type == "FarFieldSphere": - self._app.oradfield.EditInfiniteSphereSetup(self.name, self._get_args()) - elif self.type == "NearFieldBox": - self._app.oradfield.EditBoxSetup(self.name, self._get_args()) - elif self.type == "NearFieldSphere": - self._app.oradfield.EditSphereSetup(self.name, self._get_args()) - elif self.type == "NearFieldRectangle": - self._app.oradfield.EditRectangleSetup(self.name, self._get_args()) - elif self.type == "NearFieldLine": - self._app.oradfield.EditLineSetup(self.name, self._get_args()) - elif self.type == "AntennaOverlay": - self._app.oradfield.EditAntennaOverlay(self.name, self._get_args()) - elif self.type == "FieldSourceGroup": - self._app.oradfield.EditRadFieldSourceGroup(self._get_args()) - return True - - @pyaedt_function_handler() - def delete(self): - """Delete the Field Setup in AEDT. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - self._app.oradfield.DeleteSetup([self.name]) - for el in self._app.field_setups: - if el.name == self.name: - self._app.field_setups.remove(el) - return True - - -class FarFieldSetup(FieldSetup, object): - """Manages Far Field Component data and execution. - - Examples - -------- - in this example the sphere1 returned object is a ``ansys.aedt.core.modules.boundary.FarFieldSetup`` - >>> from ansys.aedt.core import Hfss - >>> hfss = Hfss() - >>> sphere1 = hfss.insert_infinite_sphere() - >>> sphere1.props["ThetaStart"] = "-90deg" - >>> sphere1.props["ThetaStop"] = "90deg" - >>> sphere1.props["ThetaStep"] = "2deg" - >>> sphere1.delete() - """ - - def __init__(self, app, component_name, props, component_type, units="deg"): - FieldSetup.__init__(self, app, component_name, props, component_type) - self.units = units - - @property - def definition(self): - """Set/Get the Far Field Angle Definition.""" - return self.props["CSDefinition"] - - @definition.setter - def definition(self, value): - actual_value = self.props["CSDefinition"] - self.props["CSDefinition"] = value - actual_defs = None - defs = None - if actual_value != value and value == "Theta-Phi": - defs = ["ThetaStart", "ThetaStop", "ThetaStep", "PhiStart", "PhiStop", "PhiStep"] - actual_defs = [ - "AzimuthStart", - "AzimuthStop", - "AzimuthStep", - "ElevationStart", - "ElevationStop", - "ElevationStep", - ] - elif actual_value != value and value == "El Over Az": - defs = ["AzimuthStart", "AzimuthStop", "AzimuthStep", "ElevationStart", "ElevationStop", "ElevationStep"] - if actual_value == "Theta-Phi": - actual_defs = ["ThetaStart", "ThetaStop", "ThetaStep", "PhiStart", "PhiStop", "PhiStep"] - else: - actual_defs = [ - "AzimuthStart", - "AzimuthStop", - "AzimuthStep", - "ElevationStart", - "ElevationStop", - "ElevationStep", - ] - elif actual_value != value: - defs = ["ElevationStart", "ElevationStop", "ElevationStep", "AzimuthStart", "AzimuthStop", "AzimuthStep"] - if actual_value == "Theta-Phi": - actual_defs = ["ThetaStart", "ThetaStop", "ThetaStep", "PhiStart", "PhiStop", "PhiStep"] - else: - actual_defs = [ - "ElevationStart", - "ElevationStop", - "ElevationStep", - "AzimuthStart", - "AzimuthStop", - "AzimuthStep", - ] - if actual_defs != defs: - self.props[defs[0]] = self.props[actual_defs[0]] - self.props[defs[1]] = self.props[actual_defs[1]] - self.props[defs[2]] = self.props[actual_defs[2]] - self.props[defs[3]] = self.props[actual_defs[3]] - self.props[defs[4]] = self.props[actual_defs[4]] - self.props[defs[5]] = self.props[actual_defs[5]] - del self.props[actual_defs[0]] - del self.props[actual_defs[1]] - del self.props[actual_defs[2]] - del self.props[actual_defs[3]] - del self.props[actual_defs[4]] - del self.props[actual_defs[5]] - self.update() - - @property - def use_custom_radiation_surface(self): - """Set/Get the Far Field Radiation Surface Enable.""" - return self.props["UseCustomRadiationSurface"] - - @use_custom_radiation_surface.setter - def use_custom_radiation_surface(self, value): - self.props["UseCustomRadiationSurface"] = value - self.update() - - @property - def custom_radiation_surface(self): - """Set/Get the Far Field Radiation Surface FaceList.""" - return self.props["CustomRadiationSurface"] - - @custom_radiation_surface.setter - def custom_radiation_surface(self, value): - if value: - self.props["UseCustomRadiationSurface"] = True - self.props["CustomRadiationSurface"] = value - else: - self.props["UseCustomRadiationSurface"] = False - self.props["CustomRadiationSurface"] = "" - self.update() - - @property - def use_local_coordinate_system(self): - """Set/Get the usage of a custom Coordinate System.""" - return self.props["UseLocalCS"] - - @use_local_coordinate_system.setter - def use_local_coordinate_system(self, value): - self.props["UseLocalCS"] = value - self.update() - - @property - def local_coordinate_system(self): - """Set/Get the custom Coordinate System name.""" - return self.props["CoordSystem"] - - @local_coordinate_system.setter - def local_coordinate_system(self, value): - if value: - self.props["UseLocalCS"] = True - self.props["CoordSystem"] = value - else: - self.props["UseLocalCS"] = False - self.props["CoordSystem"] = "" - self.update() - - @property - def polarization(self): - """Set/Get the Far Field Polarization.""" - return self.props["Polarization"] - - @polarization.setter - def polarization(self, value): - self.props["Polarization"] = value - self.update() - - @property - def slant_angle(self): - """Set/Get the Far Field Slant Angle if Polarization is Set to `Slant`.""" - - if self.props["Polarization"] == "Slant": - return self.props["SlantAngle"] - else: - return - - @slant_angle.setter - def slant_angle(self, value): - self.props["Polarization"] = "Slant" - self.props["SlantAngle"] = value - self.update() - - @property - def theta_start(self): - """Set/Get the Far Field Theta Start Angle if Definition is Set to `Theta-Phi`.""" - - if "ThetaStart" in self.props: - return self.props["ThetaStart"] - else: - return - - @property - def theta_stop(self): - """Set/Get the Far Field Theta Stop Angle if Definition is Set to `Theta-Phi`.""" - - if "ThetaStop" in self.props: - return self.props["ThetaStop"] - else: - return - - @property - def theta_step(self): - """Set/Get the Far Field Theta Step Angle if Definition is Set to `Theta-Phi`.""" - - if "ThetaStep" in self.props: - return self.props["ThetaStep"] - else: - return - - @property - def phi_start(self): - """Set/Get the Far Field Phi Start Angle if Definition is Set to `Theta-Phi`.""" - - if "PhiStart" in self.props: - return self.props["PhiStart"] - else: - return - - @property - def phi_stop(self): - """Set/Get the Far Field Phi Stop Angle if Definition is Set to `Theta-Phi`.""" - - if "PhiStop" in self.props: - return self.props["PhiStop"] - else: - return - - @property - def phi_step(self): - """Set/Get the Far Field Phi Step Angle if Definition is Set to `Theta-Phi`.""" - - if "PhiStep" in self.props: - return self.props["PhiStep"] - else: - return - - @property - def azimuth_start(self): - """Set/Get the Far Field Azimuth Start Angle if Definition is Set to `Az Over El` or `El Over Az`.""" - - if "AzimuthStart" in self.props: - return self.props["AzimuthStart"] - else: - return - - @property - def azimuth_stop(self): - """Set/Get the Far Field Azimuth Stop Angle if Definition is Set to `Az Over El` or `El Over Az`.""" - - if "AzimuthStop" in self.props: - return self.props["AzimuthStop"] - else: - return - - @property - def azimuth_step(self): - """Set/Get the Far Field Azimuth Step Angle if Definition is Set to `Az Over El` or `El Over Az`.""" - - if "AzimuthStep" in self.props: - return self.props["AzimuthStep"] - else: - return - - @property - def elevation_start(self): - """Set/Get the Far Field Elevation Start Angle if Definition is Set to `Az Over El` or `El Over Az`.""" - - if "ElevationStart" in self.props: - return self.props["ElevationStart"] - else: - return - - @property - def elevation_stop(self): - """Set/Get the Far Field Elevation Stop Angle if Definition is Set to `Az Over El` or `El Over Az`.""" - - if "ElevationStop" in self.props: - return self.props["ElevationStop"] - else: - return - - @property - def elevation_step(self): - """Set/Get the Far Field Elevation Step Angle if Definition is Set to `Az Over El` or `El Over Az`.""" - - if "ElevationStep" in self.props: - return self.props["ElevationStep"] - else: - return - - @theta_start.setter - def theta_start(self, value): - if "ThetaStart" in self.props: - self.props["ThetaStart"] = _dim_arg(value, self.units) - self.update() - - @theta_stop.setter - def theta_stop(self, value): - if "ThetaStop" in self.props: - self.props["ThetaStop"] = _dim_arg(value, self.units) - self.update() - - @theta_step.setter - def theta_step(self, value): - if "ThetaStep" in self.props: - self.props["ThetaStep"] = _dim_arg(value, self.units) - self.update() - - @phi_start.setter - def phi_start(self, value): - if "PhiStart" in self.props: - self.props["PhiStart"] = _dim_arg(value, self.units) - self.update() - - @phi_stop.setter - def phi_stop(self, value): - if "PhiStop" in self.props: - self.props["PhiStop"] = _dim_arg(value, self.units) - self.update() - - @phi_step.setter - def phi_step(self, value): - if "PhiStep" in self.props: - self.props["PhiStep"] = _dim_arg(value, self.units) - self.update() - - @azimuth_start.setter - def azimuth_start(self, value): - if "AzimuthStart" in self.props: - self.props["AzimuthStart"] = _dim_arg(value, self.units) - self.update() - - @azimuth_stop.setter - def azimuth_stop(self, value): - if "AzimuthStop" in self.props: - self.props["AzimuthStop"] = _dim_arg(value, self.units) - self.update() - - @azimuth_step.setter - def azimuth_step(self, value): - if "AzimuthStep" in self.props: - self.props["AzimuthStep"] = _dim_arg(value, self.units) - self.update() - - @elevation_start.setter - def elevation_start(self, value): - if "ElevationStart" in self.props: - self.props["ElevationStart"] = _dim_arg(value, self.units) - self.update() - - @elevation_stop.setter - def elevation_stop(self, value): - if "ElevationStop" in self.props: - self.props["ElevationStop"] = _dim_arg(value, self.units) - self.update() - - @elevation_step.setter - def elevation_step(self, value): - if "ElevationStep" in self.props: - self.props["ElevationStep"] = _dim_arg(value, self.units) - self.update() - - -class NearFieldSetup(FieldSetup, object): - """Manages Near Field Component data and execution. - - Examples - -------- - in this example the rectangle1 returned object is a ``ansys.aedt.core.modules.boundary.NearFieldSetup`` - >>> from ansys.aedt.core import Hfss - >>> hfss = Hfss() - >>> rectangle1 = hfss.insert_near_field_rectangle() - """ - - def __init__(self, app, component_name, props, component_type): - FieldSetup.__init__(self, app, component_name, props, component_type) - - -class Matrix(object): - """Manages Matrix in Q3d and Q2d Projects. - - Examples - -------- - - - """ - - def __init__(self, app, name, operations=None): - self._app = app - self.omatrix = self._app.omatrix - self.name = name - self._sources = [] - if operations: - if isinstance(operations, list): - self._operations = operations - else: - self._operations = [operations] - self.CATEGORIES = CATEGORIESQ3D() - - @pyaedt_function_handler() - def sources(self, is_gc_sources=True): - """List of matrix sources. - - Parameters - ---------- - is_gc_sources : bool, - In Q3d, define if to return GC sources or RL sources. Default `True`. - - Returns - ------- - List - """ - if self.name in list(self._app.omatrix.ListReduceMatrixes()): - if self._app.design_type == "Q3D Extractor": - self._sources = list(self._app.omatrix.ListReduceMatrixReducedSources(self.name, is_gc_sources)) - else: - self._sources = list(self._app.omatrix.ListReduceMatrixReducedSources(self.name)) - return self._sources - - @pyaedt_function_handler() - def get_sources_for_plot( - self, - get_self_terms=True, - get_mutual_terms=True, - first_element_filter=None, - second_element_filter=None, - category="C", - ): - """Return a list of source of specified matrix ready to be used in plot reports. - - Parameters - ---------- - get_self_terms : bool - Either if self terms have to be returned or not. - get_mutual_terms : bool - Either if mutual terms have to be returned or not. - first_element_filter : str, optional - Filter to apply to first element of equation. It accepts `*` and `?` as special characters. - second_element_filter : str, optional - Filter to apply to second element of equation. It accepts `*` and `?` as special characters. - category : str - Plot category name as in the report. Eg. "C" is category Capacitance. - Matrix `CATEGORIES` property can be used to map available categories. - - Returns - ------- - list - - Examples - -------- - >>> from ansys.aedt.core import Q3d - >>> q3d = Q3d(project_path) - >>> q3d.matrices[0].get_sources_for_plot(first_element_filter="Bo?1", - ... second_element_filter="GND*", category="DCL") - """ - if not first_element_filter: - first_element_filter = "*" - if not second_element_filter: - second_element_filter = "*" - is_cg = False - if category in [self.CATEGORIES.Q3D.C, self.CATEGORIES.Q3D.G]: - is_cg = True - list_output = [] - if get_self_terms: - for el in self.sources(is_gc_sources=is_cg): - value = f"{category}({el},{el})" - if filter_tuple(value, first_element_filter, second_element_filter): - list_output.append(value) - if get_mutual_terms: - for el1 in self.sources(is_gc_sources=is_cg): - for el2 in self.sources(is_gc_sources=is_cg): - if el1 != el2: - value = f"{category}({el1},{el2})" - if filter_tuple(value, first_element_filter, second_element_filter): - list_output.append(value) - return list_output - - @property - def operations(self): - """List of matrix operations. - - Returns - ------- - List - """ - if self.name in list(self._app.omatrix.ListReduceMatrixes()): - self._operations = self._app.omatrix.ListReduceMatrixOperations(self.name) - return self._operations - - @pyaedt_function_handler() - def create( - self, - source_names=None, - new_net_name=None, - new_source_name=None, - new_sink_name=None, - ): - """Create a new matrix. - - Parameters - ---------- - source_names : str, list - List or str containing the content of the matrix reduction (eg. source name). - new_net_name : str, optional - Name of the new net. The default is ``None``. - new_source_name : str, optional - Name of the new source. The default is ``None``. - new_sink_name : str, optional - Name of the new sink. The default is ``None``. - - Returns - ------- - bool - `True` if succeeded. - """ - if not isinstance(source_names, list) and source_names: - source_names = [source_names] - - command = self._write_command(source_names, new_net_name, new_source_name, new_sink_name) - self.omatrix.InsertRM(self.name, command) - return True - - @pyaedt_function_handler() - def delete(self): - """Delete current matrix. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - """ - self.omatrix.DeleteRM(self.name) - for el in self._app.matrices: - if el.name == self.name: - self._app.matrices.remove(el) - return True - - @pyaedt_function_handler() - def add_operation( - self, - operation_type, - source_names=None, - new_net_name=None, - new_source_name=None, - new_sink_name=None, - ): - """Add a new operation to existing matrix. - - Parameters - ---------- - operation_type : str - Operation to perform - source_names : str, list - List or str containing the content of the matrix reduction (eg. source name). - new_net_name : str, optional - Name of the new net. The default is ``None``. - new_source_name : str, optional - Name of the new source. The default is ``None``. - new_sink_name : str, optional - Name of the new sink. The default is ``None``. - - Returns - ------- - bool - `True` if succeeded. - """ - self._operations.append(operation_type) - if not isinstance(source_names, list) and source_names: - source_names = [source_names] - - if not new_net_name: - new_net_name = generate_unique_name("Net") - - if not new_source_name: - new_source_name = generate_unique_name("Source") - - if not new_sink_name: - new_sink_name = generate_unique_name("Sink") - - command = self._write_command(source_names, new_net_name, new_source_name, new_sink_name) - self.omatrix.RMAddOp(self.name, command) - return True - - @pyaedt_function_handler() - def _write_command(self, source_names, new_name, new_source, new_sink): - if self._operations[-1] == "JoinSeries": - command = f"""{self._operations[-1]}('{new_name}', '{"', '".join(source_names)}')""" - elif self._operations[-1] == "JoinParallel": - command = ( - f"""{self._operations[-1]}('{new_name}', '{new_source}', '{new_sink}', '{"', '".join(source_names)}')""" - ) - elif self._operations[-1] == "JoinSelectedTerminals": - command = f"""{self._operations[-1]}('', '{"', '".join(source_names)}')""" - elif self._operations[-1] == "FloatInfinity": - command = "FloatInfinity()" - elif self._operations[-1] == "AddGround": - command = f"""{self._operations[-1]}(SelectionArray[{len(source_names)}: '{"', '".join(source_names)}'], - OverrideInfo())""" - elif ( - self._operations[-1] == "SetReferenceGround" - or self._operations[-1] == "SetReferenceGround" - or self._operations[-1] == "Float" - ): - command = f"""{self._operations[-1]}(SelectionArray[{len(source_names)}: '{"', '".join(source_names)}'], - OverrideInfo())""" - elif self._operations[-1] == "Parallel" or self._operations[-1] == "DiffPair": - id_ = 0 - for el in self._app.boundaries: - if el.name == source_names[0]: - id_ = self._app.modeler[el.props["Objects"][0]].id - command = f"""{self._operations[-1]}(SelectionArray[{len(source_names)}: '{"', '".join(source_names)}'], - OverrideInfo({id_}, '{new_name}'))""" - else: - command = f"""{self._operations[-1]}('{"', '".join(source_names)}')""" - return command - - -class BoundaryObject3dLayout(BoundaryCommon, object): - """Manages boundary data and execution for Hfss3dLayout. - - Parameters - ---------- - app : object - An AEDT application from ``ansys.aedt.core.application``. - name : str - Name of the boundary. - props : dict - Properties of the boundary. - boundarytype : str - Type of the boundary. - """ - - def __init__(self, app, name, props, boundarytype): - self.auto_update = False - self._app = app - self._name = name - self._props = None - if props: - self._props = BoundaryProps(self, props) - self.type = boundarytype - self._boundary_name = self.name - self.auto_update = True - - @property - def object_properties(self): - """Object-oriented properties. - - Returns - ------- - class:`ansys.aedt.core.modeler.cad.elements_3d.BinaryTreeNode` - - """ - from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode - - cc = self._app.odesign.GetChildObject("Excitations") - child_object = None - if self.name in cc.GetChildNames(): - child_object = self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) - elif self.name in self._app.odesign.GetChildObject("Excitations").GetChildNames(): - child_object = self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) - if child_object: - return BinaryTreeNode(self.name, child_object, False) - - if "Boundaries" in self._app.odesign.GetChildNames(): - cc = self._app.odesign.GetChildObject("Boundaries") - if self.name in cc.GetChildNames(): - child_object = self._app.odesign.GetChildObject("Boundaries").GetChildObject(self.name) - elif self.name in self._app.odesign.GetChildObject("Boundaries").GetChildNames(): - child_object = self._app.odesign.GetChildObject("Boundaries").GetChildObject(self.name) - if child_object: - return BinaryTreeNode(self.name, child_object, False) - return False - - @property - def name(self): - """Boundary Name.""" - return self._name - - @name.setter - def name(self, value): - if "Port" in self.props: - self.auto_update = False - self.props["Port"] = value - self.auto_update = True - self.update() - self._name = value - - @property - def props(self): - """Excitation data. - - Returns - ------- - :class:BoundaryProps - """ - if self._props: - return self._props - props = self._get_boundary_data(self.name) - - if props: - self._props = BoundaryProps(self, props[0]) - self._type = props[1] - return self._props - - @pyaedt_function_handler() - def _get_args(self, props=None): - """Retrieve arguments. - - Parameters - ---------- - props : - The default is ``None``. - - Returns - ------- - list - List of boundary properties. - - """ - if props is None: - props = self.props - arg = ["NAME:" + self.name] - _dict2arg(props, arg) - return arg - - @pyaedt_function_handler() - def _refresh_properties(self): - if len(self._app.oeditor.GetProperties("EM Design", f"Excitations:{self.name}")) != len(self.props): - propnames = self._app.oeditor.GetProperties("EM Design", f"Excitations:{self.name}") - props = {} - for prop in propnames: - props[prop] = self._app.oeditor.GetPropertyValue("EM Design", f"Excitations:{self.name}", prop) - self._props = BoundaryProps(self, props) - - @pyaedt_function_handler() - def update(self): - """Update the boundary. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - updated = False - for el in list(self.props.keys()): - if el == "Port" and self._name != self.props[el]: - self._app.oeditor.SetPropertyValue("EM Design", "Excitations:" + self.name, el, self.props[el]) - self._name = self.props[el] - elif el in self._app.oeditor.GetProperties("EM Design", f"Excitations:{self.name}") and self.props[ - el - ] != self._app.oeditor.GetPropertyValue("EM Design", "Excitations:" + self.name, el): - self._app.oeditor.SetPropertyValue("EM Design", "Excitations:" + self.name, el, self.props[el]) - updated = True - - if updated: - self._refresh_properties() - - return True - - -class Sources(object): - """Manages sources in Circuit projects.""" - - def __init__(self, app, name, source_type=None): - self._app = app - self._name = name - self._props = self._source_props(name, source_type) - self.source_type = source_type - if not source_type: - self.source_type = self._source_type_by_key() - self._auto_update = True - - @property - def name(self): - """Source name. - - Returns - ------- - str - """ - return self._name - - @name.setter - def name(self, source_name): - if source_name not in self._app.source_names: - if source_name != self._name: - original_name = self._name - self._name = source_name - for port in self._app.excitations: - if original_name in self._app.excitation_objects[port].props["EnabledPorts"]: - self._app.excitation_objects[port].props["EnabledPorts"] = [ - w.replace(original_name, source_name) - for w in self._app.excitation_objects[port].props["EnabledPorts"] - ] - if original_name in self._app.excitation_objects[port].props["EnabledAnalyses"]: - self._app.excitation_objects[port].props["EnabledAnalyses"][source_name] = ( - self._app.excitation_objects[port].props["EnabledAnalyses"].pop(original_name) - ) - self.update(original_name) - else: - self._logger.warning("Name %s already assigned in the design", source_name) - - @property - def _logger(self): - """Logger.""" - return self._app.logger - - @pyaedt_function_handler() - def _source_props(self, source, source_type=None): - source_prop_dict = {} - if source in self._app.source_names: - source_aedt_props = self._app.odesign.GetChildObject("Excitations").GetChildObject(source) - for el in source_aedt_props.GetPropNames(): - if el == "CosimDefinition": - source_prop_dict[el] = None - elif el == "FreqDependentSourceData": - data = self._app.design_properties["NexximSources"]["Data"][source]["FDSFileName"] - freqs = re.findall(r"freqs=\[(.*?)\]", data) - magnitude = re.findall(r"magnitude=\[(.*?)\]", data) - angle = re.findall(r"angle=\[(.*?)\]", data) - vreal = re.findall(r"vreal=\[(.*?)\]", data) - vimag = re.findall(r"vimag=\[(.*?)\]", data) - source_file = re.findall("voltage_source_file=", data) - source_prop_dict["frequencies"] = None - source_prop_dict["vmag"] = None - source_prop_dict["vang"] = None - source_prop_dict["vreal"] = None - source_prop_dict["vimag"] = None - source_prop_dict["fds_filename"] = None - source_prop_dict["magnitude_angle"] = False - source_prop_dict["FreqDependentSourceData"] = data - if freqs: - source_prop_dict["frequencies"] = [float(i) for i in freqs[0].split()] - if magnitude: - source_prop_dict["vmag"] = [float(i) for i in magnitude[0].split()] - if angle: - source_prop_dict["vang"] = [float(i) for i in angle[0].split()] - if vreal: - source_prop_dict["vreal"] = [float(i) for i in vreal[0].split()] - if vimag: - source_prop_dict["vimag"] = [float(i) for i in vimag[0].split()] - if source_file: - source_prop_dict["fds_filename"] = data[len(re.findall("voltage_source_file=", data)[0]) :] - else: - if freqs and magnitude and angle: - source_prop_dict["magnitude_angle"] = True - elif freqs and vreal and vimag: - source_prop_dict["magnitude_angle"] = False - - elif el != "Name" and el != "Noise": - source_prop_dict[el] = source_aedt_props.GetPropValue(el) - if not source_prop_dict[el]: - source_prop_dict[el] = "" - else: - if source_type in SourceKeys.SourceNames: - command_template = SourceKeys.SourceTemplates[source_type] - commands = copy.deepcopy(command_template) - props = [value for value in commands if type(value) == list] - for el in props[0]: - if isinstance(el, list): - if el[0] == "CosimDefinition": - source_prop_dict[el[0]] = None - elif el[0] == "FreqDependentSourceData": - source_prop_dict["frequencies"] = None - source_prop_dict["vmag"] = None - source_prop_dict["vang"] = None - source_prop_dict["vreal"] = None - source_prop_dict["vimag"] = None - source_prop_dict["fds_filename"] = None - source_prop_dict["magnitude_angle"] = True - source_prop_dict["FreqDependentSourceData"] = "" - - elif el[0] != "ModelName" and el[0] != "LabelID": - source_prop_dict[el[0]] = el[3] - - return source_prop_dict - - @pyaedt_function_handler() - def _update_command(self, name, source_prop_dict, source_type, fds_filename=None): - command_template = SourceKeys.SourceTemplates[source_type] - commands = copy.deepcopy(command_template) - commands[0] = "NAME:" + name - commands[10] = source_prop_dict["Netlist"] - if fds_filename: - commands[14] = fds_filename - cont = 0 - props = [value for value in commands if type(value) == list] - for command in props[0]: - if isinstance(command, list) and command[0] in source_prop_dict.keys() and command[0] != "CosimDefinition": - if command[0] == "Netlist": - props[0].pop(cont) - elif command[0] == "file" and source_prop_dict[command[0]]: - props[0][cont][3] = source_prop_dict[command[0]] - props[0][cont][4] = source_prop_dict[command[0]] - elif command[0] == "FreqDependentSourceData" and fds_filename: - props[0][cont][3] = fds_filename - props[0][cont][4] = fds_filename - else: - props[0][cont][3] = source_prop_dict[command[0]] - cont += 1 - - return commands - - @pyaedt_function_handler() - def _source_type_by_key(self): - for source_name in SourceKeys.SourceNames: - template = SourceKeys.SourceProps[source_name] - if list(self._props.keys()) == template: - return source_name - return None - - @pyaedt_function_handler() - def update(self, original_name=None, new_source=None): - """Update the source in AEDT. - - Parameters - ---------- - original_name : str, optional - Original name of the source. The default value is ``None``. - new_source : str, optional - New name of the source. The default value is ``None``. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - - arg0 = ["NAME:Data"] - if self.source_type != "VoltageFrequencyDependent": - fds_filename = None - else: - fds_filename = self._props["FreqDependentSourceData"] - - for source in self._app.sources: - if "FreqDependentSourceData" in self._app.sources[source]._props.keys(): - fds_filename_source = self._app.sources[source]._props["FreqDependentSourceData"] - else: - fds_filename_source = None - if source == self.name: - arg0.append(list(self._update_command(source, self._props, self.source_type, fds_filename))) - elif source != self.name and original_name == source: - arg0.append( - list( - self._update_command( - self.name, self._props, self._app.sources[source].source_type, fds_filename - ) - ) - ) - else: - arg0.append( - list( - self._update_command( - source, - self._app.sources[source]._props, - self._app.sources[source].source_type, - fds_filename_source, - ) - ) - ) - - if new_source and new_source not in self._app.sources: - arg0.append(list(self._update_command(self.name, self._props, self.source_type, fds_filename))) - - arg1 = ["NAME:NexximSources", ["NAME:NexximSources", arg0]] - arg2 = ["NAME:ComponentConfigurationData"] - - # Check Ports with Sources - arg3 = ["NAME:EnabledPorts"] - for source_name in self._app.sources: - excitation_source = [] - for port in self._app.excitations: - if source_name in self._app.excitation_objects[port]._props["EnabledPorts"]: - excitation_source.append(port) - arg3.append(source_name + ":=") - arg3.append(excitation_source) - - if new_source and new_source not in self._app.sources: - arg3.append(new_source + ":=") - arg3.append([]) - - arg4 = ["NAME:EnabledMultipleComponents"] - for source_name in self._app.sources: - arg4.append(source_name + ":=") - arg4.append([]) - - if new_source and new_source not in self._app.sources: - arg4.append(new_source + ":=") - arg4.append([]) - - arg5 = ["NAME:EnabledAnalyses"] - for source_name in self._app.sources: - arg6 = ["NAME:" + source_name] - for port in self._app.excitations: - if source_name in self._app.excitation_objects[port]._props["EnabledAnalyses"]: - arg6.append(port + ":=") - arg6.append(self._app.excitation_objects[port]._props["EnabledAnalyses"][source_name]) - else: - arg6.append(port + ":=") - arg6.append([]) - arg5.append(arg6) - - if new_source and new_source not in self._app.sources: - arg6 = ["NAME:" + new_source] - for port in self._app.excitations: - arg6.append(port + ":=") - arg6.append([]) - arg5.append(arg6) - - arg7 = ["NAME:ComponentConfigurationData", arg3, arg4, arg5] - arg2.append(arg7) - - self._app.odesign.UpdateSources(arg1, arg2) - return True - - @pyaedt_function_handler() - def delete(self): - """Delete the source in AEDT. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - self._app.modeler._odesign.DeleteSource(self.name) - for port in self._app.excitations: - if self.name in self._app.excitation_objects[port].props["EnabledPorts"]: - self._app.excitation_objects[port].props["EnabledPorts"].remove(self.name) - if self.name in self._app.excitation_objects[port].props["EnabledAnalyses"]: - del self._app.excitation_objects[port].props["EnabledAnalyses"][self.name] - return True - - @pyaedt_function_handler() - def create(self): - """Create a new source in AEDT. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - self.update(original_name=None, new_source=self.name) - return True - - -class PowerSinSource(Sources, object): - """Power Sinusoidal Class.""" - - def __init__(self, app, name, source_type=None): - Sources.__init__(self, app, name, source_type) - - @property - def _child(self): - return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) - - @property - def ac_magnitude(self): - """AC magnitude value. - - Returns - ------- - str - """ - return self._props["ACMAG"] - - @ac_magnitude.setter - def ac_magnitude(self, value): - self._props["ACMAG"] = value - self._child.SetPropValue("ACMAG", value) - - @property - def ac_phase(self): - """AC phase value. - - Returns - ------- - str - """ - return self._props["ACPHASE"] - - @ac_phase.setter - def ac_phase(self, value): - self._props["ACPHASE"] = value - self._child.SetPropValue("ACPHASE", value) - - @property - def dc_magnitude(self): - """DC voltage value. - - Returns - ------- - str - """ - return self._props["DC"] - - @dc_magnitude.setter - def dc_magnitude(self, value): - self._props["DC"] = value - self._child.SetPropValue("DC", value) - - @property - def power_offset(self): - """Power offset from zero watts. - - Returns - ------- - str - """ - return self._props["VO"] - - @power_offset.setter - def power_offset(self, value): - self._props["VO"] = value - self._child.SetPropValue("VO", value) - - @property - def power_magnitude(self): - """Available power of the source above power offset. - - Returns - ------- - str - """ - return self._props["POWER"] - - @power_magnitude.setter - def power_magnitude(self, value): - self._props["POWER"] = value - self._child.SetPropValue("POWER", value) - - @property - def frequency(self): - """Frequency. - - Returns - ------- - str - """ - return self._props["FREQ"] - - @frequency.setter - def frequency(self, value): - self._props["FREQ"] = value - self._child.SetPropValue("FREQ", value) - - @property - def delay(self): - """Delay to start of sine wave. - - Returns - ------- - str - """ - return self._props["TD"] - - @delay.setter - def delay(self, value): - self._props["TD"] = value - self._child.SetPropValue("TD", value) - - @property - def damping_factor(self): - """Damping factor. - - Returns - ------- - str - """ - return self._props["ALPHA"] - - @damping_factor.setter - def damping_factor(self, value): - self._props["ALPHA"] = value - self._child.SetPropValue("ALPHA", value) - - @property - def phase_delay(self): - """Phase delay. - - Returns - ------- - str - """ - return self._props["THETA"] - - @phase_delay.setter - def phase_delay(self, value): - self._props["THETA"] = value - self._child.SetPropValue("THETA", value) - - @property - def tone(self): - """Frequency to use for harmonic balance. - - Returns - ------- - str - """ - return self._props["TONE"] - - @tone.setter - def tone(self, value): - self._props["TONE"] = value - self._child.SetPropValue("TONE", value) - - -class PowerIQSource(Sources, object): - """Power IQ Class.""" - - def __init__(self, app, name, source_type=None): - Sources.__init__(self, app, name, source_type) - - @property - def _child(self): - return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) - - @property - def carrier_frequency(self): - """Carrier frequency value. - - Returns - ------- - str - """ - return self._props["FC"] - - @carrier_frequency.setter - def carrier_frequency(self, value): - self._props["FC"] = value - self._child.SetPropValue("FC", value) - - @property - def sampling_time(self): - """Sampling time value. - - Returns - ------- - str - """ - return self._props["TS"] - - @sampling_time.setter - def sampling_time(self, value): - self._props["TS"] = value - self._child.SetPropValue("TS", value) - - @property - def dc_magnitude(self): - """DC voltage value. - - Returns - ------- - str - """ - return self._props["DC"] - - @dc_magnitude.setter - def dc_magnitude(self, value): - self._props["DC"] = value - self._child.SetPropValue("DC", value) - - @property - def repeat_from(self): - """Repeat from time. - - Returns - ------- - str - """ - return self._props["R"] - - @repeat_from.setter - def repeat_from(self, value): - self._props["R"] = value - self._child.SetPropValue("R", value) - - @property - def delay(self): - """Delay to start of sine wave. - - Returns - ------- - str - """ - return self._props["TD"] - - @delay.setter - def delay(self, value): - self._props["TD"] = value - self._child.SetPropValue("TD", value) - - @property - def carrier_amplitude_voltage(self): - """Carrier amplitude value, voltage-based. - - Returns - ------- - str - """ - return self._props["V"] - - @carrier_amplitude_voltage.setter - def carrier_amplitude_voltage(self, value): - self._props["V"] = value - self._child.SetPropValue("V", value) - - @property - def carrier_amplitude_power(self): - """Carrier amplitude value, power-based. - - Returns - ------- - str - """ - return self._props["VA"] - - @carrier_amplitude_power.setter - def carrier_amplitude_power(self, value): - self._props["VA"] = value - self._child.SetPropValue("VA", value) - - @property - def carrier_offset(self): - """Carrier offset. - - Returns - ------- - str - """ - return self._props["VO"] - - @carrier_offset.setter - def carrier_offset(self, value): - self._props["VO"] = value - self._child.SetPropValue("VO", value) - - @property - def real_impedance(self): - """Real carrier impedance. - - Returns - ------- - str - """ - return self._props["RZ"] - - @real_impedance.setter - def real_impedance(self, value): - self._props["RZ"] = value - self._child.SetPropValue("RZ", value) - - @property - def imaginary_impedance(self): - """Imaginary carrier impedance. - - Returns - ------- - str - """ - return self._props["IZ"] - - @imaginary_impedance.setter - def imaginary_impedance(self, value): - self._props["IZ"] = value - self._child.SetPropValue("IZ", value) - - @property - def damping_factor(self): - """Damping factor. - - Returns - ------- - str - """ - return self._props["ALPHA"] - - @damping_factor.setter - def damping_factor(self, value): - self._props["ALPHA"] = value - self._child.SetPropValue("ALPHA", value) - - @property - def phase_delay(self): - """Phase delay. - - Returns - ------- - str - """ - return self._props["THETA"] - - @phase_delay.setter - def phase_delay(self, value): - self._props["THETA"] = value - self._child.SetPropValue("THETA", value) - - @property - def tone(self): - """Frequency to use for harmonic balance. - - Returns - ------- - str - """ - return self._props["TONE"] - - @tone.setter - def tone(self, value): - self._props["TONE"] = value - self._child.SetPropValue("TONE", value) - - @property - def i_q_values(self): - """I and Q value at each timepoint. - - Returns - ------- - str - """ - i_q = [] - for cont in range(1, 20): - i_q.append( - [self._props["time" + str(cont)], self._props["ival" + str(cont)], self._props["qval" + str(cont)]] - ) - return i_q - - @i_q_values.setter - def i_q_values(self, value): - cont = 0 - for point in value: - self._props["time" + str(cont + 1)] = point[0] - self._child.SetPropValue("time" + str(cont + 1), point[0]) - self._props["ival" + str(cont + 1)] = point[1] - self._child.SetPropValue("ival" + str(cont + 1), point[1]) - self._props["qval" + str(cont + 1)] = point[2] - self._child.SetPropValue("qval" + str(cont + 1), point[2]) - cont += 1 - - @property - def file( - self, - ): - """File path with I and Q values. - - Returns - ------- - str - """ - return self._props["file"] - - @file.setter - def file(self, value): - self._props["file"] = value - self.update() - - -class VoltageFrequencyDependentSource(Sources, object): - """Voltage Frequency Dependent Class.""" - - def __init__(self, app, name, source_type=None): - Sources.__init__(self, app, name, source_type) - - @property - def _child(self): - return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) - - @property - def frequencies(self): - """List of frequencies in ``Hz``. - - Returns - ------- - list - """ - return self._props["frequencies"] - - @frequencies.setter - def frequencies(self, value): - self._props["frequencies"] = [float(i) for i in value] - self._update_prop() - - @property - def vmag(self): - """List of magnitudes in ``V``. - - Returns - ------- - list - """ - return self._props["vmag"] - - @vmag.setter - def vmag(self, value): - self._props["vmag"] = [float(i) for i in value] - self._update_prop() - - @property - def vang(self): - """List of angles in ``rad``. - - Returns - ------- - list - """ - return self._props["vang"] - - @vang.setter - def vang(self, value): - self._props["vang"] = [float(i) for i in value] - self._update_prop() - - @property - def vreal(self): - """List of real values in ``V``. - - Returns - ------- - list - """ - return self._props["vreal"] - - @vreal.setter - def vreal(self, value): - self._props["vreal"] = [float(i) for i in value] - self._update_prop() - - @property - def vimag(self): - """List of imaginary values in ``V``. - - Returns - ------- - list - """ - return self._props["vimag"] - - @vimag.setter - def vimag(self, value): - self._props["vimag"] = [float(i) for i in value] - self._update_prop() - - @property - def magnitude_angle(self): - """Enable magnitude and angle data. - - Returns - ------- - bool - """ - return self._props["magnitude_angle"] - - @magnitude_angle.setter - def magnitude_angle(self, value): - self._props["magnitude_angle"] = value - self._update_prop() - - @property - def fds_filename(self): - """FDS file path. - - Returns - ------- - bool - """ - return self._props["fds_filename"] - - @fds_filename.setter - def fds_filename(self, name): - if not name: - self._props["fds_filename"] = None - self._update_prop() - else: - self._props["fds_filename"] = name - self._props["FreqDependentSourceData"] = "voltage_source_file=" + name - self.update() - - @pyaedt_function_handler() - def _update_prop(self): - if ( - self._props["vmag"] - and self._props["vang"] - and self._props["frequencies"] - and self._props["magnitude_angle"] - and not self._props["fds_filename"] - ): - if len(self._props["vmag"]) == len(self._props["vang"]) == len(self._props["frequencies"]): - self._props["FreqDependentSourceData"] = ( - "freqs=" - + str(self._props["frequencies"]).replace(",", "") - + " vmag=" - + str(self._props["vmag"]).replace(",", "") - + " vang=" - + str(self._props["vang"]).replace(",", "") - ) - self.update() - elif ( - self._props["vreal"] - and self._props["vimag"] - and self._props["frequencies"] - and not self._props["magnitude_angle"] - and not self._props["fds_filename"] - ): - if len(self._props["vreal"]) == len(self._props["vimag"]) == len(self._props["frequencies"]): - self._props["FreqDependentSourceData"] = ( - "freqs=" - + str(self._props["frequencies"]).replace(",", "") - + " vreal=" - + str(self._props["vreal"]).replace(",", "") - + " vimag=" - + str(self._props["vimag"]).replace(",", "") - ) - self.update() - else: - self._props["FreqDependentSourceData"] = "" - self.update() - return True - - -class VoltageDCSource(Sources, object): - """Power Sinusoidal Class.""" - - def __init__(self, app, name, source_type=None): - Sources.__init__(self, app, name, source_type) - - @property - def _child(self): - return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) - - @property - def ac_magnitude(self): - """AC magnitude value. - - Returns - ------- - str - """ - return self._props["ACMAG"] - - @ac_magnitude.setter - def ac_magnitude(self, value): - self._props["ACMAG"] = value - self._child.SetPropValue("ACMAG", value) - - @property - def ac_phase(self): - """AC phase value. - - Returns - ------- - str - """ - return self._props["ACPHASE"] - - @ac_phase.setter - def ac_phase(self, value): - self._props["ACPHASE"] = value - self._child.SetPropValue("ACPHASE", value) - - @property - def dc_magnitude(self): - """DC voltage value. - - Returns - ------- - str - """ - return self._props["DC"] - - @dc_magnitude.setter - def dc_magnitude(self, value): - self._props["DC"] = value - self._child.SetPropValue("DC", value) - - -class VoltageSinSource(Sources, object): - """Power Sinusoidal Class.""" - - def __init__(self, app, name, source_type=None): - Sources.__init__(self, app, name, source_type) - - @property - def _child(self): - return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) - - @property - def ac_magnitude(self): - """AC magnitude value. - - Returns - ------- - str - """ - return self._props["ACMAG"] - - @ac_magnitude.setter - def ac_magnitude(self, value): - self._props["ACMAG"] = value - self._child.SetPropValue("ACMAG", value) - - @property - def ac_phase(self): - """AC phase value. - - Returns - ------- - str - """ - return self._props["ACPHASE"] - - @ac_phase.setter - def ac_phase(self, value): - self._props["ACPHASE"] = value - self._child.SetPropValue("ACPHASE", value) - - @property - def dc_magnitude(self): - """DC voltage value. - - Returns - ------- - str - """ - return self._props["DC"] - - @dc_magnitude.setter - def dc_magnitude(self, value): - self._props["DC"] = value - self._child.SetPropValue("DC", value) - - @property - def voltage_amplitude(self): - """Voltage amplitude. - - Returns - ------- - str - """ - return self._props["VA"] - - @voltage_amplitude.setter - def voltage_amplitude(self, value): - self._props["VA"] = value - self._child.SetPropValue("VA", value) - - @property - def voltage_offset(self): - """Voltage offset from zero watts. - - Returns - ------- - str - """ - return self._props["VO"] - - @voltage_offset.setter - def voltage_offset(self, value): - self._props["VO"] = value - self._child.SetPropValue("VO", value) - - @property - def frequency(self): - """Frequency. - - Returns - ------- - str - """ - return self._props["FREQ"] - - @frequency.setter - def frequency(self, value): - self._props["FREQ"] = value - self._child.SetPropValue("FREQ", value) - - @property - def delay(self): - """Delay to start of sine wave. - - Returns - ------- - str - """ - return self._props["TD"] - - @delay.setter - def delay(self, value): - self._props["TD"] = value - self._child.SetPropValue("TD", value) - - @property - def damping_factor(self): - """Damping factor. - - Returns - ------- - str - """ - return self._props["ALPHA"] - - @damping_factor.setter - def damping_factor(self, value): - self._props["ALPHA"] = value - self._child.SetPropValue("ALPHA", value) - - @property - def phase_delay(self): - """Phase delay. - - Returns - ------- - str - """ - return self._props["THETA"] - - @phase_delay.setter - def phase_delay(self, value): - self._props["THETA"] = value - self._child.SetPropValue("THETA", value) - - @property - def tone(self): - """Frequency to use for harmonic balance. - - Returns - ------- - str - """ - return self._props["TONE"] - - @tone.setter - def tone(self, value): - self._props["TONE"] = value - self._child.SetPropValue("TONE", value) - - -class CurrentSinSource(Sources, object): - """Current Sinusoidal Class.""" - - def __init__(self, app, name, source_type=None): - Sources.__init__(self, app, name, source_type) - - @property - def _child(self): - return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) - - @property - def ac_magnitude(self): - """AC magnitude value. - - Returns - ------- - str - """ - return self._props["ACMAG"] - - @ac_magnitude.setter - def ac_magnitude(self, value): - self._props["ACMAG"] = value - self._child.SetPropValue("ACMAG", value) - - @property - def ac_phase(self): - """AC phase value. - - Returns - ------- - str - """ - return self._props["ACPHASE"] - - @ac_phase.setter - def ac_phase(self, value): - self._props["ACPHASE"] = value - self._child.SetPropValue("ACPHASE", value) - - @property - def dc_magnitude(self): - """DC current value. - - Returns - ------- - str - """ - return self._props["DC"] - - @dc_magnitude.setter - def dc_magnitude(self, value): - self._props["DC"] = value - self._child.SetPropValue("DC", value) - - @property - def current_amplitude(self): - """Current amplitude. - - Returns - ------- - str - """ - return self._props["VA"] - - @current_amplitude.setter - def current_amplitude(self, value): - self._props["VA"] = value - self._child.SetPropValue("VA", value) - - @property - def current_offset(self): - """Current offset. - - Returns - ------- - str - """ - return self._props["VO"] - - @current_offset.setter - def current_offset(self, value): - self._props["VO"] = value - self._child.SetPropValue("VO", value) - - @property - def frequency(self): - """Frequency. - - Returns - ------- - str - """ - return self._props["FREQ"] - - @frequency.setter - def frequency(self, value): - self._props["FREQ"] = value - self._child.SetPropValue("FREQ", value) - - @property - def delay(self): - """Delay to start of sine wave. - - Returns - ------- - str - """ - return self._props["TD"] - - @delay.setter - def delay(self, value): - self._props["TD"] = value - self._child.SetPropValue("TD", value) - - @property - def damping_factor(self): - """Damping factor. - - Returns - ------- - str - """ - return self._props["ALPHA"] - - @damping_factor.setter - def damping_factor(self, value): - self._props["ALPHA"] = value - self._child.SetPropValue("ALPHA", value) - - @property - def phase_delay(self): - """Phase delay. - - Returns - ------- - str - """ - return self._props["THETA"] - - @phase_delay.setter - def phase_delay(self, value): - self._props["THETA"] = value - self._child.SetPropValue("THETA", value) - - @property - def multiplier(self): - """Multiplier for simulating multiple parallel current sources. - - Returns - ------- - str - """ - return self._props["M"] - - @multiplier.setter - def multiplier(self, value): - self._props["M"] = value - self._child.SetPropValue("M", value) - - @property - def tone(self): - """Frequency to use for harmonic balance. - - Returns - ------- - str - """ - return self._props["TONE"] - - @tone.setter - def tone(self, value): - self._props["TONE"] = value - self._child.SetPropValue("TONE", value) - - -class Excitations(object): - """Manages Excitations in Circuit Projects. - - Examples - -------- - - """ - - def __init__(self, app, name): - self._app = app - self._name = name - for comp in self._app.modeler.schematic.components: - if ( - "PortName" in self._app.modeler.schematic.components[comp].parameters.keys() - and self._app.modeler.schematic.components[comp].parameters["PortName"] == self.name - ): - self.schematic_id = comp - self.id = self._app.modeler.schematic.components[comp].id - self._angle = self._app.modeler.schematic.components[comp].angle - self.levels = self._app.modeler.schematic.components[comp].levels - self._location = self._app.modeler.schematic.components[comp].location - self._mirror = self._app.modeler.schematic.components[comp].mirror - self.pins = self._app.modeler.schematic.components[comp].pins - self._use_symbol_color = self._app.modeler.schematic.components[comp].usesymbolcolor - break - self._props = self._excitation_props(name) - self._auto_update = True - - @property - def name(self): - """Excitation name. - - Returns - ------- - str - """ - return self._name - - @name.setter - def name(self, port_name): - if port_name not in self._app.excitations: - if port_name != self._name: - # Take previous properties - self._app.odesign.RenamePort(self._name, port_name) - self._name = port_name - self._app.modeler.schematic.components[self.schematic_id].name = "IPort@" + port_name - self.pins[0].name = "IPort@" + port_name + ";" + str(self.schematic_id) - else: - self._logger.warning("Name %s already assigned in the design", port_name) - - @property - def angle(self): - """Symbol angle. - - Returns - ------- - float - """ - return self._angle - - @angle.setter - def angle(self, angle): - self._app.modeler.schematic.components[self.schematic_id].angle = angle - - @property - def mirror(self): - """Enable port mirror. - - Returns - ------- - bool - """ - return self._mirror - - @mirror.setter - def mirror(self, mirror_value=True): - self._app.modeler.schematic.components[self.schematic_id].mirror = mirror_value - self._mirror = mirror_value - - @property - def location(self): - """Port location. - - Returns - ------- - list - """ - return self._location - - @location.setter - def location(self, location_xy): - # The command must be called two times. - self._app.modeler.schematic.components[self.schematic_id].location = location_xy - self._app.modeler.schematic.components[self.schematic_id].location = location_xy - self._location = location_xy - - @property - def use_symbol_color(self): - """Use symbol color. - - Returns - ------- - list - """ - return self._use_symbol_color - - @use_symbol_color.setter - def use_symbol_color(self, use_color=True): - self._app.modeler.schematic.components[self.schematic_id].usesymbolcolor = use_color - self._app.modeler.schematic.components[self.schematic_id].set_use_symbol_color(use_color) - self._use_symbol_color = use_color - - @property - def impedance(self): - """Port termination. - - Returns - ------- - list - """ - return [self._props["rz"], self._props["iz"]] - - @impedance.setter - def impedance(self, termination=None): - if termination and len(termination) == 2: - self._app.modeler.schematic.components[self.schematic_id].change_property( - ["NAME:rz", "Value:=", termination[0]] - ) - self._app.modeler.schematic.components[self.schematic_id].change_property( - ["NAME:iz", "Value:=", termination[1]] - ) - self._props["rz"] = termination[0] - self._props["iz"] = termination[1] - - @property - def enable_noise(self): - """Enable noise. - - Returns - ------- - bool - """ - - return self._props["EnableNoise"] - - @enable_noise.setter - def enable_noise(self, enable=False): - self._app.modeler.schematic.components[self.schematic_id].change_property( - ["NAME:EnableNoise", "Value:=", enable] - ) - self._props["EnableNoise"] = enable - - @property - def noise_temperature(self): - """Enable noise. - - Returns - ------- - str - """ - - return self._props["noisetemp"] - - @noise_temperature.setter - def noise_temperature(self, noise=None): - if noise: - self._app.modeler.schematic.components[self.schematic_id].change_property( - ["NAME:noisetemp", "Value:=", noise] - ) - self._props["noisetemp"] = noise - - @property - def microwave_symbol(self): - """Enable microwave symbol. - - Returns - ------- - bool - """ - if self._props["SymbolType"] == 1: - return True - else: - return False - - @microwave_symbol.setter - def microwave_symbol(self, enable=False): - if enable: - self._props["SymbolType"] = 1 - else: - self._props["SymbolType"] = 0 - self.update() - - @property - def reference_node(self): - """Reference node. - - Returns - ------- - str - """ - if self._props["RefNode"] == "Z": - return "Ground" - return self._props["RefNode"] - - @reference_node.setter - def reference_node(self, ref_node=None): - if ref_node: - self._logger.warning("Set reference node only working with gRPC") - if ref_node == "Ground": - ref_node = "Z" - self._props["RefNode"] = ref_node - self.update() - - @property - def enabled_sources(self): - """Enabled sources. - - Returns - ------- - list - """ - return self._props["EnabledPorts"] - - @enabled_sources.setter - def enabled_sources(self, sources=None): - if sources: - self._props["EnabledPorts"] = sources - self.update() - - @property - def enabled_analyses(self): - """Enabled analyses. - - Returns - ------- - dict - """ - return self._props["EnabledAnalyses"] - - @enabled_analyses.setter - def enabled_analyses(self, analyses=None): - if analyses: - self._props["EnabledAnalyses"] = analyses - self.update() - - @pyaedt_function_handler() - def _excitation_props(self, port): - excitation_prop_dict = {} - for comp in self._app.modeler.schematic.components: - if ( - "PortName" in self._app.modeler.schematic.components[comp].parameters.keys() - and self._app.modeler.schematic.components[comp].parameters["PortName"] == port - ): - excitation_prop_dict["rz"] = "50ohm" - excitation_prop_dict["iz"] = "0ohm" - excitation_prop_dict["term"] = None - excitation_prop_dict["TerminationData"] = None - excitation_prop_dict["RefNode"] = "Z" - excitation_prop_dict["EnableNoise"] = False - excitation_prop_dict["noisetemp"] = "16.85cel" - - if "RefNode" in self._app.modeler.schematic.components[comp].parameters: - excitation_prop_dict["RefNode"] = self._app.modeler.schematic.components[comp].parameters["RefNode"] - if "term" in self._app.modeler.schematic.components[comp].parameters: - excitation_prop_dict["term"] = self._app.modeler.schematic.components[comp].parameters["term"] - excitation_prop_dict["TerminationData"] = self._app.modeler.schematic.components[comp].parameters[ - "TerminationData" - ] - else: - if "rz" in self._app.modeler.schematic.components[comp].parameters: - excitation_prop_dict["rz"] = self._app.modeler.schematic.components[comp].parameters["rz"] - excitation_prop_dict["iz"] = self._app.modeler.schematic.components[comp].parameters["iz"] - - if "EnableNoise" in self._app.modeler.schematic.components[comp].parameters: - if self._app.modeler.schematic.components[comp].parameters["EnableNoise"] == "true": - excitation_prop_dict["EnableNoise"] = True - else: - excitation_prop_dict["EnableNoise"] = False - - excitation_prop_dict["noisetemp"] = self._app.modeler.schematic.components[comp].parameters[ - "noisetemp" - ] - - if not self._app.design_properties or not self._app.design_properties["NexximPorts"]["Data"]: - excitation_prop_dict["SymbolType"] = 0 - else: - excitation_prop_dict["SymbolType"] = self._app.design_properties["NexximPorts"]["Data"][port][ - "SymbolType" - ] - - if "pnum" in self._app.modeler.schematic.components[comp].parameters: - excitation_prop_dict["pnum"] = self._app.modeler.schematic.components[comp].parameters["pnum"] - else: - excitation_prop_dict["pnum"] = None - source_port = [] - if not self._app.design_properties: - enabled_ports = None - else: - enabled_ports = self._app.design_properties["ComponentConfigurationData"]["EnabledPorts"] - if isinstance(enabled_ports, dict): - for source in enabled_ports: - if enabled_ports[source] and port in enabled_ports[source]: - source_port.append(source) - excitation_prop_dict["EnabledPorts"] = source_port - - components_port = [] - if not self._app.design_properties: - multiple = None - else: - multiple = self._app.design_properties["ComponentConfigurationData"]["EnabledMultipleComponents"] - if isinstance(multiple, dict): - for source in multiple: - if multiple[source] and port in multiple[source]: - components_port.append(source) - excitation_prop_dict["EnabledMultipleComponents"] = components_port - - port_analyses = {} - if not self._app.design_properties: - enabled_analyses = None - else: - enabled_analyses = self._app.design_properties["ComponentConfigurationData"]["EnabledAnalyses"] - if isinstance(enabled_analyses, dict): - for source in enabled_analyses: - if ( - enabled_analyses[source] - and port in enabled_analyses[source] - and source in excitation_prop_dict["EnabledPorts"] - ): - port_analyses[source] = enabled_analyses[source][port] - excitation_prop_dict["EnabledAnalyses"] = port_analyses - return excitation_prop_dict - - @pyaedt_function_handler() - def update(self): - """Update the excitation in AEDT. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - - # self._logger.warning("Property port update only working with GRPC") - - if self._props["RefNode"] == "Ground": - self._props["RefNode"] = "Z" - - arg0 = [ - "NAME:" + self.name, - "IIPortName:=", - self.name, - "SymbolType:=", - self._props["SymbolType"], - "DoPostProcess:=", - False, - ] - - arg1 = ["NAME:ChangedProps"] - arg2 = [] - - # Modify RefNode - if self._props["RefNode"] != "Z": - arg2 = [ - "NAME:NewProps", - ["NAME:RefNode", "PropType:=", "TextProp", "OverridingDef:=", True, "Value:=", self._props["RefNode"]], - ] - - # Modify Termination - if self._props["term"] and self._props["TerminationData"]: - arg2 = [ - "NAME:NewProps", - ["NAME:term", "PropType:=", "TextProp", "OverridingDef:=", True, "Value:=", self._props["term"]], - ] - - for prop in self._props: - skip1 = (prop == "rz" or prop == "iz") and isinstance(self._props["term"], str) - skip2 = prop == "EnabledPorts" or prop == "EnabledMultipleComponents" or prop == "EnabledAnalyses" - skip3 = prop == "SymbolType" - skip4 = prop == "TerminationData" and not isinstance(self._props["term"], str) - if not skip1 and not skip2 and not skip3 and not skip4 and self._props[prop] is not None: - command = ["NAME:" + prop, "Value:=", self._props[prop]] - arg1.append(command) - - arg1 = [["NAME:Properties", arg2, arg1]] - self._app.odesign.ChangePortProperty(self.name, arg0, arg1) - - for source in self._app.sources: - self._app.sources[source].update() - return True - - @pyaedt_function_handler() - def delete(self): - """Delete the port in AEDT. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - self._app.modeler._odesign.DeletePort(self.name) - return True - - @property - def _logger(self): - """Logger.""" - return self._app.logger - - -class NetworkObject(BoundaryObject): - """Manages networks in Icepak projects.""" - - def __init__(self, app, name=None, props=None, create=False): - if not app.design_type == "Icepak": # pragma: no cover - raise NotImplementedError("Networks object works only with Icepak projects ") - if name is None: - self._name = generate_unique_name("Network") - else: - self._name = name - super(NetworkObject, self).__init__(app, self._name, props, "Network", False) - if self.props is None: - self._props = {} - self._nodes = [] - self._links = [] - self._schematic_data = {} - self._update_from_props() - if create: - self.create() - - def _clean_list(self, arg): - new_list = [] - for item in arg: - if isinstance(item, list): - if item[0] == "NAME:PageNet": - page_net_list = [] - for i in item: - if isinstance(i, list): - name = page_net_list[-1] - page_net_list.pop(-1) - for j in i: - page_net_list.append(name) - page_net_list.append(j) - else: - page_net_list.append(i) - new_list.append(page_net_list) - else: - new_list.append(self._clean_list(item)) - else: - new_list.append(item) - return new_list - - @pyaedt_function_handler() - def create(self): - """ - Create network in AEDT. - - Returns - ------- - bool: - True if successful. - """ - if not self.props.get("Faces", None): - self.props["Faces"] = [node.props["FaceID"] for _, node in self.face_nodes.items()] - if not self.props.get("SchematicData", None): - self.props["SchematicData"] = {} - - if self.props.get("Links", None): - self.props["Links"] = {link_name: link_values.props for link_name, link_values in self.links.items()} - else: # pragma : no cover - raise KeyError("Links information is missing.") - if self.props.get("Nodes", None): - self.props["Nodes"] = {node_name: node_values.props for node_name, node_values in self.nodes.items()} - else: # pragma : no cover - raise KeyError("Nodes information is missing.") - - args = self._get_args() - - clean_args = self._clean_list(args) - self._app.oboundary.AssignNetworkBoundary(clean_args) - return True - - @pyaedt_function_handler() - def _update_from_props(self): - nodes = self.props.get("Nodes", None) - if nodes is not None: - nd_name_list = [node.name for node in self._nodes] - for node_name, node_dict in nodes.items(): - if node_name not in nd_name_list: - nd_type = node_dict.get("NodeType", None) - if nd_type == "InternalNode": - self.add_internal_node( - node_name, - node_dict.get("Power", node_dict.get("Power Variation Data", None)), - mass=node_dict.get("Mass", None), - specific_heat=node_dict.get("SpecificHeat", None), - ) - elif nd_type == "BoundaryNode": - self.add_boundary_node( - node_name, - assignment_type=node_dict["ValueType"].replace("Value", ""), - value=node_dict[node_dict["ValueType"].replace("Value", "")], - ) - else: - if ( - node_dict["ThermalResistance"] == "NoResistance" - or node_dict["ThermalResistance"] == "Specified" - ): - node_material, node_thickness = None, None - node_resistance = node_dict["Resistance"] - else: - node_thickness, node_material = node_dict["Thickness"], node_dict["Material"] - node_resistance = None - self.add_face_node( - node_dict["FaceID"], - name=node_name, - thermal_resistance=node_dict["ThermalResistance"], - material=node_material, - thickness=node_thickness, - resistance=node_resistance, - ) - links = self.props.get("Links", None) - if links is not None: - l_name_list = [l.name for l in self._links] - for link_name, link_dict in links.items(): - if link_name not in l_name_list: - self.add_link(link_dict[0], link_dict[1], link_dict[-1], link_name) - - @property - def auto_update(self): - """ - Get if auto-update is enabled. - - Returns - ------- - bool: - Whether auto-update is enabled. - """ - return False - - @auto_update.setter - def auto_update(self, b): - """ - Set auto-update on or off. - - Parameters - ---------- - b : bool - Whether to enable auto-update. - - """ - if b: - self._app.logger.warning( - "Network objects auto_update property is False by default" " and cannot be set to True." - ) - - @property - def links(self): - """ - Get links of the network. - - Returns - ------- - dict: - Links dictionary. - - """ - self._update_from_props() - return {link.name: link for link in self._links} - - @property - def r_links(self): - """ - Get r-links of the network. - - Returns - ------- - dict: - R-links dictionary. - - """ - self._update_from_props() - return {link.name: link for link in self._links if link._link_type[0] == "R-Link"} - - @property - def c_links(self): - """ - Get c-links of the network. - - Returns - ------- - dict: - C-links dictionary. - - """ - self._update_from_props() - return {link.name: link for link in self._links if link._link_type[0] == "C-Link"} - - @property - def nodes(self): - """ - Get nodes of the network. - - Returns - ------- - dict: - Nodes dictionary. - - """ - self._update_from_props() - return {node.name: node for node in self._nodes} - - @property - def face_nodes(self): - """ - Get face nodes of the network. - - Returns - ------- - dict: - Face nodes dictionary. - - """ - self._update_from_props() - return {node.name: node for node in self._nodes if node.node_type == "FaceNode"} - - @property - def faces_ids_in_network(self): - """ - Get ID of faces included in the network. - - Returns - ------- - list: - Face IDs. - - """ - out_arr = [] - for _, node_dict in self.face_nodes.items(): - out_arr.append(node_dict.props["FaceID"]) - return out_arr - - @property - def objects_in_network(self): - """ - Get objects included in the network. - - Returns - ------- - list: - Objects names. - - """ - out_arr = [] - for face_id in self.faces_ids_in_network: - out_arr.append(self._app.oeditor.GetObjectNameByFaceID(face_id)) - return out_arr - - @property - def internal_nodes(self): - """ - Get internal nodes. - - Returns - ------- - dict: - Internal nodes. - - """ - self._update_from_props() - return {node.name: node for node in self._nodes if node.node_type == "InternalNode"} - - @property - def boundary_nodes(self): - """ - Get boundary nodes. - - Returns - ------- - dict: - Boundary nodes. - - """ - self._update_from_props() - return {node.name: node for node in self._nodes if node.node_type == "BoundaryNode"} - - @property - def name(self): - """ - Get network name. - - Returns - ------- - str - Network name. - """ - return self._name - - @name.setter - def name(self, new_network_name): - """ - Set new name of the network. - - Parameters - ---------- - new_network_name : str - New name of the network. - """ - bound_names = [b.name for b in self._app.boundaries] - if self.name in bound_names: - if new_network_name not in bound_names: - if new_network_name != self._name: - self._app._oboundary.RenameBoundary(self._name, new_network_name) - self._name = new_network_name - else: - self._app.logger.warning("Name %s already assigned in the design", new_network_name) - else: - self._name = new_network_name - - @pyaedt_function_handler() - def add_internal_node(self, name, power, mass=None, specific_heat=None): - """Add an internal node to the network. - - Parameters - ---------- - name : str - Name of the node. - power : str or float or dict - String, float, or dictionary containing the value of the assignment. - If a float is passed, the ``"W"`` unit is used. A dictionary can be - passed to use temperature-dependent or transient - assignments. - mass : str or float, optional - Value of the mass assignment. This parameter is relevant only - if the solution is transient. If a float is passed, the ``"Kg"`` unit - is used. The default is ``None``, in which case ``"0.001kg"`` is used. - specific_heat : str or float, optional - Value of the specific heat assignment. This parameter is - relevant only if the solution is transient. If a float is passed, - the ``"J_per_Kelkg"`` unit is used. The default is ``None`, in - which case ``"1000J_per_Kelkg"`` is used. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - Examples - -------- - - >>> import ansys.aedt.core - >>> app = ansys.aedt.core.Icepak() - >>> network = ansys.aedt.core.modules.boundary.Network(app) - >>> network.add_internal_node("TestNode", {"Type": "Transient", - >>> "Function": "Linear", "Values": ["0.01W", "1"]}) - """ - if self._app.solution_type != "SteadyState" and mass is None and specific_heat is None: - self._app.logger.warning("The solution is transient but neither mass nor specific heat is assigned.") - if self._app.solution_type == "SteadyState" and ( - mass is not None or specific_heat is not None - ): # pragma: no cover - self._app.logger.warning( - "Because the solution is steady state, neither mass nor specific heat assignment is relevant." - ) - if isinstance(power, (int, float)): - power = str(power) + "W" - props_dict = {"Power": power} - if mass is not None: - if isinstance(mass, (int, float)): - mass = str(mass) + "kg" - props_dict.update({"Mass": mass}) - if specific_heat is not None: - if isinstance(specific_heat, (int, float)): - specific_heat = str(specific_heat) + "J_per_Kelkg" - props_dict.update({"SpecificHeat": specific_heat}) - new_node = self._Node(name, self._app, node_type="InternalNode", props=props_dict, network=self) - self._nodes.append(new_node) - self._add_to_props(new_node) - return new_node - - @pyaedt_function_handler() - def add_boundary_node(self, name, assignment_type, value): - """ - Add a boundary node to the network. - - Parameters - ---------- - name : str - Name of the node. - assignment_type : str - Type assignment. Options are ``"Power"`` and ``"Temperature"``. - 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"`` - is selected for the type, a dictionary can be passed to use - temperature-dependent or transient assignment. - - Returns - ------- - bool - ``True`` if successful. - - Examples - -------- - - >>> import ansys.aedt.core - >>> app = ansys.aedt.core.Icepak() - >>> network = ansys.aedt.core.modules.boundary.Network(app) - >>> network.add_boundary_node("TestNode", "Temperature", 2) - >>> ds = app.create_dataset1d_design("Test_DataSet",[1, 2, 3],[3, 4, 5]) - >>> network.add_boundary_node("TestNode", "Power", {"Type": "Temp Dep", - >>> "Function": "Piecewise Linear", - >>> "Values": "Test_DataSet"}) - """ - if assignment_type not in ["Power", "Temperature", "PowerValue", "TemperatureValue"]: # pragma: no cover - raise AttributeError('``type`` can be only ``"Power"`` or ``"Temperature"``.') - if isinstance(value, (float, int)): - if assignment_type == "Power" or assignment_type == "PowerValue": - value = str(value) + "W" - else: - value = str(value) + "cel" - if isinstance(value, dict) and ( - assignment_type == "Temperature" or assignment_type == "TemperatureValue" - ): # pragma: no cover - raise AttributeError( - "Temperature-dependent or transient assignment is not supported in a temperature boundary node." - ) - if not assignment_type.endswith("Value"): - assignment_type += "Value" - new_node = self._Node( - name, - self._app, - node_type="BoundaryNode", - props={"ValueType": assignment_type, assignment_type.removesuffix("Value"): value}, - network=self, - ) - self._nodes.append(new_node) - self._add_to_props(new_node) - return new_node - - @pyaedt_function_handler() - def _add_to_props(self, new_node, type_dict="Nodes"): - try: - self.props[type_dict].update({new_node.name: new_node.props}) - except KeyError: - self.props[type_dict] = {new_node.name: new_node.props} - - @pyaedt_function_handler(face_id="assignment") - def add_face_node( - self, assignment, name=None, thermal_resistance="NoResistance", material=None, thickness=None, resistance=None - ): - """ - Create a face node in the network. - - Parameters - ---------- - assignment : int - Face ID. - name : str, optional - Name of the node. Default is ``None``. - thermal_resistance : str - Thermal resistance value and unit. Default is ``"NoResistance"``. - material : str, optional - Material specification (required if ``thermal_resistance="Compute"``). - Default is ``None``. - thickness : str or float, optional - Thickness value and unit (required if ``thermal_resistance="Compute"``). - If a float is passed, ``"mm"`` unit is automatically used. Default is ``None``. - resistance : str or float, optional - Resistance value and unit (required if ``thermal_resistance="Specified"``). - If a float is passed, ``"cel_per_w"`` unit is automatically used. Default is ``None``. - - Returns - ------- - bool - True if successful. - - Examples - -------- - - >>> import ansys.aedt.core - >>> app = ansys.aedt.core.Icepak() - >>> network = ansys.aedt.core.modules.boundary.Network(app) - >>> box = app.modeler.create_box([5, 5, 5],[20, 50, 80]) - >>> faces_ids = [face.id for face in box.faces] - >>> network.add_face_node(faces_ids[0]) - >>> network.add_face_node(faces_ids[1],name="TestNode",thermal_resistance="Compute", - ... material="Al-Extruded",thickness="2mm") - >>> network.add_face_node(faces_ids[2],name="TestNode",thermal_resistance="Specified",resistance=2) - """ - props_dict = {} - props_dict["FaceID"] = assignment - if thermal_resistance is not None: - if thermal_resistance == "Compute": - if resistance is not None: - self._app.logger.info( - '``resistance`` assignment is incompatible with ``thermal_resistance="Compute"``' - "and it is ignored." - ) - if material is not None or thickness is not None: - props_dict["ThermalResistance"] = thermal_resistance - props_dict["Material"] = material - if not isinstance(thickness, str): - thickness = str(thickness) + "mm" - props_dict["Thickness"] = thickness - else: # pragma: no cover - raise AttributeError( - 'If ``thermal_resistance="Compute"`` both ``material`` and ``thickness``' - "arguments must be prescribed." - ) - if thermal_resistance == "Specified": - if material is not None or thickness is not None: - self._app.logger.warning( - "Because ``material`` and ``thickness`` assignments are incompatible with" - '``thermal_resistance="Specified"``, they are ignored.' - ) - if resistance is not None: - props_dict["ThermalResistance"] = thermal_resistance - if not isinstance(resistance, str): - resistance = str(resistance) + "cel_per_w" - props_dict["Resistance"] = resistance - else: # pragma : no cover - raise AttributeError( - 'If ``thermal_resistance="Specified"``, ``resistance`` argument must be prescribed.' - ) - - if name is None: - name = "FaceID" + str(assignment) - new_node = self._Node(name, self._app, node_type="FaceNode", props=props_dict, network=self) - self._nodes.append(new_node) - self._add_to_props(new_node) - return new_node - - @pyaedt_function_handler(nodes_dict="nodes") - def add_nodes_from_dictionaries(self, nodes): - """ - Add nodes to the network from dictionary. - - Parameters - ---------- - nodes : 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: - - - Face nodes must contain the ``"ID"`` key associated with an integer containing the face ID. - Optional keys and values pairs are: - - - ``"ThermalResistance"``: a string specifying the type of thermal resistance. - Options are ``"NoResistance"`` (default), ``"Compute"``, and ``"Specified"``. - - ``"Thickness"``: a string with the thickness value and unit (required if ``"Compute"`` - is selected for ``"ThermalResistance"``). - - ``"Material"``: a string with the name of the material (required if ``"Compute"`` is - selected for ``"ThermalResistance"``). - - ``"Resistance"``: a string with the resistance value and unit (required if - ``"Specified"`` is selected for ``"ThermalResistance"``). - - ``"Name"``: a string with the name of the node. If not - specified, a name is generated automatically. - - - - Internal nodes must contain the following keys and values pairs: - - - ``"Name"``: a string with the node name - - ``"Power"``: a string with the assigned power or a dictionary for transient or - temperature-dependent assignment - Optional keys and values pairs: - - ``"Mass"``: a string with the mass value and unit - - ``"SpecificHeat"``: a string with the specific heat value and unit - - - Boundary nodes must contain the following keys and values pairs: - - - ``"Name"``: a string with the node name - - ``"ValueType"``: a string specifying the type of value (``"Power"`` or - ``"Temperature"``) - Depending on the ``"ValueType"`` choice, one of the following keys and values pairs must - be used: - - ``"Power"``: a string with the power value and unit or a dictionary for transient or - temperature-dependent assignment - - ``"Temperature"``: a string with the temperature value and unit or a dictionary for - transient or temperature-dependent assignment - According to the ``"ValueType"`` choice, ``"Power"`` or ``"Temperature"`` key must be - used. Their associated value a string with the value and unit of the quantity prescribed or - a dictionary for transient or temperature dependent assignment. - - - All the temperature dependent or thermal dictionaries should contain three keys: - ``"Type"``, ``"Function"``, and ``"Values"``. Accepted ``"Type"`` values are: - ``"Temp Dep"`` and ``"Transient"``. Accepted ``"Function"`` are: ``"Linear"``, - ``"Power Law"``, ``"Exponential"``, ``"Sinusoidal"``, ``"Square Wave"``, and - ``"Piecewise Linear"``. ``"Temp Dep"`` only support the latter. ``"Values"`` - contains a list of strings containing the parameters required by the ``"Function"`` - selection (e.g. ``"Linear"`` requires two parameters: the value of the variable at t=0 - and the slope of the line). The parameters required by each ``Function`` option is in - Icepak documentation. The parameters must contain the units where needed. - - Returns - ------- - bool - ``True`` if successful. ``False`` otherwise. - - Examples - -------- - - >>> import ansys.aedt.core - >>> app = ansys.aedt.core.Icepak() - >>> network = ansys.aedt.core.modules.boundary.Network(app) - >>> box = app.modeler.create_box([5, 5, 5],[20, 50, 80]) - >>> faces_ids = [face.id for face in box.faces] - >>> nodes_dict = [ - >>> {"FaceID": faces_ids[0]}, - >>> {"Name": "TestNode", "FaceID": faces_ids[1], - >>> "ThermalResistance": "Compute", "Thickness": "2mm"}, - >>> {"FaceID": faces_ids[2], "ThermalResistance": "Specified", "Resistance": "2cel_per_w"}, - >>> {"Name": "Junction", "Power": "1W"}] - >>> network.add_nodes_from_dictionaries(nodes_dict) - - """ - if isinstance(nodes, dict): - nodes = [nodes] - for node_dict in nodes: - if "FaceID" in node_dict.keys(): - self.add_face_node( - assignment=node_dict["FaceID"], - name=node_dict.get("Name", None), - thermal_resistance=node_dict.get("ThermalResistance", None), - material=node_dict.get("Material", None), - thickness=node_dict.get("Thickness", None), - resistance=node_dict.get("Resistance", None), - ) - elif "ValueType" in node_dict.keys(): - if node_dict["ValueType"].endswith("Value"): - value = node_dict[node_dict["ValueType"].removesuffix("Value")] - else: - value = node_dict[node_dict["ValueType"]] - self.add_boundary_node(name=node_dict["Name"], assignment_type=node_dict["ValueType"], value=value) - else: - self.add_internal_node( - name=node_dict["Name"], - power=node_dict.get("Power", None), - mass=node_dict.get("Mass", None), - specific_heat=node_dict.get("SpecificHeat", None), - ) - return True - - @pyaedt_function_handler() - def add_link(self, node1, node2, value, name=None): - """Create links in the network object. - - Parameters - ---------- - node1 : str or int - String containing one of the node names that the link is connecting or an integer - containing the ID of the face. If an ID is used and the node associated with the - corresponding face is not created yet, it is added automatically. - node2 : str or int - String containing one of the node names that the link is connecting or an integer - containing the ID of the face. If an ID is used and the node associated with the - corresponding face is not created yet, it is added atuomatically. - value : str or float - String containing the value and unit of the connection. If a float is passed, an - R-Link is added to the network and the ``"cel_per_w"`` unit is used. - name : str, optional - Name of the link. The default is ``None``, in which case a name is - automatically generated. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - Examples - -------- - - >>> import ansys.aedt.core - >>> app = ansys.aedt.core.Icepak() - >>> network = ansys.aedt.core.modules.boundary.Network(app) - >>> box = app.modeler.create_box([5, 5, 5],[20, 50, 80]) - >>> faces_ids = [face.id for face in box.faces] - >>> connection = {"Name": "LinkTest", "Connection": [faces_ids[1], faces_ids[0]], "Value": "1cel_per_w"} - >>> network.add_links_from_dictionaries(connection) - - """ - if name is None: - new_name = True - while new_name: - name = generate_unique_name("Link") - if name not in self.links.keys(): - new_name = False - new_link = self._Link(node1, node2, value, name, self) - self._links.append(new_link) - self._add_to_props(new_link, "Links") - return True - - @pyaedt_function_handler() - def add_links_from_dictionaries(self, connections): - """Create links in the network object. - - Parameters - ---------- - connections : dict or list of dict - Dictionary or list of dictionaries containing the links between nodes. Each dictionary - consists of these elements: - - - ``"Link"``: a three-item list consisting of the two nodes that the link is connecting and - the value with unit of the link. The node of the connection can be referred to with the - name (str) or face ID (int). The link type (resistance, heat transfer coefficient, or - mass flow) is determined automatically from the unit. - - ``"Name"`` (optional): a string specifying the name of the link. - - - Returns - ------- - bool - ``True`` if successful. - - Examples - -------- - - >>> import ansys.aedt.core - >>> app = ansys.aedt.core.Icepak() - >>> network = ansys.aedt.core.modules.boundary.Network(app) - >>> box = app.modeler.create_box([5, 5, 5],[20, 50, 80]) - >>> faces_ids = [face.id for face in box.faces] - >>> [network.add_face_node(faces_ids[i]) for i in range(2)] - >>> connection = {"Name": "LinkTest", "Link": [faces_ids[1], faces_ids[0], "1cel_per_w"]} - >>> network.add_links_from_dictionaries(connection) - - """ - if isinstance(connections, dict): - connections = [connections] - for connection in connections: - name = connection.get("Name", None) - try: - self.add_link(connection["Link"][0], connection["Link"][1], connection["Link"][2], name) - except Exception: # pragma : no cover - if name: - self._app.logger.error("Failed to add " + name + " link.") - else: - self._app.logger.error( - "Failed to add link associated with the following dictionary:\n" + str(connection) - ) - return True - - @pyaedt_function_handler() - def update(self): - """Update the network in AEDT. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - if self.name in [b.name for b in self._app.boundaries]: - self.delete() - try: - self.create() - self._app._boundaries[self.name] = self - return True - except Exception: # pragma : no cover - self._app.odesign.Undo() - self._app.logger.error("Update of network object failed.") - return False - else: # pragma : no cover - self._app.logger.warning("Network object not yet created in design.") - return False - - @pyaedt_function_handler() - def update_assignment(self): - """ - Update assignments of the network. - """ - return self.update() - - class _Link: - def __init__(self, node_1, node_2, value, name, network): - self.name = name - if not isinstance(node_1, str): - node_1 = "FaceID" + str(node_1) - if not isinstance(node_2, str): - node_2 = "FaceID" + str(node_2) - if not isinstance(value, str): - value = str(value) + "cel_per_w" - self.node_1 = node_1 - self.node_2 = node_2 - self.value = value - self._network = network - - @property - def _link_type(self): - unit2type_conversion = { - "g_per_s": ["C-Link", "Node1ToNode2"], - "kg_per_s": ["C-Link", "Node1ToNode2"], - "lbm_per_min": ["C-Link", "Node1ToNode2"], - "lbm_per_s": ["C-Link", "Node1ToNode2"], - "Kel_per_W": ["R-Link", "R"], - "cel_per_w": ["R-Link", "R"], - "FahSec_per_btu": ["R-Link", "R"], - "Kels_per_J": ["R-Link", "R"], - "w_per_m2kel": ["R-Link", "HTC"], - "w_per_m2Cel": ["R-Link", "HTC"], - "btu_per_rankHrFt2": ["R-Link", "HTC"], - "btu_per_fahHrFt2": ["R-Link", "HTC"], - "btu_per_rankSecFt2": ["R-Link", "HTC"], - "btu_per_fahSecFt2": ["R-Link", "HTC"], - "w_per_cm2kel": ["R-Link", "HTC"], - } - _, unit = decompose_variable_value(self.value) - return unit2type_conversion[unit] - - @property - def props(self): - """ - Get link properties. - - Returns - ------- - list - First two elements of the list are the node names that the link connects, - the third element is the link type while the fourth contains the value - associated with the link. - """ - return [self.node_1, self.node_2] + self._link_type + [self.value] - - @pyaedt_function_handler() - def delete_link(self): - """ - Delete link from network. - """ - self._network.props["Links"].pop(self.name) - self._network._links.remove(self) - - class _Node: - def __init__(self, name, app, network, node_type=None, props=None): - self.name = name - self._type = node_type - self._app = app - self._props = props - self._node_props() - self._network = network - - @pyaedt_function_handler() - def delete_node(self): - """ - Delete node from network. - """ - self._network.props["Nodes"].pop(self.name) - self._network._nodes.remove(self) - - @property - def node_type(self): - """ - Get node type. - - Returns - ------- - str - Node type. - """ - if self._type is None: # pragma: no cover - if self.props is None: - self._app.logger.error( - "Cannot define node_type. Both its assignment and properties assignment are missing." - ) - return None - else: - type_in_dict = self.props.get("NodeType", None) - if type_in_dict is None: - self._type = "FaceNode" - else: - self._type = type_in_dict - return self._type - - @property - def props(self): - """ - Get properties of the node. - - Returns - ------- - dict - Node properties. - """ - return self._props - - @props.setter - def props(self, props): - """ - Set properties of the node. - - Parameters - ---------- - props : dict - Node properties. - """ - self._props = props - self._node_props() - - def _node_props(self): - face_node_default_dict = { - "FaceID": None, - "ThermalResistance": "NoResistance", - "Thickness": "1mm", - "Material": "Al-Extruded", - "Resistance": "0cel_per_w", - } - boundary_node_default_dict = { - "NodeType": "BoundaryNode", - "ValueType": "PowerValue", - "Power": "0W", - "Temperature": "25cel", - } - internal_node_default_dict = { - "NodeType": "InternalNode", - "Power": "0W", - "Mass": "0.001kg", - "SpecificHeat": "1000J_per_Kelkg", - } - if self.props is None: - if self.node_type == "InternalNode": - self._props = internal_node_default_dict - elif self.node_type == "FaceNode": - self._props = face_node_default_dict - elif self.node_type == "BoundaryNode": - self._props = boundary_node_default_dict - else: - if self.node_type == "InternalNode": - self._props = self._create_node_dict(internal_node_default_dict) - elif self.node_type == "FaceNode": - self._props = self._create_node_dict(face_node_default_dict) - elif self.node_type == "BoundaryNode": - self._props = self._create_node_dict(boundary_node_default_dict) - - @pyaedt_function_handler() - def _create_node_dict(self, default_dict): - node_dict = self.props - node_name = node_dict.get("Name", self.name) - if not node_name: - try: - self.name = "Face" + str(node_dict["FaceID"]) - except KeyError: # pragma: no cover - raise KeyError('"Name" key is needed for "BoundaryNodes" and "InternalNodes" dictionaries.') - else: - self.name = node_name - node_dict.pop("Name", None) - node_args = copy.deepcopy(default_dict) - for k in node_dict.keys(): - val = node_dict[k] - if isinstance(val, dict): # pragma : no cover - val = self._app._parse_variation_data( - k, val["Type"], variation_value=val["Values"], function=val["Function"] - ) - node_args.pop(k) - node_args.update(val) - else: - node_args[k] = val - - return node_args - - -def _create_boundary(bound): - try: - if bound.create(): - bound._app._boundaries[bound.name] = bound - return bound - else: # pragma : no cover - raise Exception - except Exception: # pragma: no cover - return None - - -class BoundaryDictionary: - """ - Handles Icepak transient and temperature-dependent boundary condition assignments. - - Parameters - ---------- - assignment_type : str - Type of assignment represented by the class. Options are `"Temp Dep"`` - and ``"Transient"``. - function_type : str - Variation function to assign. If ``assignment_type=="Temp Dep"``, - the function can only be ``"Piecewise Linear"``. Otherwise, the function can be - ``"Exponential"``, ``"Linear"``, ``"Piecewise Linear"``, ``"Power Law"``, - ``"Sinusoidal"``, and ``"Square Wave"``. - """ - - def __init__(self, assignment_type, function_type): - if assignment_type not in ["Temp Dep", "Transient"]: # pragma : no cover - raise AttributeError(f"The argument {assignment_type} for ``assignment_type`` is not valid.") - if assignment_type == "Temp Dep" and function_type != "Piecewise Linear": # pragma : no cover - raise AttributeError( - "Temperature dependent assignments only support" - ' ``"Piecewise Linear"`` as ``function_type`` argument.' - ) - self.assignment_type = assignment_type - self.function_type = function_type - - @property - def props(self): - """Dictionary that defines all the boundary condition properties.""" - return { - "Type": self.assignment_type, - "Function": self.function_type, - "Values": self._parse_value(), - } - - @abstractmethod - def _parse_value(self): - pass # pragma : no cover - - @pyaedt_function_handler() - def __getitem__(self, k): - return self.props.get(k) - - -class LinearDictionary(BoundaryDictionary): - """ - Manages linear conditions assignments, which are children of the ``BoundaryDictionary`` class. - - This class applies a condition ``y`` dependent on the time ``t``: - ``y=a+b*t`` - - Parameters - ---------- - intercept : str - Value of the assignment condition at the initial time, which - corresponds to the coefficient ``a`` in the formula. - slope : str - Slope of the assignment condition, which - corresponds to the coefficient ``b`` in the formula. - """ - - def __init__(self, intercept, slope): - super().__init__("Transient", "Linear") - self.intercept = intercept - self.slope = slope - - @pyaedt_function_handler() - def _parse_value(self): - return [self.slope, self.intercept] - - -class PowerLawDictionary(BoundaryDictionary): - """ - Manages power law condition assignments, which are children of the ``BoundaryDictionary`` class. - - This class applies a condition ``y`` dependent on the time ``t``: - ``y=a+b*t^c`` - - Parameters - ---------- - intercept : str - Value of the assignment condition at the initial time, which - corresponds to the coefficient ``a`` in the formula. - coefficient : str - Coefficient that multiplies the power term, which - corresponds to the coefficient ``b`` in the formula. - scaling_exponent : str - Exponent of the power term, which - corresponds to the coefficient ``c`` in the formula. - """ - - def __init__(self, intercept, coefficient, scaling_exponent): - super().__init__("Transient", "Power Law") - self.intercept = intercept - self.coefficient = coefficient - self.scaling_exponent = scaling_exponent - - @pyaedt_function_handler() - def _parse_value(self): - return [self.intercept, self.coefficient, self.scaling_exponent] - - -class ExponentialDictionary(BoundaryDictionary): - """ - Manages exponential condition assignments, which are children of the ``BoundaryDictionary`` class. - - This class applies a condition ``y`` dependent on the time ``t``: - ``y=a+b*exp(c*t)`` - - Parameters - ---------- - vertical_offset : str - Vertical offset summed to the exponential law, which - corresponds to the coefficient ``a`` in the formula. - coefficient : str - Coefficient that multiplies the exponential term, which - corresponds to the coefficient ``b`` in the formula. - exponent_coefficient : str - Coefficient in the exponential term, which - corresponds to the coefficient ``c`` in the formula. - """ - - def __init__(self, vertical_offset, coefficient, exponent_coefficient): - super().__init__("Transient", "Exponential") - self.vertical_offset = vertical_offset - self.coefficient = coefficient - self.exponent_coefficient = exponent_coefficient - - @pyaedt_function_handler() - def _parse_value(self): - return [self.vertical_offset, self.coefficient, self.exponent_coefficient] - - -class SinusoidalDictionary(BoundaryDictionary): - """ - Manages sinusoidal condition assignments, which are children of the ``BoundaryDictionary`` class. - - This class applies a condition ``y`` dependent on the time ``t``: - ``y=a+b*sin(2*pi(t-t0)/T)`` - - Parameters - ---------- - vertical_offset : str - Vertical offset summed to the sinusoidal law, which - corresponds to the coefficient ``a`` in the formula. - vertical_scaling : str - Coefficient that multiplies the sinusoidal term, which - corresponds to the coefficient ``b`` in the formula. - period : str - Period of the sinusoid, which - corresponds to the coefficient ``T`` in the formula. - period_offset : str - Offset of the sinusoid, which - corresponds to the coefficient ``t0`` in the formula. - """ - - def __init__(self, vertical_offset, vertical_scaling, period, period_offset): - super().__init__("Transient", "Sinusoidal") - self.vertical_offset = vertical_offset - self.vertical_scaling = vertical_scaling - self.period = period - self.period_offset = period_offset - - @pyaedt_function_handler() - def _parse_value(self): - return [self.vertical_offset, self.vertical_scaling, self.period, self.period_offset] - - -class SquareWaveDictionary(BoundaryDictionary): - """ - Manages square wave condition assignments, which are children of the ``BoundaryDictionary`` class. - - Parameters - ---------- - on_value : str - Maximum value of the square wave. - initial_time_off : str - Time after which the square wave assignment starts. - on_time : str - Time for which the square wave keeps the maximum value during one period. - off_time : str - Time for which the square wave keeps the minimum value during one period. - off_value : str - Minimum value of the square wave. - """ - - def __init__(self, on_value, initial_time_off, on_time, off_time, off_value): - super().__init__("Transient", "Square Wave") - self.on_value = on_value - self.initial_time_off = initial_time_off - self.on_time = on_time - self.off_time = off_time - self.off_value = off_value - - @pyaedt_function_handler() - def _parse_value(self): - return [self.on_value, self.initial_time_off, self.on_time, self.off_time, self.off_value] - - -class PieceWiseLinearDictionary(BoundaryDictionary): - """ - Manages dataset condition assignments, which are children of the ``BoundaryDictionary`` class. - - Parameters - ---------- - assignment_type : str - Type of assignment represented by the class. - Options are ``"Temp Dep"`` and ``"Transient"``. - ds : str - Dataset name to assign. - scale : str - Scaling factor for the y values of the dataset. - """ - - def __init__(self, assignment_type, ds, scale): - super().__init__(assignment_type, "Piecewise Linear") - self.scale = scale - self._assignment_type = assignment_type - self.dataset = ds - - @pyaedt_function_handler() - def _parse_value(self): - return [self.scale, self.dataset.name] - - @property - def dataset_name(self): - """Dataset name that defines the piecewise assignment.""" - return self.dataset.name diff --git a/src/ansys/aedt/core/modules/boundary/__init__.py b/src/ansys/aedt/core/modules/boundary/__init__.py new file mode 100644 index 00000000000..9c4476773da --- /dev/null +++ b/src/ansys/aedt/core/modules/boundary/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. diff --git a/src/ansys/aedt/core/modules/boundary/circuit_boundary.py b/src/ansys/aedt/core/modules/boundary/circuit_boundary.py new file mode 100644 index 00000000000..d01259dc8c1 --- /dev/null +++ b/src/ansys/aedt/core/modules/boundary/circuit_boundary.py @@ -0,0 +1,2630 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import copy +import re + +from ansys.aedt.core.application.variables import decompose_variable_value +from ansys.aedt.core.generic.general_methods import generate_unique_name +from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.modules.boundary.common import BoundaryObject +from ansys.aedt.core.modules.circuit_templates import SourceKeys + + +class Sources(object): + """Manages sources in Circuit projects.""" + + def __init__(self, app, name, source_type=None): + self._app = app + self._name = name + self._props = self._source_props(name, source_type) + self.source_type = source_type + if not source_type: + self.source_type = self._source_type_by_key() + self._auto_update = True + + @property + def name(self): + """Source name. + + Returns + ------- + str + """ + return self._name + + @name.setter + def name(self, source_name): + if source_name not in self._app.source_names: + if source_name != self._name: + original_name = self._name + self._name = source_name + for port in self._app.excitations: + if original_name in self._app.excitation_objects[port].props["EnabledPorts"]: + self._app.excitation_objects[port].props["EnabledPorts"] = [ + w.replace(original_name, source_name) + for w in self._app.excitation_objects[port].props["EnabledPorts"] + ] + if original_name in self._app.excitation_objects[port].props["EnabledAnalyses"]: + self._app.excitation_objects[port].props["EnabledAnalyses"][source_name] = ( + self._app.excitation_objects[port].props["EnabledAnalyses"].pop(original_name) + ) + self.update(original_name) + else: + self._logger.warning("Name %s already assigned in the design", source_name) + + @property + def _logger(self): + """Logger.""" + return self._app.logger + + @pyaedt_function_handler() + def _source_props(self, source, source_type=None): + source_prop_dict = {} + if source in self._app.source_names: + source_aedt_props = self._app.odesign.GetChildObject("Excitations").GetChildObject(source) + for el in source_aedt_props.GetPropNames(): + if el == "CosimDefinition": + source_prop_dict[el] = None + elif el == "FreqDependentSourceData": + data = self._app.design_properties["NexximSources"]["Data"][source]["FDSFileName"] + freqs = re.findall(r"freqs=\[(.*?)\]", data) + magnitude = re.findall(r"magnitude=\[(.*?)\]", data) + angle = re.findall(r"angle=\[(.*?)\]", data) + vreal = re.findall(r"vreal=\[(.*?)\]", data) + vimag = re.findall(r"vimag=\[(.*?)\]", data) + source_file = re.findall("voltage_source_file=", data) + source_prop_dict["frequencies"] = None + source_prop_dict["vmag"] = None + source_prop_dict["vang"] = None + source_prop_dict["vreal"] = None + source_prop_dict["vimag"] = None + source_prop_dict["fds_filename"] = None + source_prop_dict["magnitude_angle"] = False + source_prop_dict["FreqDependentSourceData"] = data + if freqs: + source_prop_dict["frequencies"] = [float(i) for i in freqs[0].split()] + if magnitude: + source_prop_dict["vmag"] = [float(i) for i in magnitude[0].split()] + if angle: + source_prop_dict["vang"] = [float(i) for i in angle[0].split()] + if vreal: + source_prop_dict["vreal"] = [float(i) for i in vreal[0].split()] + if vimag: + source_prop_dict["vimag"] = [float(i) for i in vimag[0].split()] + if source_file: + source_prop_dict["fds_filename"] = data[len(re.findall("voltage_source_file=", data)[0]) :] + else: + if freqs and magnitude and angle: + source_prop_dict["magnitude_angle"] = True + elif freqs and vreal and vimag: + source_prop_dict["magnitude_angle"] = False + + elif el != "Name" and el != "Noise": + source_prop_dict[el] = source_aedt_props.GetPropValue(el) + if not source_prop_dict[el]: + source_prop_dict[el] = "" + else: + if source_type in SourceKeys.SourceNames: + command_template = SourceKeys.SourceTemplates[source_type] + commands = copy.deepcopy(command_template) + props = [value for value in commands if isinstance(value, list)] + for el in props[0]: + if isinstance(el, list): + if el[0] == "CosimDefinition": + source_prop_dict[el[0]] = None + elif el[0] == "FreqDependentSourceData": + source_prop_dict["frequencies"] = None + source_prop_dict["vmag"] = None + source_prop_dict["vang"] = None + source_prop_dict["vreal"] = None + source_prop_dict["vimag"] = None + source_prop_dict["fds_filename"] = None + source_prop_dict["magnitude_angle"] = True + source_prop_dict["FreqDependentSourceData"] = "" + + elif el[0] != "ModelName" and el[0] != "LabelID": + source_prop_dict[el[0]] = el[3] + + return source_prop_dict + + @pyaedt_function_handler() + def _update_command(self, name, source_prop_dict, source_type, fds_filename=None): + command_template = SourceKeys.SourceTemplates[source_type] + commands = copy.deepcopy(command_template) + commands[0] = "NAME:" + name + commands[10] = source_prop_dict["Netlist"] + if fds_filename: + commands[14] = fds_filename + cont = 0 + props = [value for value in commands if isinstance(value, list)] + for command in props[0]: + if isinstance(command, list) and command[0] in source_prop_dict.keys() and command[0] != "CosimDefinition": + if command[0] == "Netlist": + props[0].pop(cont) + elif command[0] == "file" and source_prop_dict[command[0]]: + props[0][cont][3] = source_prop_dict[command[0]] + props[0][cont][4] = source_prop_dict[command[0]] + elif command[0] == "FreqDependentSourceData" and fds_filename: + props[0][cont][3] = fds_filename + props[0][cont][4] = fds_filename + else: + props[0][cont][3] = source_prop_dict[command[0]] + cont += 1 + + return commands + + @pyaedt_function_handler() + def _source_type_by_key(self): + for source_name in SourceKeys.SourceNames: + template = SourceKeys.SourceProps[source_name] + if list(self._props.keys()) == template: + return source_name + return None + + @pyaedt_function_handler() + def update(self, original_name=None, new_source=None): + """Update the source in AEDT. + + Parameters + ---------- + original_name : str, optional + Original name of the source. The default value is ``None``. + new_source : str, optional + New name of the source. The default value is ``None``. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + + arg0 = ["NAME:Data"] + if self.source_type != "VoltageFrequencyDependent": + fds_filename = None + else: + fds_filename = self._props["FreqDependentSourceData"] + + for source in self._app.sources: + if "FreqDependentSourceData" in self._app.sources[source]._props.keys(): + fds_filename_source = self._app.sources[source]._props["FreqDependentSourceData"] + else: + fds_filename_source = None + if source == self.name: + arg0.append(list(self._update_command(source, self._props, self.source_type, fds_filename))) + elif source != self.name and original_name == source: + arg0.append( + list( + self._update_command( + self.name, self._props, self._app.sources[source].source_type, fds_filename + ) + ) + ) + else: + arg0.append( + list( + self._update_command( + source, + self._app.sources[source]._props, + self._app.sources[source].source_type, + fds_filename_source, + ) + ) + ) + + if new_source and new_source not in self._app.sources: + arg0.append(list(self._update_command(self.name, self._props, self.source_type, fds_filename))) + + arg1 = ["NAME:NexximSources", ["NAME:NexximSources", arg0]] + arg2 = ["NAME:ComponentConfigurationData"] + + # Check Ports with Sources + arg3 = ["NAME:EnabledPorts"] + for source_name in self._app.sources: + excitation_source = [] + for port in self._app.excitations: + # self._app.excitation_objects[port]._props + if source_name in self._app.excitation_objects[port]._props["EnabledPorts"]: + excitation_source.append(port) + arg3.append(source_name + ":=") + arg3.append(excitation_source) + + if new_source and new_source not in self._app.sources: + arg3.append(new_source + ":=") + arg3.append([]) + + arg4 = ["NAME:EnabledMultipleComponents"] + for source_name in self._app.sources: + arg4.append(source_name + ":=") + arg4.append([]) + + if new_source and new_source not in self._app.sources: + arg4.append(new_source + ":=") + arg4.append([]) + + arg5 = ["NAME:EnabledAnalyses"] + for source_name in self._app.sources: + arg6 = ["NAME:" + source_name] + for port in self._app.excitations: + if source_name in self._app.excitation_objects[port]._props["EnabledAnalyses"]: + arg6.append(port + ":=") + arg6.append(self._app.excitation_objects[port]._props["EnabledAnalyses"][source_name]) + else: + arg6.append(port + ":=") + arg6.append([]) + arg5.append(arg6) + + if new_source and new_source not in self._app.sources: + arg6 = ["NAME:" + new_source] + for port in self._app.excitations: + arg6.append(port + ":=") + arg6.append([]) + arg5.append(arg6) + + arg7 = ["NAME:ComponentConfigurationData", arg3, arg4, arg5] + arg2.append(arg7) + + self._app.odesign.UpdateSources(arg1, arg2) + return True + + @pyaedt_function_handler() + def delete(self): + """Delete the source in AEDT. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + self._app.modeler._odesign.DeleteSource(self.name) + for port in self._app.excitations: + if self.name in self._app.excitation_objects[port].props["EnabledPorts"]: + self._app.excitation_objects[port].props["EnabledPorts"].remove(self.name) + if self.name in self._app.excitation_objects[port].props["EnabledAnalyses"]: + del self._app.excitation_objects[port].props["EnabledAnalyses"][self.name] + return True + + @pyaedt_function_handler() + def create(self): + """Create a new source in AEDT. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + self.update(original_name=None, new_source=self.name) + return True + + +class PowerSinSource(Sources, object): + """Power Sinusoidal Class.""" + + def __init__(self, app, name, source_type=None): + Sources.__init__(self, app, name, source_type) + + @property + def _child(self): + return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) + + @property + def ac_magnitude(self): + """AC magnitude value. + + Returns + ------- + str + """ + return self._props["ACMAG"] + + @ac_magnitude.setter + def ac_magnitude(self, value): + self._props["ACMAG"] = value + self._child.SetPropValue("ACMAG", value) + + @property + def ac_phase(self): + """AC phase value. + + Returns + ------- + str + """ + return self._props["ACPHASE"] + + @ac_phase.setter + def ac_phase(self, value): + self._props["ACPHASE"] = value + self._child.SetPropValue("ACPHASE", value) + + @property + def dc_magnitude(self): + """DC voltage value. + + Returns + ------- + str + """ + return self._props["DC"] + + @dc_magnitude.setter + def dc_magnitude(self, value): + self._props["DC"] = value + self._child.SetPropValue("DC", value) + + @property + def power_offset(self): + """Power offset from zero watts. + + Returns + ------- + str + """ + return self._props["VO"] + + @power_offset.setter + def power_offset(self, value): + self._props["VO"] = value + self._child.SetPropValue("VO", value) + + @property + def power_magnitude(self): + """Available power of the source above power offset. + + Returns + ------- + str + """ + return self._props["POWER"] + + @power_magnitude.setter + def power_magnitude(self, value): + self._props["POWER"] = value + self._child.SetPropValue("POWER", value) + + @property + def frequency(self): + """Frequency. + + Returns + ------- + str + """ + return self._props["FREQ"] + + @frequency.setter + def frequency(self, value): + self._props["FREQ"] = value + self._child.SetPropValue("FREQ", value) + + @property + def delay(self): + """Delay to start of sine wave. + + Returns + ------- + str + """ + return self._props["TD"] + + @delay.setter + def delay(self, value): + self._props["TD"] = value + self._child.SetPropValue("TD", value) + + @property + def damping_factor(self): + """Damping factor. + + Returns + ------- + str + """ + return self._props["ALPHA"] + + @damping_factor.setter + def damping_factor(self, value): + self._props["ALPHA"] = value + self._child.SetPropValue("ALPHA", value) + + @property + def phase_delay(self): + """Phase delay. + + Returns + ------- + str + """ + return self._props["THETA"] + + @phase_delay.setter + def phase_delay(self, value): + self._props["THETA"] = value + self._child.SetPropValue("THETA", value) + + @property + def tone(self): + """Frequency to use for harmonic balance. + + Returns + ------- + str + """ + return self._props["TONE"] + + @tone.setter + def tone(self, value): + self._props["TONE"] = value + self._child.SetPropValue("TONE", value) + + +class PowerIQSource(Sources, object): + """Power IQ Class.""" + + def __init__(self, app, name, source_type=None): + Sources.__init__(self, app, name, source_type) + + @property + def _child(self): + return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) + + @property + def carrier_frequency(self): + """Carrier frequency value. + + Returns + ------- + str + """ + return self._props["FC"] + + @carrier_frequency.setter + def carrier_frequency(self, value): + self._props["FC"] = value + self._child.SetPropValue("FC", value) + + @property + def sampling_time(self): + """Sampling time value. + + Returns + ------- + str + """ + return self._props["TS"] + + @sampling_time.setter + def sampling_time(self, value): + self._props["TS"] = value + self._child.SetPropValue("TS", value) + + @property + def dc_magnitude(self): + """DC voltage value. + + Returns + ------- + str + """ + return self._props["DC"] + + @dc_magnitude.setter + def dc_magnitude(self, value): + self._props["DC"] = value + self._child.SetPropValue("DC", value) + + @property + def repeat_from(self): + """Repeat from time. + + Returns + ------- + str + """ + return self._props["R"] + + @repeat_from.setter + def repeat_from(self, value): + self._props["R"] = value + self._child.SetPropValue("R", value) + + @property + def delay(self): + """Delay to start of sine wave. + + Returns + ------- + str + """ + return self._props["TD"] + + @delay.setter + def delay(self, value): + self._props["TD"] = value + self._child.SetPropValue("TD", value) + + @property + def carrier_amplitude_voltage(self): + """Carrier amplitude value, voltage-based. + + Returns + ------- + str + """ + return self._props["V"] + + @carrier_amplitude_voltage.setter + def carrier_amplitude_voltage(self, value): + self._props["V"] = value + self._child.SetPropValue("V", value) + + @property + def carrier_amplitude_power(self): + """Carrier amplitude value, power-based. + + Returns + ------- + str + """ + return self._props["VA"] + + @carrier_amplitude_power.setter + def carrier_amplitude_power(self, value): + self._props["VA"] = value + self._child.SetPropValue("VA", value) + + @property + def carrier_offset(self): + """Carrier offset. + + Returns + ------- + str + """ + return self._props["VO"] + + @carrier_offset.setter + def carrier_offset(self, value): + self._props["VO"] = value + self._child.SetPropValue("VO", value) + + @property + def real_impedance(self): + """Real carrier impedance. + + Returns + ------- + str + """ + return self._props["RZ"] + + @real_impedance.setter + def real_impedance(self, value): + self._props["RZ"] = value + self._child.SetPropValue("RZ", value) + + @property + def imaginary_impedance(self): + """Imaginary carrier impedance. + + Returns + ------- + str + """ + return self._props["IZ"] + + @imaginary_impedance.setter + def imaginary_impedance(self, value): + self._props["IZ"] = value + self._child.SetPropValue("IZ", value) + + @property + def damping_factor(self): + """Damping factor. + + Returns + ------- + str + """ + return self._props["ALPHA"] + + @damping_factor.setter + def damping_factor(self, value): + self._props["ALPHA"] = value + self._child.SetPropValue("ALPHA", value) + + @property + def phase_delay(self): + """Phase delay. + + Returns + ------- + str + """ + return self._props["THETA"] + + @phase_delay.setter + def phase_delay(self, value): + self._props["THETA"] = value + self._child.SetPropValue("THETA", value) + + @property + def tone(self): + """Frequency to use for harmonic balance. + + Returns + ------- + str + """ + return self._props["TONE"] + + @tone.setter + def tone(self, value): + self._props["TONE"] = value + self._child.SetPropValue("TONE", value) + + @property + def i_q_values(self): + """I and Q value at each timepoint. + + Returns + ------- + str + """ + i_q = [] + for cont in range(1, 20): + i_q.append( + [self._props["time" + str(cont)], self._props["ival" + str(cont)], self._props["qval" + str(cont)]] + ) + return i_q + + @i_q_values.setter + def i_q_values(self, value): + cont = 0 + for point in value: + self._props["time" + str(cont + 1)] = point[0] + self._child.SetPropValue("time" + str(cont + 1), point[0]) + self._props["ival" + str(cont + 1)] = point[1] + self._child.SetPropValue("ival" + str(cont + 1), point[1]) + self._props["qval" + str(cont + 1)] = point[2] + self._child.SetPropValue("qval" + str(cont + 1), point[2]) + cont += 1 + + @property + def file( + self, + ): + """File path with I and Q values. + + Returns + ------- + str + """ + return self._props["file"] + + @file.setter + def file(self, value): + self._props["file"] = value + self.update() + + +class VoltageFrequencyDependentSource(Sources, object): + """Voltage Frequency Dependent Class.""" + + def __init__(self, app, name, source_type=None): + Sources.__init__(self, app, name, source_type) + + @property + def _child(self): + return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) + + @property + def frequencies(self): + """List of frequencies in ``Hz``. + + Returns + ------- + list + """ + return self._props["frequencies"] + + @frequencies.setter + def frequencies(self, value): + self._props["frequencies"] = [float(i) for i in value] + self._update_prop() + + @property + def vmag(self): + """List of magnitudes in ``V``. + + Returns + ------- + list + """ + return self._props["vmag"] + + @vmag.setter + def vmag(self, value): + self._props["vmag"] = [float(i) for i in value] + self._update_prop() + + @property + def vang(self): + """List of angles in ``rad``. + + Returns + ------- + list + """ + return self._props["vang"] + + @vang.setter + def vang(self, value): + self._props["vang"] = [float(i) for i in value] + self._update_prop() + + @property + def vreal(self): + """List of real values in ``V``. + + Returns + ------- + list + """ + return self._props["vreal"] + + @vreal.setter + def vreal(self, value): + self._props["vreal"] = [float(i) for i in value] + self._update_prop() + + @property + def vimag(self): + """List of imaginary values in ``V``. + + Returns + ------- + list + """ + return self._props["vimag"] + + @vimag.setter + def vimag(self, value): + self._props["vimag"] = [float(i) for i in value] + self._update_prop() + + @property + def magnitude_angle(self): + """Enable magnitude and angle data. + + Returns + ------- + bool + """ + return self._props["magnitude_angle"] + + @magnitude_angle.setter + def magnitude_angle(self, value): + self._props["magnitude_angle"] = value + self._update_prop() + + @property + def fds_filename(self): + """FDS file path. + + Returns + ------- + bool + """ + return self._props["fds_filename"] + + @fds_filename.setter + def fds_filename(self, name): + if not name: + self._props["fds_filename"] = None + self._update_prop() + else: + self._props["fds_filename"] = name + self._props["FreqDependentSourceData"] = "voltage_source_file=" + name + self.update() + + @pyaedt_function_handler() + def _update_prop(self): + if ( + self._props["vmag"] + and self._props["vang"] + and self._props["frequencies"] + and self._props["magnitude_angle"] + and not self._props["fds_filename"] + ): + if len(self._props["vmag"]) == len(self._props["vang"]) == len(self._props["frequencies"]): + self._props["FreqDependentSourceData"] = ( + "freqs=" + + str(self._props["frequencies"]).replace(",", "") + + " vmag=" + + str(self._props["vmag"]).replace(",", "") + + " vang=" + + str(self._props["vang"]).replace(",", "") + ) + self.update() + elif ( + self._props["vreal"] + and self._props["vimag"] + and self._props["frequencies"] + and not self._props["magnitude_angle"] + and not self._props["fds_filename"] + ): + if len(self._props["vreal"]) == len(self._props["vimag"]) == len(self._props["frequencies"]): + self._props["FreqDependentSourceData"] = ( + "freqs=" + + str(self._props["frequencies"]).replace(",", "") + + " vreal=" + + str(self._props["vreal"]).replace(",", "") + + " vimag=" + + str(self._props["vimag"]).replace(",", "") + ) + self.update() + else: + self._props["FreqDependentSourceData"] = "" + self.update() + return True + + +class VoltageDCSource(Sources, object): + """Power Sinusoidal Class.""" + + def __init__(self, app, name, source_type=None): + Sources.__init__(self, app, name, source_type) + + @property + def _child(self): + return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) + + @property + def ac_magnitude(self): + """AC magnitude value. + + Returns + ------- + str + """ + return self._props["ACMAG"] + + @ac_magnitude.setter + def ac_magnitude(self, value): + self._props["ACMAG"] = value + self._child.SetPropValue("ACMAG", value) + + @property + def ac_phase(self): + """AC phase value. + + Returns + ------- + str + """ + return self._props["ACPHASE"] + + @ac_phase.setter + def ac_phase(self, value): + self._props["ACPHASE"] = value + self._child.SetPropValue("ACPHASE", value) + + @property + def dc_magnitude(self): + """DC voltage value. + + Returns + ------- + str + """ + return self._props["DC"] + + @dc_magnitude.setter + def dc_magnitude(self, value): + self._props["DC"] = value + self._child.SetPropValue("DC", value) + + +class VoltageSinSource(Sources, object): + """Power Sinusoidal Class.""" + + def __init__(self, app, name, source_type=None): + Sources.__init__(self, app, name, source_type) + + @property + def _child(self): + return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) + + @property + def ac_magnitude(self): + """AC magnitude value. + + Returns + ------- + str + """ + return self._props["ACMAG"] + + @ac_magnitude.setter + def ac_magnitude(self, value): + self._props["ACMAG"] = value + self._child.SetPropValue("ACMAG", value) + + @property + def ac_phase(self): + """AC phase value. + + Returns + ------- + str + """ + return self._props["ACPHASE"] + + @ac_phase.setter + def ac_phase(self, value): + self._props["ACPHASE"] = value + self._child.SetPropValue("ACPHASE", value) + + @property + def dc_magnitude(self): + """DC voltage value. + + Returns + ------- + str + """ + return self._props["DC"] + + @dc_magnitude.setter + def dc_magnitude(self, value): + self._props["DC"] = value + self._child.SetPropValue("DC", value) + + @property + def voltage_amplitude(self): + """Voltage amplitude. + + Returns + ------- + str + """ + return self._props["VA"] + + @voltage_amplitude.setter + def voltage_amplitude(self, value): + self._props["VA"] = value + self._child.SetPropValue("VA", value) + + @property + def voltage_offset(self): + """Voltage offset from zero watts. + + Returns + ------- + str + """ + return self._props["VO"] + + @voltage_offset.setter + def voltage_offset(self, value): + self._props["VO"] = value + self._child.SetPropValue("VO", value) + + @property + def frequency(self): + """Frequency. + + Returns + ------- + str + """ + return self._props["FREQ"] + + @frequency.setter + def frequency(self, value): + self._props["FREQ"] = value + self._child.SetPropValue("FREQ", value) + + @property + def delay(self): + """Delay to start of sine wave. + + Returns + ------- + str + """ + return self._props["TD"] + + @delay.setter + def delay(self, value): + self._props["TD"] = value + self._child.SetPropValue("TD", value) + + @property + def damping_factor(self): + """Damping factor. + + Returns + ------- + str + """ + return self._props["ALPHA"] + + @damping_factor.setter + def damping_factor(self, value): + self._props["ALPHA"] = value + self._child.SetPropValue("ALPHA", value) + + @property + def phase_delay(self): + """Phase delay. + + Returns + ------- + str + """ + return self._props["THETA"] + + @phase_delay.setter + def phase_delay(self, value): + self._props["THETA"] = value + self._child.SetPropValue("THETA", value) + + @property + def tone(self): + """Frequency to use for harmonic balance. + + Returns + ------- + str + """ + return self._props["TONE"] + + @tone.setter + def tone(self, value): + self._props["TONE"] = value + self._child.SetPropValue("TONE", value) + + +class CurrentSinSource(Sources, object): + """Current Sinusoidal Class.""" + + def __init__(self, app, name, source_type=None): + Sources.__init__(self, app, name, source_type) + + @property + def _child(self): + return self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) + + @property + def ac_magnitude(self): + """AC magnitude value. + + Returns + ------- + str + """ + return self._props["ACMAG"] + + @ac_magnitude.setter + def ac_magnitude(self, value): + self._props["ACMAG"] = value + self._child.SetPropValue("ACMAG", value) + + @property + def ac_phase(self): + """AC phase value. + + Returns + ------- + str + """ + return self._props["ACPHASE"] + + @ac_phase.setter + def ac_phase(self, value): + self._props["ACPHASE"] = value + self._child.SetPropValue("ACPHASE", value) + + @property + def dc_magnitude(self): + """DC current value. + + Returns + ------- + str + """ + return self._props["DC"] + + @dc_magnitude.setter + def dc_magnitude(self, value): + self._props["DC"] = value + self._child.SetPropValue("DC", value) + + @property + def current_amplitude(self): + """Current amplitude. + + Returns + ------- + str + """ + return self._props["VA"] + + @current_amplitude.setter + def current_amplitude(self, value): + self._props["VA"] = value + self._child.SetPropValue("VA", value) + + @property + def current_offset(self): + """Current offset. + + Returns + ------- + str + """ + return self._props["VO"] + + @current_offset.setter + def current_offset(self, value): + self._props["VO"] = value + self._child.SetPropValue("VO", value) + + @property + def frequency(self): + """Frequency. + + Returns + ------- + str + """ + return self._props["FREQ"] + + @frequency.setter + def frequency(self, value): + self._props["FREQ"] = value + self._child.SetPropValue("FREQ", value) + + @property + def delay(self): + """Delay to start of sine wave. + + Returns + ------- + str + """ + return self._props["TD"] + + @delay.setter + def delay(self, value): + self._props["TD"] = value + self._child.SetPropValue("TD", value) + + @property + def damping_factor(self): + """Damping factor. + + Returns + ------- + str + """ + return self._props["ALPHA"] + + @damping_factor.setter + def damping_factor(self, value): + self._props["ALPHA"] = value + self._child.SetPropValue("ALPHA", value) + + @property + def phase_delay(self): + """Phase delay. + + Returns + ------- + str + """ + return self._props["THETA"] + + @phase_delay.setter + def phase_delay(self, value): + self._props["THETA"] = value + self._child.SetPropValue("THETA", value) + + @property + def multiplier(self): + """Multiplier for simulating multiple parallel current sources. + + Returns + ------- + str + """ + return self._props["M"] + + @multiplier.setter + def multiplier(self, value): + self._props["M"] = value + self._child.SetPropValue("M", value) + + @property + def tone(self): + """Frequency to use for harmonic balance. + + Returns + ------- + str + """ + return self._props["TONE"] + + @tone.setter + def tone(self, value): + self._props["TONE"] = value + self._child.SetPropValue("TONE", value) + + +class Excitations(object): + """Manages Excitations in Circuit Projects. + + Examples + -------- + + """ + + def __init__(self, app, name): + self._app = app + self._name = name + for comp in self._app.modeler.schematic.components: + if ( + "PortName" in self._app.modeler.schematic.components[comp].parameters.keys() + and self._app.modeler.schematic.components[comp].parameters["PortName"] == self.name + ): + self.schematic_id = comp + self.id = self._app.modeler.schematic.components[comp].id + self._angle = self._app.modeler.schematic.components[comp].angle + self.levels = self._app.modeler.schematic.components[comp].levels + self._location = self._app.modeler.schematic.components[comp].location + self._mirror = self._app.modeler.schematic.components[comp].mirror + self.pins = self._app.modeler.schematic.components[comp].pins + self._use_symbol_color = self._app.modeler.schematic.components[comp].usesymbolcolor + break + self._props = self._excitation_props(name) + self._auto_update = True + + @property + def name(self): + """Excitation name. + + Returns + ------- + str + """ + return self._name + + @name.setter + def name(self, port_name): + if port_name not in self._app.excitations: + if port_name != self._name: + # Take previous properties + self._app.odesign.RenamePort(self._name, port_name) + self._name = port_name + self._app.modeler.schematic.components[self.schematic_id].name = "IPort@" + port_name + self.pins[0].name = "IPort@" + port_name + ";" + str(self.schematic_id) + else: + self._logger.warning("Name %s already assigned in the design", port_name) + + @property + def angle(self): + """Symbol angle. + + Returns + ------- + float + """ + return self._angle + + @angle.setter + def angle(self, angle): + self._app.modeler.schematic.components[self.schematic_id].angle = angle + + @property + def mirror(self): + """Enable port mirror. + + Returns + ------- + bool + """ + return self._mirror + + @mirror.setter + def mirror(self, mirror_value=True): + self._app.modeler.schematic.components[self.schematic_id].mirror = mirror_value + self._mirror = mirror_value + + @property + def location(self): + """Port location. + + Returns + ------- + list + """ + return self._location + + @location.setter + def location(self, location_xy): + # The command must be called two times. + self._app.modeler.schematic.components[self.schematic_id].location = location_xy + self._app.modeler.schematic.components[self.schematic_id].location = location_xy + self._location = location_xy + + @property + def use_symbol_color(self): + """Use symbol color. + + Returns + ------- + list + """ + return self._use_symbol_color + + @use_symbol_color.setter + def use_symbol_color(self, use_color=True): + self._app.modeler.schematic.components[self.schematic_id].usesymbolcolor = use_color + self._app.modeler.schematic.components[self.schematic_id].set_use_symbol_color(use_color) + self._use_symbol_color = use_color + + @property + def impedance(self): + """Port termination. + + Returns + ------- + list + """ + return [self._props["rz"], self._props["iz"]] + + @impedance.setter + def impedance(self, termination=None): + if termination and len(termination) == 2: + self._app.modeler.schematic.components[self.schematic_id].change_property( + ["NAME:rz", "Value:=", termination[0]] + ) + self._app.modeler.schematic.components[self.schematic_id].change_property( + ["NAME:iz", "Value:=", termination[1]] + ) + self._props["rz"] = termination[0] + self._props["iz"] = termination[1] + + @property + def enable_noise(self): + """Enable noise. + + Returns + ------- + bool + """ + + return self._props["EnableNoise"] + + @enable_noise.setter + def enable_noise(self, enable=False): + self._app.modeler.schematic.components[self.schematic_id].change_property( + ["NAME:EnableNoise", "Value:=", enable] + ) + self._props["EnableNoise"] = enable + + @property + def noise_temperature(self): + """Enable noise. + + Returns + ------- + str + """ + + return self._props["noisetemp"] + + @noise_temperature.setter + def noise_temperature(self, noise=None): + if noise: + self._app.modeler.schematic.components[self.schematic_id].change_property( + ["NAME:noisetemp", "Value:=", noise] + ) + self._props["noisetemp"] = noise + + @property + def microwave_symbol(self): + """Enable microwave symbol. + + Returns + ------- + bool + """ + if self._props["SymbolType"] == 1: + return True + else: + return False + + @microwave_symbol.setter + def microwave_symbol(self, enable=False): + if enable: + self._props["SymbolType"] = 1 + else: + self._props["SymbolType"] = 0 + self.update() + + @property + def reference_node(self): + """Reference node. + + Returns + ------- + str + """ + if self._props["RefNode"] == "Z": + return "Ground" + return self._props["RefNode"] + + @reference_node.setter + def reference_node(self, ref_node=None): + if ref_node: + self._logger.warning("Set reference node only working with gRPC") + if ref_node == "Ground": + ref_node = "Z" + self._props["RefNode"] = ref_node + self.update() + + @property + def enabled_sources(self): + """Enabled sources. + + Returns + ------- + list + """ + return self._props["EnabledPorts"] + + @enabled_sources.setter + def enabled_sources(self, sources=None): + if sources: + self._props["EnabledPorts"] = sources + self.update() + + @property + def enabled_analyses(self): + """Enabled analyses. + + Returns + ------- + dict + """ + return self._props["EnabledAnalyses"] + + @enabled_analyses.setter + def enabled_analyses(self, analyses=None): + if analyses: + self._props["EnabledAnalyses"] = analyses + self.update() + + @pyaedt_function_handler() + def _excitation_props(self, port): + excitation_prop_dict = {} + for comp in self._app.modeler.schematic.components: + if ( + "PortName" in self._app.modeler.schematic.components[comp].parameters.keys() + and self._app.modeler.schematic.components[comp].parameters["PortName"] == port + ): + excitation_prop_dict["rz"] = "50ohm" + excitation_prop_dict["iz"] = "0ohm" + excitation_prop_dict["term"] = None + excitation_prop_dict["TerminationData"] = None + excitation_prop_dict["RefNode"] = "Z" + excitation_prop_dict["EnableNoise"] = False + excitation_prop_dict["noisetemp"] = "16.85cel" + + if "RefNode" in self._app.modeler.schematic.components[comp].parameters: + excitation_prop_dict["RefNode"] = self._app.modeler.schematic.components[comp].parameters["RefNode"] + if "term" in self._app.modeler.schematic.components[comp].parameters: + excitation_prop_dict["term"] = self._app.modeler.schematic.components[comp].parameters["term"] + excitation_prop_dict["TerminationData"] = self._app.modeler.schematic.components[comp].parameters[ + "TerminationData" + ] + else: + if "rz" in self._app.modeler.schematic.components[comp].parameters: + excitation_prop_dict["rz"] = self._app.modeler.schematic.components[comp].parameters["rz"] + excitation_prop_dict["iz"] = self._app.modeler.schematic.components[comp].parameters["iz"] + + if "EnableNoise" in self._app.modeler.schematic.components[comp].parameters: + if self._app.modeler.schematic.components[comp].parameters["EnableNoise"] == "true": + excitation_prop_dict["EnableNoise"] = True + else: + excitation_prop_dict["EnableNoise"] = False + + excitation_prop_dict["noisetemp"] = self._app.modeler.schematic.components[comp].parameters[ + "noisetemp" + ] + + if not self._app.design_properties or not self._app.design_properties["NexximPorts"]["Data"]: + excitation_prop_dict["SymbolType"] = 0 + else: + excitation_prop_dict["SymbolType"] = self._app.design_properties["NexximPorts"]["Data"][port][ + "SymbolType" + ] + + if "pnum" in self._app.modeler.schematic.components[comp].parameters: + excitation_prop_dict["pnum"] = self._app.modeler.schematic.components[comp].parameters["pnum"] + else: + excitation_prop_dict["pnum"] = None + source_port = [] + if not self._app.design_properties: + enabled_ports = None + else: + enabled_ports = self._app.design_properties["ComponentConfigurationData"]["EnabledPorts"] + if isinstance(enabled_ports, dict): + for source in enabled_ports: + if enabled_ports[source] and port in enabled_ports[source]: + source_port.append(source) + excitation_prop_dict["EnabledPorts"] = source_port + + components_port = [] + if not self._app.design_properties: + multiple = None + else: + multiple = self._app.design_properties["ComponentConfigurationData"]["EnabledMultipleComponents"] + if isinstance(multiple, dict): + for source in multiple: + if multiple[source] and port in multiple[source]: + components_port.append(source) + excitation_prop_dict["EnabledMultipleComponents"] = components_port + + port_analyses = {} + if not self._app.design_properties: + enabled_analyses = None + else: + enabled_analyses = self._app.design_properties["ComponentConfigurationData"]["EnabledAnalyses"] + if isinstance(enabled_analyses, dict): + for source in enabled_analyses: + if ( + enabled_analyses[source] + and port in enabled_analyses[source] + and source in excitation_prop_dict["EnabledPorts"] + ): + port_analyses[source] = enabled_analyses[source][port] + excitation_prop_dict["EnabledAnalyses"] = port_analyses + return excitation_prop_dict + + @pyaedt_function_handler() + def update(self): + """Update the excitation in AEDT. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + + # self._logger.warning("Property port update only working with GRPC") + + if self._props["RefNode"] == "Ground": + self._props["RefNode"] = "Z" + + arg0 = [ + "NAME:" + self.name, + "IIPortName:=", + self.name, + "SymbolType:=", + self._props["SymbolType"], + "DoPostProcess:=", + False, + ] + + arg1 = ["NAME:ChangedProps"] + arg2 = [] + + # Modify RefNode + if self._props["RefNode"] != "Z": + arg2 = [ + "NAME:NewProps", + ["NAME:RefNode", "PropType:=", "TextProp", "OverridingDef:=", True, "Value:=", self._props["RefNode"]], + ] + + # Modify Termination + if self._props["term"] and self._props["TerminationData"]: + arg2 = [ + "NAME:NewProps", + ["NAME:term", "PropType:=", "TextProp", "OverridingDef:=", True, "Value:=", self._props["term"]], + ] + + for prop in self._props: + skip1 = (prop == "rz" or prop == "iz") and isinstance(self._props["term"], str) + skip2 = prop == "EnabledPorts" or prop == "EnabledMultipleComponents" or prop == "EnabledAnalyses" + skip3 = prop == "SymbolType" + skip4 = prop == "TerminationData" and not isinstance(self._props["term"], str) + if not skip1 and not skip2 and not skip3 and not skip4 and self._props[prop] is not None: + command = ["NAME:" + prop, "Value:=", self._props[prop]] + arg1.append(command) + + arg1 = [["NAME:Properties", arg2, arg1]] + self._app.odesign.ChangePortProperty(self.name, arg0, arg1) + + for source in self._app.sources: + self._app.sources[source].update() + return True + + @pyaedt_function_handler() + def delete(self): + """Delete the port in AEDT. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + self._app.modeler._odesign.DeletePort(self.name) + return True + + @property + def _logger(self): + """Logger.""" + return self._app.logger + + +class NetworkObject(BoundaryObject): + """Manages networks in Icepak projects.""" + + def __init__(self, app, name=None, props=None, create=False): + if not app.design_type == "Icepak": # pragma: no cover + raise NotImplementedError("Networks object works only with Icepak projects ") + if name is None: + self._name = generate_unique_name("Network") + else: + self._name = name + super(NetworkObject, self).__init__(app, self._name, props, "Network", False) + + self._nodes = [] + self._links = [] + self._schematic_data = {} + self._update_from_props() + if create: + self.create() + + def _clean_list(self, arg): + new_list = [] + for item in arg: + if isinstance(item, list): + if item[0] == "NAME:PageNet": + page_net_list = [] + for i in item: + if isinstance(i, list): + name = page_net_list[-1] + page_net_list.pop(-1) + for j in i: + page_net_list.append(name) + page_net_list.append(j) + else: + page_net_list.append(i) + new_list.append(page_net_list) + else: + new_list.append(self._clean_list(item)) + else: + new_list.append(item) + return new_list + + @pyaedt_function_handler() + def create(self): + """ + Create network in AEDT. + + Returns + ------- + bool: + True if successful. + """ + if not self.props.get("Faces", None): + self.props["Faces"] = [node.props["FaceID"] for _, node in self.face_nodes.items()] + if not self.props.get("SchematicData", None): + self.props["SchematicData"] = {} + + if self.props.get("Links", None): + self.props["Links"] = {link_name: link_values.props for link_name, link_values in self.links.items()} + else: # pragma : no cover + raise KeyError("Links information is missing.") + if self.props.get("Nodes", None): + self.props["Nodes"] = {node_name: node_values.props for node_name, node_values in self.nodes.items()} + else: # pragma : no cover + raise KeyError("Nodes information is missing.") + + args = self._get_args() + + clean_args = self._clean_list(args) + self._app.oboundary.AssignNetworkBoundary(clean_args) + return True + + @pyaedt_function_handler() + def _update_from_props(self): + nodes = self.props.get("Nodes", None) + if nodes is not None: + nd_name_list = [node.name for node in self._nodes] + for node_name, node_dict in nodes.items(): + if node_name not in nd_name_list: + nd_type = node_dict.get("NodeType", None) + if nd_type == "InternalNode": + self.add_internal_node( + node_name, + node_dict.get("Power", node_dict.get("Power Variation Data", None)), + mass=node_dict.get("Mass", None), + specific_heat=node_dict.get("SpecificHeat", None), + ) + elif nd_type == "BoundaryNode": + self.add_boundary_node( + node_name, + assignment_type=node_dict["ValueType"].replace("Value", ""), + value=node_dict[node_dict["ValueType"].replace("Value", "")], + ) + else: + if ( + node_dict["ThermalResistance"] == "NoResistance" + or node_dict["ThermalResistance"] == "Specified" + ): + node_material, node_thickness = None, None + node_resistance = node_dict["Resistance"] + else: + node_thickness, node_material = node_dict["Thickness"], node_dict["Material"] + node_resistance = None + self.add_face_node( + node_dict["FaceID"], + name=node_name, + thermal_resistance=node_dict["ThermalResistance"], + material=node_material, + thickness=node_thickness, + resistance=node_resistance, + ) + links = self.props.get("Links", None) + if links is not None: + l_name_list = [l.name for l in self._links] + for link_name, link_dict in links.items(): + if link_name not in l_name_list: + self.add_link(link_dict[0], link_dict[1], link_dict[-1], link_name) + + @property + def auto_update(self): + """ + Get if auto-update is enabled. + + Returns + ------- + bool: + Whether auto-update is enabled. + """ + return False + + @auto_update.setter + def auto_update(self, b): + """ + Set auto-update on or off. + + Parameters + ---------- + b : bool + Whether to enable auto-update. + + """ + if b: + self._app.logger.warning( + "Network objects auto_update property is False by default" " and cannot be set to True." + ) + + @property + def links(self): + """ + Get links of the network. + + Returns + ------- + dict: + Links dictionary. + + """ + self._update_from_props() + return {link.name: link for link in self._links} + + @property + def r_links(self): + """ + Get r-links of the network. + + Returns + ------- + dict: + R-links dictionary. + + """ + self._update_from_props() + return {link.name: link for link in self._links if link._link_type[0] == "R-Link"} + + @property + def c_links(self): + """ + Get c-links of the network. + + Returns + ------- + dict: + C-links dictionary. + + """ + self._update_from_props() + return {link.name: link for link in self._links if link._link_type[0] == "C-Link"} + + @property + def nodes(self): + """ + Get nodes of the network. + + Returns + ------- + dict: + Nodes dictionary. + + """ + self._update_from_props() + return {node.name: node for node in self._nodes} + + @property + def face_nodes(self): + """ + Get face nodes of the network. + + Returns + ------- + dict: + Face nodes dictionary. + + """ + self._update_from_props() + return {node.name: node for node in self._nodes if node.node_type == "FaceNode"} + + @property + def faces_ids_in_network(self): + """ + Get ID of faces included in the network. + + Returns + ------- + list: + Face IDs. + + """ + out_arr = [] + for _, node_dict in self.face_nodes.items(): + out_arr.append(node_dict.props["FaceID"]) + return out_arr + + @property + def objects_in_network(self): + """ + Get objects included in the network. + + Returns + ------- + list: + Objects names. + + """ + out_arr = [] + for face_id in self.faces_ids_in_network: + out_arr.append(self._app.oeditor.GetObjectNameByFaceID(face_id)) + return out_arr + + @property + def internal_nodes(self): + """ + Get internal nodes. + + Returns + ------- + dict: + Internal nodes. + + """ + self._update_from_props() + return {node.name: node for node in self._nodes if node.node_type == "InternalNode"} + + @property + def boundary_nodes(self): + """ + Get boundary nodes. + + Returns + ------- + dict: + Boundary nodes. + + """ + self._update_from_props() + return {node.name: node for node in self._nodes if node.node_type == "BoundaryNode"} + + @property + def name(self): + """ + Get network name. + + Returns + ------- + str + Network name. + """ + return self._name + + @name.setter + def name(self, new_network_name): + """ + Set new name of the network. + + Parameters + ---------- + new_network_name : str + New name of the network. + """ + bound_names = [b.name for b in self._app.boundaries] + if self.name in bound_names: + if new_network_name not in bound_names: + if new_network_name != self._name: + self._app._oboundary.RenameBoundary(self._name, new_network_name) + self._name = new_network_name + else: + self._app.logger.warning("Name %s already assigned in the design", new_network_name) + else: + self._name = new_network_name + + @pyaedt_function_handler() + def add_internal_node(self, name, power, mass=None, specific_heat=None): + """Add an internal node to the network. + + Parameters + ---------- + name : str + Name of the node. + power : str or float or dict + String, float, or dictionary containing the value of the assignment. + If a float is passed, the ``"W"`` unit is used. A dictionary can be + passed to use temperature-dependent or transient + assignments. + mass : str or float, optional + Value of the mass assignment. This parameter is relevant only + if the solution is transient. If a float is passed, the ``"Kg"`` unit + is used. The default is ``None``, in which case ``"0.001kg"`` is used. + specific_heat : str or float, optional + Value of the specific heat assignment. This parameter is + relevant only if the solution is transient. If a float is passed, + the ``"J_per_Kelkg"`` unit is used. The default is ``None`, in + which case ``"1000J_per_Kelkg"`` is used. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + Examples + -------- + >>> import ansys.aedt.core + >>> app = ansys.aedt.core.Icepak() + >>> network = ansys.aedt.core.modules.boundary.Network(app) + >>> network.add_internal_node("TestNode", {"Type": "Transient", + >>> "Function": "Linear", "Values": ["0.01W", "1"]}) + """ + if self._app.solution_type != "SteadyState" and mass is None and specific_heat is None: + self._app.logger.warning("The solution is transient but neither mass nor specific heat is assigned.") + if self._app.solution_type == "SteadyState" and ( + mass is not None or specific_heat is not None + ): # pragma: no cover + self._app.logger.warning( + "Because the solution is steady state, neither mass nor specific heat assignment is relevant." + ) + if isinstance(power, (int, float)): + power = str(power) + "W" + props_dict = {"Power": power} + if mass is not None: + if isinstance(mass, (int, float)): + mass = str(mass) + "kg" + props_dict.update({"Mass": mass}) + if specific_heat is not None: + if isinstance(specific_heat, (int, float)): + specific_heat = str(specific_heat) + "J_per_Kelkg" + props_dict.update({"SpecificHeat": specific_heat}) + new_node = self._Node(name, self._app, node_type="InternalNode", props=props_dict, network=self) + self._nodes.append(new_node) + self._add_to_props(new_node) + return new_node + + @pyaedt_function_handler() + def add_boundary_node(self, name, assignment_type, value): + """ + Add a boundary node to the network. + + Parameters + ---------- + name : str + Name of the node. + assignment_type : str + Type assignment. Options are ``"Power"`` and ``"Temperature"``. + 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"`` + is selected for the type, a dictionary can be passed to use + temperature-dependent or transient assignment. + + Returns + ------- + bool + ``True`` if successful. + + Examples + -------- + >>> import ansys.aedt.core + >>> app = ansys.aedt.core.Icepak() + >>> network = ansys.aedt.core.modules.boundary.Network(app) + >>> network.add_boundary_node("TestNode", "Temperature", 2) + >>> ds = app.create_dataset1d_design("Test_DataSet",[1, 2, 3],[3, 4, 5]) + >>> network.add_boundary_node("TestNode", "Power", {"Type": "Temp Dep", + >>> "Function": "Piecewise Linear", + >>> "Values": "Test_DataSet"}) + """ + if assignment_type not in ["Power", "Temperature", "PowerValue", "TemperatureValue"]: # pragma: no cover + raise AttributeError('``type`` can be only ``"Power"`` or ``"Temperature"``.') + if isinstance(value, (float, int)): + if assignment_type == "Power" or assignment_type == "PowerValue": + value = str(value) + "W" + else: + value = str(value) + "cel" + if isinstance(value, dict) and ( + assignment_type == "Temperature" or assignment_type == "TemperatureValue" + ): # pragma: no cover + raise AttributeError( + "Temperature-dependent or transient assignment is not supported in a temperature boundary node." + ) + if not assignment_type.endswith("Value"): + assignment_type += "Value" + new_node = self._Node( + name, + self._app, + node_type="BoundaryNode", + props={"ValueType": assignment_type, assignment_type.removesuffix("Value"): value}, + network=self, + ) + self._nodes.append(new_node) + self._add_to_props(new_node) + return new_node + + @pyaedt_function_handler() + def _add_to_props(self, new_node, type_dict="Nodes"): + try: + self.props[type_dict].update({new_node.name: new_node.props}) + except KeyError: + self.props[type_dict] = {new_node.name: new_node.props} + + @pyaedt_function_handler(face_id="assignment") + def add_face_node( + self, assignment, name=None, thermal_resistance="NoResistance", material=None, thickness=None, resistance=None + ): + """ + Create a face node in the network. + + Parameters + ---------- + assignment : int + Face ID. + name : str, optional + Name of the node. Default is ``None``. + thermal_resistance : str + Thermal resistance value and unit. Default is ``"NoResistance"``. + material : str, optional + Material specification (required if ``thermal_resistance="Compute"``). + Default is ``None``. + thickness : str or float, optional + Thickness value and unit (required if ``thermal_resistance="Compute"``). + If a float is passed, ``"mm"`` unit is automatically used. Default is ``None``. + resistance : str or float, optional + Resistance value and unit (required if ``thermal_resistance="Specified"``). + If a float is passed, ``"cel_per_w"`` unit is automatically used. Default is ``None``. + + Returns + ------- + bool + True if successful. + + Examples + -------- + >>> import ansys.aedt.core + >>> app = ansys.aedt.core.Icepak() + >>> network = ansys.aedt.core.modules.boundary.Network(app) + >>> box = app.modeler.create_box([5, 5, 5],[20, 50, 80]) + >>> faces_ids = [face.id for face in box.faces] + >>> network.add_face_node(faces_ids[0]) + >>> network.add_face_node(faces_ids[1],name="TestNode",thermal_resistance="Compute", + ... material="Al-Extruded",thickness="2mm") + >>> network.add_face_node(faces_ids[2],name="TestNode",thermal_resistance="Specified",resistance=2) + """ + props_dict = {} + props_dict["FaceID"] = assignment + if thermal_resistance is not None: + if thermal_resistance == "Compute": + if resistance is not None: + self._app.logger.info( + '``resistance`` assignment is incompatible with ``thermal_resistance="Compute"``' + "and it is ignored." + ) + if material is not None or thickness is not None: + props_dict["ThermalResistance"] = thermal_resistance + props_dict["Material"] = material + if not isinstance(thickness, str): + thickness = str(thickness) + "mm" + props_dict["Thickness"] = thickness + else: # pragma: no cover + raise AttributeError( + 'If ``thermal_resistance="Compute"`` both ``material`` and ``thickness``' + "arguments must be prescribed." + ) + if thermal_resistance == "Specified": + if material is not None or thickness is not None: + self._app.logger.warning( + "Because ``material`` and ``thickness`` assignments are incompatible with" + '``thermal_resistance="Specified"``, they are ignored.' + ) + if resistance is not None: + props_dict["ThermalResistance"] = thermal_resistance + if not isinstance(resistance, str): + resistance = str(resistance) + "cel_per_w" + props_dict["Resistance"] = resistance + else: # pragma : no cover + raise AttributeError( + 'If ``thermal_resistance="Specified"``, ``resistance`` argument must be prescribed.' + ) + + if name is None: + name = "FaceID" + str(assignment) + new_node = self._Node(name, self._app, node_type="FaceNode", props=props_dict, network=self) + self._nodes.append(new_node) + self._add_to_props(new_node) + return new_node + + @pyaedt_function_handler(nodes_dict="nodes") + def add_nodes_from_dictionaries(self, nodes): + """ + Add nodes to the network from dictionary. + + Parameters + ---------- + nodes : 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: + + - Face nodes must contain the ``"ID"`` key associated with an integer containing the face ID. + Optional keys and values pairs are: + + - ``"ThermalResistance"``: a string specifying the type of thermal resistance. + Options are ``"NoResistance"`` (default), ``"Compute"``, and ``"Specified"``. + - ``"Thickness"``: a string with the thickness value and unit (required if ``"Compute"`` + is selected for ``"ThermalResistance"``). + - ``"Material"``: a string with the name of the material (required if ``"Compute"`` is + selected for ``"ThermalResistance"``). + - ``"Resistance"``: a string with the resistance value and unit (required if + ``"Specified"`` is selected for ``"ThermalResistance"``). + - ``"Name"``: a string with the name of the node. If not + specified, a name is generated automatically. + + + - Internal nodes must contain the following keys and values pairs: + + - ``"Name"``: a string with the node name + - ``"Power"``: a string with the assigned power or a dictionary for transient or + temperature-dependent assignment + Optional keys and values pairs: + - ``"Mass"``: a string with the mass value and unit + - ``"SpecificHeat"``: a string with the specific heat value and unit + + - Boundary nodes must contain the following keys and values pairs: + + - ``"Name"``: a string with the node name + - ``"ValueType"``: a string specifying the type of value (``"Power"`` or + ``"Temperature"``) + Depending on the ``"ValueType"`` choice, one of the following keys and values pairs must + be used: + - ``"Power"``: a string with the power value and unit or a dictionary for transient or + temperature-dependent assignment + - ``"Temperature"``: a string with the temperature value and unit or a dictionary for + transient or temperature-dependent assignment + According to the ``"ValueType"`` choice, ``"Power"`` or ``"Temperature"`` key must be + used. Their associated value a string with the value and unit of the quantity prescribed or + a dictionary for transient or temperature dependent assignment. + + + All the temperature dependent or thermal dictionaries should contain three keys: + ``"Type"``, ``"Function"``, and ``"Values"``. Accepted ``"Type"`` values are: + ``"Temp Dep"`` and ``"Transient"``. Accepted ``"Function"`` are: ``"Linear"``, + ``"Power Law"``, ``"Exponential"``, ``"Sinusoidal"``, ``"Square Wave"``, and + ``"Piecewise Linear"``. ``"Temp Dep"`` only support the latter. ``"Values"`` + contains a list of strings containing the parameters required by the ``"Function"`` + selection (e.g. ``"Linear"`` requires two parameters: the value of the variable at t=0 + and the slope of the line). The parameters required by each ``Function`` option is in + Icepak documentation. The parameters must contain the units where needed. + + Returns + ------- + bool + ``True`` if successful. ``False`` otherwise. + + Examples + -------- + >>> import ansys.aedt.core + >>> app = ansys.aedt.core.Icepak() + >>> network = ansys.aedt.core.modules.boundary.Network(app) + >>> box = app.modeler.create_box([5, 5, 5],[20, 50, 80]) + >>> faces_ids = [face.id for face in box.faces] + >>> nodes_dict = [ + >>> {"FaceID": faces_ids[0]}, + >>> {"Name": "TestNode", "FaceID": faces_ids[1], + >>> "ThermalResistance": "Compute", "Thickness": "2mm"}, + >>> {"FaceID": faces_ids[2], "ThermalResistance": "Specified", "Resistance": "2cel_per_w"}, + >>> {"Name": "Junction", "Power": "1W"}] + >>> network.add_nodes_from_dictionaries(nodes_dict) + """ + if isinstance(nodes, dict): + nodes = [nodes] + for node_dict in nodes: + if "FaceID" in node_dict.keys(): + self.add_face_node( + assignment=node_dict["FaceID"], + name=node_dict.get("Name", None), + thermal_resistance=node_dict.get("ThermalResistance", None), + material=node_dict.get("Material", None), + thickness=node_dict.get("Thickness", None), + resistance=node_dict.get("Resistance", None), + ) + elif "ValueType" in node_dict.keys(): + if node_dict["ValueType"].endswith("Value"): + value = node_dict[node_dict["ValueType"].removesuffix("Value")] + else: + value = node_dict[node_dict["ValueType"]] + self.add_boundary_node(name=node_dict["Name"], assignment_type=node_dict["ValueType"], value=value) + else: + self.add_internal_node( + name=node_dict["Name"], + power=node_dict.get("Power", None), + mass=node_dict.get("Mass", None), + specific_heat=node_dict.get("SpecificHeat", None), + ) + return True + + @pyaedt_function_handler() + def add_link(self, node1, node2, value, name=None): + """Create links in the network object. + + Parameters + ---------- + node1 : str or int + String containing one of the node names that the link is connecting or an integer + containing the ID of the face. If an ID is used and the node associated with the + corresponding face is not created yet, it is added automatically. + node2 : str or int + String containing one of the node names that the link is connecting or an integer + containing the ID of the face. If an ID is used and the node associated with the + corresponding face is not created yet, it is added atuomatically. + value : str or float + String containing the value and unit of the connection. If a float is passed, an + R-Link is added to the network and the ``"cel_per_w"`` unit is used. + name : str, optional + Name of the link. The default is ``None``, in which case a name is + automatically generated. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + Examples + -------- + >>> import ansys.aedt.core + >>> app = ansys.aedt.core.Icepak() + >>> network = ansys.aedt.core.modules.boundary.Network(app) + >>> box = app.modeler.create_box([5, 5, 5],[20, 50, 80]) + >>> faces_ids = [face.id for face in box.faces] + >>> connection = {"Name": "LinkTest", "Connection": [faces_ids[1], faces_ids[0]], "Value": "1cel_per_w"} + >>> network.add_links_from_dictionaries(connection) + """ + if name is None: + new_name = True + while new_name: + name = generate_unique_name("Link") + if name not in self.links.keys(): + new_name = False + new_link = self._Link(node1, node2, value, name, self) + self._links.append(new_link) + self._add_to_props(new_link, "Links") + return True + + @pyaedt_function_handler() + def add_links_from_dictionaries(self, connections): + """Create links in the network object. + + Parameters + ---------- + connections : dict or list of dict + Dictionary or list of dictionaries containing the links between nodes. Each dictionary + consists of these elements: + + - ``"Link"``: a three-item list consisting of the two nodes that the link is connecting and + the value with unit of the link. The node of the connection can be referred to with the + name (str) or face ID (int). The link type (resistance, heat transfer coefficient, or + mass flow) is determined automatically from the unit. + - ``"Name"`` (optional): a string specifying the name of the link. + + + Returns + ------- + bool + ``True`` if successful. + + Examples + -------- + >>> import ansys.aedt.core + >>> app = ansys.aedt.core.Icepak() + >>> network = ansys.aedt.core.modules.boundary.Network(app) + >>> box = app.modeler.create_box([5, 5, 5],[20, 50, 80]) + >>> faces_ids = [face.id for face in box.faces] + >>> [network.add_face_node(faces_ids[i]) for i in range(2)] + >>> connection = {"Name": "LinkTest", "Link": [faces_ids[1], faces_ids[0], "1cel_per_w"]} + >>> network.add_links_from_dictionaries(connection) + """ + if isinstance(connections, dict): + connections = [connections] + for connection in connections: + name = connection.get("Name", None) + try: + self.add_link(connection["Link"][0], connection["Link"][1], connection["Link"][2], name) + except Exception: # pragma : no cover + if name: + self._app.logger.error("Failed to add " + name + " link.") + else: + self._app.logger.error( + "Failed to add link associated with the following dictionary:\n" + str(connection) + ) + return True + + @pyaedt_function_handler() + def update(self): + """Update the network in AEDT. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + if self.name in [b.name for b in self._app.boundaries]: + self.delete() + try: + self.create() + self._app._boundaries[self.name] = self + return True + except Exception: # pragma : no cover + self._app.odesign.Undo() + self._app.logger.error("Update of network object failed.") + return False + else: # pragma : no cover + self._app.logger.warning("Network object not yet created in design.") + return False + + @pyaedt_function_handler() + def update_assignment(self): + """Update assignments of the network.""" + return self.update() + + class _Link: + def __init__(self, node_1, node_2, value, name, network): + self.name = name + if not isinstance(node_1, str): + node_1 = "FaceID" + str(node_1) + if not isinstance(node_2, str): + node_2 = "FaceID" + str(node_2) + if not isinstance(value, str): + value = str(value) + "cel_per_w" + self.node_1 = node_1 + self.node_2 = node_2 + self.value = value + self._network = network + + @property + def _link_type(self): + unit2type_conversion = { + "g_per_s": ["C-Link", "Node1ToNode2"], + "kg_per_s": ["C-Link", "Node1ToNode2"], + "lbm_per_min": ["C-Link", "Node1ToNode2"], + "lbm_per_s": ["C-Link", "Node1ToNode2"], + "Kel_per_W": ["R-Link", "R"], + "cel_per_w": ["R-Link", "R"], + "FahSec_per_btu": ["R-Link", "R"], + "Kels_per_J": ["R-Link", "R"], + "w_per_m2kel": ["R-Link", "HTC"], + "w_per_m2Cel": ["R-Link", "HTC"], + "btu_per_rankHrFt2": ["R-Link", "HTC"], + "btu_per_fahHrFt2": ["R-Link", "HTC"], + "btu_per_rankSecFt2": ["R-Link", "HTC"], + "btu_per_fahSecFt2": ["R-Link", "HTC"], + "w_per_cm2kel": ["R-Link", "HTC"], + } + _, unit = decompose_variable_value(self.value) + return unit2type_conversion[unit] + + @property + def props(self): + """ + Get link properties. + + Returns + ------- + list + First two elements of the list are the node names that the link connects, + the third element is the link type while the fourth contains the value + associated with the link. + """ + return [self.node_1, self.node_2] + self._link_type + [self.value] + + @pyaedt_function_handler() + def delete_link(self): + """ + Delete link from network. + """ + self._network.props["Links"].pop(self.name) + self._network._links.remove(self) + + class _Node: + def __init__(self, name, app, network, node_type=None, props=None): + self.name = name + self._type = node_type + self._app = app + self._props = props + self._node_props() + self._network = network + + @pyaedt_function_handler() + def delete_node(self): + """Delete node from network.""" + self._network.props["Nodes"].pop(self.name) + self._network._nodes.remove(self) + + @property + def node_type(self): + """Get node type. + + Returns + ------- + str + Node type. + """ + if self._type is None: # pragma: no cover + if self.props is None: + self._app.logger.error( + "Cannot define node_type. Both its assignment and properties assignment are missing." + ) + return None + else: + type_in_dict = self.props.get("NodeType", None) + if type_in_dict is None: + self._type = "FaceNode" + else: + self._type = type_in_dict + return self._type + + @property + def props(self): + """Get properties of the node. + + Returns + ------- + dict + Node properties. + """ + return self._props + + @props.setter + def props(self, props): + """Set properties of the node. + + Parameters + ---------- + props : dict + Node properties. + """ + self._props = props + self._node_props() + + def _node_props(self): + face_node_default_dict = { + "FaceID": None, + "ThermalResistance": "NoResistance", + "Thickness": "1mm", + "Material": "Al-Extruded", + "Resistance": "0cel_per_w", + } + boundary_node_default_dict = { + "NodeType": "BoundaryNode", + "ValueType": "PowerValue", + "Power": "0W", + "Temperature": "25cel", + } + internal_node_default_dict = { + "NodeType": "InternalNode", + "Power": "0W", + "Mass": "0.001kg", + "SpecificHeat": "1000J_per_Kelkg", + } + if self.props is None: + if self.node_type == "InternalNode": + self._props = internal_node_default_dict + elif self.node_type == "FaceNode": + self._props = face_node_default_dict + elif self.node_type == "BoundaryNode": + self._props = boundary_node_default_dict + else: + if self.node_type == "InternalNode": + self._props = self._create_node_dict(internal_node_default_dict) + elif self.node_type == "FaceNode": + self._props = self._create_node_dict(face_node_default_dict) + elif self.node_type == "BoundaryNode": + self._props = self._create_node_dict(boundary_node_default_dict) + + @pyaedt_function_handler() + def _create_node_dict(self, default_dict): + node_dict = self.props + node_name = node_dict.get("Name", self.name) + if not node_name: + try: + self.name = "Face" + str(node_dict["FaceID"]) + except KeyError: # pragma: no cover + raise KeyError('"Name" key is needed for "BoundaryNodes" and "InternalNodes" dictionaries.') + else: + self.name = node_name + node_dict.pop("Name", None) + node_args = copy.deepcopy(default_dict) + for k in node_dict.keys(): + val = node_dict[k] + if isinstance(val, dict): # pragma : no cover + val = self._app._parse_variation_data( + k, val["Type"], variation_value=val["Values"], function=val["Function"] + ) + node_args.pop(k) + node_args.update(val) + else: + node_args[k] = val + + return node_args diff --git a/src/ansys/aedt/core/modules/boundary/common.py b/src/ansys/aedt/core/modules/boundary/common.py new file mode 100644 index 00000000000..9bbbc2147ad --- /dev/null +++ b/src/ansys/aedt/core/modules/boundary/common.py @@ -0,0 +1,715 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +This module contains these classes: ``BoundaryCommon`` and ``BoundaryObject``. +""" + +from ansys.aedt.core.generic.data_handlers import _dict2arg +from ansys.aedt.core.generic.general_methods import PropsManager +from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode +from ansys.aedt.core.modeler.cad.elements_3d import EdgePrimitive +from ansys.aedt.core.modeler.cad.elements_3d import FacePrimitive +from ansys.aedt.core.modeler.cad.elements_3d import VertexPrimitive + + +class BoundaryProps(dict): + """AEDT Boundary Component Internal Parameters.""" + + def __setitem__(self, key, value): + dict.__setitem__(self, key, value) + if self._pyaedt_boundary.auto_update: + if key in ["Edges", "Faces", "Objects"]: + res = self._pyaedt_boundary.update_assignment() + else: + res = self._pyaedt_boundary.update() + if not res: + self._pyaedt_boundary._app.logger.warning("Update of %s Failed. Check needed arguments", key) + + def __init__(self, boundary, props): + dict.__init__(self) + if props: + for key, value in props.items(): + if isinstance(value, dict): + dict.__setitem__(self, key, BoundaryProps(boundary, value)) + elif isinstance(value, list): + list_els = [] + for el in value: + if isinstance(el, dict): + list_els.append(BoundaryProps(boundary, el)) + else: + list_els.append(el) + dict.__setitem__(self, key, list_els) + else: + dict.__setitem__(self, key, value) + self._pyaedt_boundary = boundary + + def _setitem_without_update(self, key, value): + dict.__setitem__(self, key, value) + + +class BoundaryCommon(PropsManager): + """ """ + + @pyaedt_function_handler() + def _get_args(self, props=None): + """Retrieve boundary properties. + + Parameters + ---------- + props : dict, optional + The default is ``None``. + + Returns + ------- + dict + Dictionary of boundary properties. + + """ + if not props: + props = self.props + arg = ["NAME:" + self.name] + _dict2arg(props, arg) + return arg + + @pyaedt_function_handler() + def _initialize_bynary_tree(self): + if self._child_object: + BinaryTreeNode.__init__(self, self._name, self._child_object, False) + + @pyaedt_function_handler() + def delete(self): + """Delete the boundary. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + if self.type == "Matrix" or self.type == "Force" or self.type == "Torque": + self._app.o_maxwell_parameters.DeleteParameters([self.name]) + else: + self._app.oboundary.DeleteBoundaries([self.name]) + if self.name in self._app.excitation_objects.keys(): + self._app.excitation_objects.pop(self.name) + return True + + def _get_boundary_data(self, ds): + try: + if "MaxwellParameterSetup" in self._app.design_properties: + param = "MaxwellParameters" + setup = "MaxwellParameterSetup" + if isinstance(self._app.design_properties[setup][param][ds], dict): + return [ + self._app.design_properties["MaxwellParameterSetup"]["MaxwellParameters"][ds], + self._app.design_properties["MaxwellParameterSetup"]["MaxwellParameters"][ds][ + "MaxwellParameterType" + ], + ] + except Exception: + self._app.logger.debug( + "An error occurred while getting boundary data for MaxwellParameterSetup." + ) # pragma: no cover + try: + if ( + "ModelSetup" in self._app.design_properties + and "MotionSetupList" in self._app.design_properties["ModelSetup"] + ): + motion_list = "MotionSetupList" + setup = "ModelSetup" + # check moving part + if isinstance(self._app.design_properties[setup][motion_list][ds], dict): + return [ + self._app.design_properties["ModelSetup"]["MotionSetupList"][ds], + self._app.design_properties["ModelSetup"]["MotionSetupList"][ds]["MotionType"], + ] + except Exception: + self._app.logger.debug("An error occurred while getting boundary data for ModelSetup.") # pragma: no cover + try: + if ds in self._app.design_properties["BoundarySetup"]["Boundaries"]: + if ( + self._app.design_properties["BoundarySetup"]["Boundaries"][ds]["BoundType"] == "Network" + and self._app.design_type == "Icepak" + ): + return [self._app.design_properties["BoundarySetup"]["Boundaries"][ds], ""] + else: + return [ + self._app.design_properties["BoundarySetup"]["Boundaries"][ds], + self._app.design_properties["BoundarySetup"]["Boundaries"][ds]["BoundType"], + ] + except Exception: + self._app.logger.debug( + "An error occurred while getting boundary data for BoundarySetup." + ) # pragma: no cover + return [] + + +def disable_auto_update(func): + """Decorator used to disable automatic update.""" + + def wrapper(self, *args, **kwargs): + """Inner wrapper function.""" + obj = self + if not hasattr(self, "auto_update"): + obj = self.pcb + auto_update = obj.auto_update + obj.auto_update = False + out = func(self, *args, **kwargs) + if auto_update: + obj.update() + obj.auto_update = auto_update + return out + + return wrapper + + +class BoundaryObject(BoundaryCommon, BinaryTreeNode): + """Manages boundary data and execution. + + Parameters + ---------- + app : object + An AEDT application from ``ansys.aedt.core.application``. + name : str + Name of the boundary. + props : dict, optional + Properties of the boundary. + boundarytype : str, optional + Type of the boundary. + + Examples + -------- + + Create a cylinder at the XY working plane and assign a copper coating of 0.2 mm to it. The Coating is a boundary + operation and coat will return a ``ansys.aedt.core.modules.boundary.BoundaryObject`` + + >>> from ansys.aedt.core import Hfss + >>> hfss =Hfss() + >>> origin = hfss.modeler.Position(0, 0, 0) + >>> inner = hfss.modeler.create_cylinder(hfss.PLANE.XY,origin,3,200,0,"inner") + >>> inner_id = hfss.modeler.get_obj_id("inner",) + >>> coat = hfss.assign_coating([inner_id],"copper",use_thickness=True,thickness="0.2mm") + """ + + def __init__(self, app, name, props=None, boundarytype=None, auto_update=True): + self.auto_update = False + self._app = app + self._name = name + self.__props = None + self.__props = BoundaryProps(self, props) if props else {} + self._type = boundarytype + self._boundary_name = self.name + self.auto_update = auto_update + self._initialize_bynary_tree() + + @property + def _child_object(self): + """Object-oriented properties. + + Returns + ------- + class:`ansys.aedt.core.modeler.cad.elements_3d.BinaryTreeNode` + + """ + child_object = None + design_childs = self._app.get_oo_name(self._app.odesign) + + if "Thermal" in design_childs: + cc = self._app.get_oo_object(self._app.odesign, "Thermal") + cc_names = self._app.get_oo_name(cc) + if self.name in cc_names: + child_object = cc.GetChildObject(self.name) + elif "Boundaries" in design_childs: + cc = self._app.get_oo_object(self._app.odesign, "Boundaries") + if self.name in cc.GetChildNames(): + child_object = cc.GetChildObject(self.name) + elif "Excitations" in design_childs and self.name in self._app.get_oo_name( + self._app.odesign, "Excitations" + ): + child_object = self._app.get_oo_object(self._app.odesign, "Excitations").GetChildObject(self.name) + elif self._app.design_type in ["Maxwell 3D", "Maxwell 2D"] and "Model" in design_childs: + model = self._app.get_oo_object(self._app.odesign, "Model") + if self.name in model.GetChildNames(): + child_object = model.GetChildObject(self.name) + elif "Excitations" in design_childs and self._app.get_oo_name(self._app.odesign, "Excitations"): + for port in self._app.get_oo_name(self._app.odesign, "Excitations"): + terminals = self._app.get_oo_name(self._app.odesign, f"Excitations\\{port}") + if self.name in terminals: + child_object = self._app.get_oo_object(self._app.odesign, f"Excitations\\{port}\\{self.name}") + elif "Conductors" in design_childs and self._app.get_oo_name(self._app.odesign, "Conductors"): + for port in self._app.get_oo_name(self._app.odesign, "Conductors"): + if self.name == port: + child_object = self._app.get_oo_object(self._app.odesign, f"Conductors\\{port}") + return child_object + + @property + def props(self): + """Boundary data. + + Returns + ------- + :class:BoundaryProps + """ + if self.__props: + return self.__props + props = self._get_boundary_data(self.name) + + if props: + self.__props = BoundaryProps(self, props[0]) + self._type = props[1] + return self.__props + + @property + def type(self): + """Boundary type. + + Returns + ------- + str + Returns the type of the boundary. + """ + if not self._type: + if self.available_properties: + if "Type" in self.available_properties: + self._type = self.props["Type"] + elif "BoundType" in self.available_properties: + self._type = self.props["BoundType"] + elif self.properties and self.properties["Type"]: + self._type = self.properties["Type"] + + if self._app.design_type == "Icepak" and self._type == "Source": + return "SourceIcepak" + else: + return self._type + + @type.setter + def type(self, value): + self._type = value + + @property + def name(self): + """Boundary Name.""" + return self._name + + @name.setter + def name(self, value): + self._name = value + self.update() + + @pyaedt_function_handler() + def _get_args(self, props=None): + """Retrieve arguments. + + Parameters + ---------- + props : + The default is ``None``. + + Returns + ------- + list + List of boundary properties. + + """ + if props is None: + props = self.props + arg = ["NAME:" + self.name] + _dict2arg(props, arg) + return arg + + @pyaedt_function_handler() + def create(self): + """Create a boundary. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + bound_type = self.type + if bound_type == "Perfect E": + self._app.oboundary.AssignPerfectE(self._get_args()) + elif bound_type == "Perfect H": + self._app.oboundary.AssignPerfectH(self._get_args()) + elif bound_type == "Aperture": + self._app.oboundary.AssignAperture(self._get_args()) + elif bound_type == "Radiation": + self._app.oboundary.AssignRadiation(self._get_args()) + elif bound_type == "FE-BI": + self._app.oboundary.AssignFEBI(self._get_args()) + elif bound_type == "Finite Conductivity": + self._app.oboundary.AssignFiniteCond(self._get_args()) + elif bound_type == "Lumped RLC": + self._app.oboundary.AssignLumpedRLC(self._get_args()) + elif bound_type == "Impedance": + self._app.oboundary.AssignImpedance(self._get_args()) + elif bound_type == "Layered Impedance": + self._app.oboundary.AssignLayeredImp(self._get_args()) + elif bound_type == "Anisotropic Impedance": + self._app.oboundary.AssignAnisotropicImpedance(self._get_args()) + elif bound_type == "Primary": + self._app.oboundary.AssignPrimary(self._get_args()) + elif bound_type == "Secondary": + self._app.oboundary.AssignSecondary(self._get_args()) + elif bound_type == "Lattice Pair": + self._app.oboundary.AssignLatticePair(self._get_args()) + elif bound_type == "HalfSpace": + self._app.oboundary.AssignHalfSpace(self._get_args()) + elif bound_type == "Multipaction SEE": + self._app.oboundary.AssignMultipactionSEE(self._get_args()) + elif bound_type == "Fresnel": + self._app.oboundary.AssignFresnel(self._get_args()) + elif bound_type == "Symmetry": + self._app.oboundary.AssignSymmetry(self._get_args()) + elif bound_type == "Zero Tangential H Field": + self._app.oboundary.AssignZeroTangentialHField(self._get_args()) + elif bound_type == "Zero Integrated Tangential H Field": + self._app.oboundary.AssignIntegratedZeroTangentialHField(self._get_args()) + elif bound_type == "Tangential H Field": + self._app.oboundary.AssignTangentialHField(self._get_args()) + elif bound_type == "Insulating": + self._app.oboundary.AssignInsulating(self._get_args()) + elif bound_type == "Independent": + self._app.oboundary.AssignIndependent(self._get_args()) + elif bound_type == "Dependent": + self._app.oboundary.AssignDependent(self._get_args()) + elif bound_type == "Band": + self._app.omodelsetup.AssignBand(self._get_args()) + elif bound_type == "InfiniteGround": + self._app.oboundary.AssignInfiniteGround(self._get_args()) + elif bound_type == "ThinConductor": + self._app.oboundary.AssignThinConductor(self._get_args()) + elif bound_type == "Stationary Wall": + self._app.oboundary.AssignStationaryWallBoundary(self._get_args()) + elif bound_type == "Symmetry Wall": + self._app.oboundary.AssignSymmetryWallBoundary(self._get_args()) + elif bound_type == "Recirculating": + self._app.oboundary.AssignRecircBoundary(self._get_args()) + elif bound_type == "Resistance": + self._app.oboundary.AssignResistanceBoundary(self._get_args()) + elif bound_type == "Conducting Plate": + self._app.oboundary.AssignConductingPlateBoundary(self._get_args()) + elif bound_type == "Adiabatic Plate": + self._app.oboundary.AssignAdiabaticPlateBoundary(self._get_args()) + elif bound_type == "Network": + self._app.oboundary.AssignNetworkBoundary(self._get_args()) + elif bound_type == "Grille": + self._app.oboundary.AssignGrilleBoundary(self._get_args()) + elif bound_type == "Block": + self._app.oboundary.AssignBlockBoundary(self._get_args()) + elif bound_type == "Blower": + self._app.oboundary.AssignBlowerBoundary(self._get_args()) + elif bound_type == "SourceIcepak": + self._app.oboundary.AssignSourceBoundary(self._get_args()) + elif bound_type == "Opening": + self._app.oboundary.AssignOpeningBoundary(self._get_args()) + elif bound_type == "EMLoss": + self._app.oboundary.AssignEMLoss(self._get_args()) + elif bound_type == "ThermalCondition": + self._app.oboundary.AssignThermalCondition(self._get_args()) + elif bound_type == "Convection": + self._app.oboundary.AssignConvection(self._get_args()) + elif bound_type == "HeatFlux": + self._app.oboundary.AssignHeatFlux(self._get_args()) + elif bound_type == "HeatGeneration": + self._app.oboundary.AssignHeatGeneration(self._get_args()) + elif bound_type == "Temperature": + self._app.oboundary.AssignTemperature(self._get_args()) + elif bound_type == "RotatingFluid": + self._app.oboundary.AssignRotatingFluid(self._get_args()) + elif bound_type == "Frictionless": + self._app.oboundary.AssignFrictionlessSupport(self._get_args()) + elif bound_type == "FixedSupport": + self._app.oboundary.AssignFixedSupport(self._get_args()) + elif bound_type == "Voltage": + self._app.oboundary.AssignVoltage(self._get_args()) + elif bound_type == "VoltageDrop": + self._app.oboundary.AssignVoltageDrop(self._get_args()) + elif bound_type == "Floating": + self._app.oboundary.AssignFloating(self._get_args()) + elif bound_type == "Current": + self._app.oboundary.AssignCurrent(self._get_args()) + elif bound_type == "CurrentDensity": + self._app.oboundary.AssignCurrentDensity(self._get_args()) + elif bound_type == "CurrentDensityGroup": + self._app.oboundary.AssignCurrentDensityGroup(self._get_args()[2], self._get_args()[3]) + elif bound_type == "CurrentDensityTerminal": + self._app.oboundary.AssignCurrentDensityTerminal(self._get_args()) + elif bound_type == "CurrentDensityTerminalGroup": + self._app.oboundary.AssignCurrentDensityTerminalGroup(self._get_args()[2], self._get_args()[3]) + elif bound_type == "Balloon": + self._app.oboundary.AssignBalloon(self._get_args()) + elif bound_type == "Winding" or bound_type == "Winding Group": + self._app.oboundary.AssignWindingGroup(self._get_args()) + elif bound_type == "Vector Potential": + self._app.oboundary.AssignVectorPotential(self._get_args()) + elif bound_type == "CoilTerminal" or bound_type == "Coil Terminal": + self._app.oboundary.AssignCoilTerminal(self._get_args()) + elif bound_type == "Coil": + self._app.oboundary.AssignCoil(self._get_args()) + elif bound_type == "Source": + self._app.oboundary.AssignSource(self._get_args()) + elif bound_type == "Sink": + self._app.oboundary.AssignSink(self._get_args()) + elif bound_type == "SignalNet": + self._app.oboundary.AssignSignalNet(self._get_args()) + elif bound_type == "GroundNet": + self._app.oboundary.AssignGroundNet(self._get_args()) + elif bound_type == "FloatingNet": + self._app.oboundary.AssignFloatingNet(self._get_args()) + elif bound_type == "SignalLine": + self._app.oboundary.AssignSingleSignalLine(self._get_args()) + elif bound_type == "ReferenceGround": + self._app.oboundary.AssignSingleReferenceGround(self._get_args()) + elif bound_type == "Circuit Port": + self._app.oboundary.AssignCircuitPort(self._get_args()) + elif bound_type == "Lumped Port": + self._app.oboundary.AssignLumpedPort(self._get_args()) + elif bound_type == "Wave Port": + self._app.oboundary.AssignWavePort(self._get_args()) + elif bound_type == "Floquet Port": + self._app.oboundary.AssignFloquetPort(self._get_args()) + elif bound_type == "AutoIdentify": + # Build reference conductor argument as a list of strings + # ref_cond_arg should be a list. + ref_cond_arg = ["NAME:ReferenceConductors"] + self.props["ReferenceConductors"] + self._app.oboundary.AutoIdentifyPorts( + ["NAME:Faces", self.props["Faces"]], + self.props["IsWavePort"], + ref_cond_arg, + self.name, + self.props["RenormalizeModes"], + ) + elif bound_type == "SBRTxRxSettings": + self._app.oboundary.SetSBRTxRxSettings(self._get_args()) + elif bound_type == "EndConnection": + self._app.oboundary.AssignEndConnection(self._get_args()) + elif bound_type == "Hybrid": + self._app.oboundary.AssignHybridRegion(self._get_args()) + elif bound_type == "FluxTangential": + self._app.oboundary.AssignFluxTangential(self._get_args()) + elif bound_type == "Plane Incident Wave": + self._app.oboundary.AssignPlaneWave(self._get_args()) + elif bound_type == "ResistiveSheet": + self._app.oboundary.AssignResistiveSheet(self._get_args()) + else: + return False + self._initialize_bynary_tree() + + return True + + @pyaedt_function_handler() + def update(self): + """Update the boundary. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + bound_type = self.type + if bound_type == "Perfect E": + self._app.oboundary.EditPerfectE(self._boundary_name, self._get_args()) + elif bound_type == "Perfect H": + self._app.oboundary.EditPerfectH(self._boundary_name, self._get_args()) + elif bound_type == "Aperture": + self._app.oboundary.EditAperture(self._boundary_name, self._get_args()) + elif bound_type == "Radiation": + self._app.oboundary.EditRadiation(self._boundary_name, self._get_args()) + elif bound_type == "Finite Conductivity": + self._app.oboundary.EditFiniteCond(self._boundary_name, self._get_args()) + elif bound_type == "Lumped RLC": + self._app.oboundary.EditLumpedRLC(self._boundary_name, self._get_args()) + elif bound_type == "Impedance": + self._app.oboundary.EditImpedance(self._boundary_name, self._get_args()) + elif bound_type == "Layered Impedance": + self._app.oboundary.EditLayeredImpedance(self._boundary_name, self._get_args()) + elif bound_type == "Anisotropic Impedance": + self._app.oboundary.EditAssignAnisotropicImpedance( + self._boundary_name, self._get_args() + ) # pragma: no cover + elif bound_type == "Primary": + self._app.oboundary.EditPrimary(self._boundary_name, self._get_args()) + elif bound_type == "Secondary": + self._app.oboundary.EditSecondary(self._boundary_name, self._get_args()) + elif bound_type == "Lattice Pair": + self._app.oboundary.EditLatticePair(self._boundary_name, self._get_args()) + elif bound_type == "HalfSpace": + self._app.oboundary.EditHalfSpace(self._boundary_name, self._get_args()) + elif bound_type == "Multipaction SEE": + self._app.oboundary.EditMultipactionSEE(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Fresnel": + self._app.oboundary.EditFresnel(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Symmetry": + self._app.oboundary.EditSymmetry(self._boundary_name, self._get_args()) + elif bound_type == "Zero Tangential H Field": + self._app.oboundary.EditZeroTangentialHField(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Zero Integrated Tangential H Field": + self._app.oboundary.EditIntegratedZeroTangentialHField( + self._boundary_name, self._get_args() + ) # pragma: no cover + elif bound_type == "Tangential H Field": + self._app.oboundary.EditTangentialHField(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Insulating": + self._app.oboundary.EditInsulating(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Independent": + self._app.oboundary.EditIndependent(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Dependent": + self._app.oboundary.EditDependent(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Band": + self._app.omodelsetup.EditMotionSetup(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "InfiniteGround": + self._app.oboundary.EditInfiniteGround(self._boundary_name, self._get_args()) + elif bound_type == "ThinConductor": + self._app.oboundary.EditThinConductor(self._boundary_name, self._get_args()) + elif bound_type == "Stationary Wall": + self._app.oboundary.EditStationaryWallBoundary(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Symmetry Wall": + self._app.oboundary.EditSymmetryWallBoundary(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Recirculating": + self._app.oboundary.EditRecircBoundary(self._boundary_name, self._get_args()) + elif bound_type == "Resistance": + self._app.oboundary.EditResistanceBoundary(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Conducting Plate": + self._app.oboundary.EditConductingPlateBoundary(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Adiabatic Plate": + self._app.oboundary.EditAdiabaticPlateBoundary(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Network": + self._app.oboundary.EditNetworkBoundary(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Grille": + self._app.oboundary.EditGrilleBoundary(self._boundary_name, self._get_args()) + elif bound_type == "Opening": + self._app.oboundary.EditOpeningBoundary(self._boundary_name, self._get_args()) + elif bound_type == "EMLoss": + self._app.oboundary.EditEMLoss(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Block": + self._app.oboundary.EditBlockBoundary(self._boundary_name, self._get_args()) + elif bound_type == "Blower": + self._app.oboundary.EditBlowerBoundary(self._boundary_name, self._get_args()) + elif bound_type == "SourceIcepak": + self._app.oboundary.EditSourceBoundary(self._boundary_name, self._get_args()) + elif bound_type == "HeatFlux": + self._app.oboundary.EditHeatFlux(self._boundary_name, self._get_args()) + elif bound_type == "HeatGeneration": + self._app.oboundary.EditHeatGeneration(self._boundary_name, self._get_args()) + elif bound_type == "Voltage": + self._app.oboundary.EditVoltage(self._boundary_name, self._get_args()) + elif bound_type == "VoltageDrop": + self._app.oboundary.EditVoltageDrop(self._boundary_name, self._get_args()) + elif bound_type == "Current": + self._app.oboundary.EditCurrent(self._boundary_name, self._get_args()) + elif bound_type == "CurrentDensity": + self._app.oboundary.AssignCurrentDensity(self._get_args()) + elif bound_type == "CurrentDensityGroup": + self._app.oboundary.AssignCurrentDensityGroup(self._get_args()[2], self._get_args()[3]) + elif bound_type == "CurrentDensityTerminal": + self._app.oboundary.AssignCurrentDensityTerminal(self._get_args()) + elif bound_type == "CurrentDensityTerminalGroup": + self._app.oboundary.AssignCurrentDensityTerminalGroup(self._get_args()[2], self._get_args()[3]) + elif bound_type == "Winding" or bound_type == "Winding Group": + self._app.oboundary.EditWindingGroup(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Vector Potential": + self._app.oboundary.EditVectorPotential(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "CoilTerminal" or bound_type == "Coil Terminal": + self._app.oboundary.EditCoilTerminal(self._boundary_name, self._get_args()) + elif bound_type == "Coil": + self._app.oboundary.EditCoil(self._boundary_name, self._get_args()) + elif bound_type == "Source": + self._app.oboundary.EditTerminal(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Sink": + self._app.oboundary.EditTerminal(self._boundary_name, self._get_args()) + elif bound_type == "SignalNet" or bound_type == "GroundNet" or bound_type == "FloatingNet": + self._app.oboundary.EditTerminal(self._boundary_name, self._get_args()) + elif bound_type in "Circuit Port": + self._app.oboundary.EditCircuitPort(self._boundary_name, self._get_args()) + elif bound_type in "Lumped Port": + self._app.oboundary.EditLumpedPort(self._boundary_name, self._get_args()) + elif bound_type in "Wave Port": + self._app.oboundary.EditWavePort(self._boundary_name, self._get_args()) + elif bound_type == "SetSBRTxRxSettings": + self._app.oboundary.SetSBRTxRxSettings(self._get_args()) # pragma: no cover + elif bound_type == "Floquet Port": + self._app.oboundary.EditFloquetPort(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "End Connection": + self._app.oboundary.EditEndConnection(self._boundary_name, self._get_args()) + elif bound_type == "Hybrid": + self._app.oboundary.EditHybridRegion(self._boundary_name, self._get_args()) + elif bound_type == "Terminal": + self._app.oboundary.EditTerminal(self._boundary_name, self._get_args()) + elif bound_type == "Plane Incident Wave": + self._app.oboundary.EditIncidentWave(self._boundary_name, self._get_args()) + elif bound_type == "ResistiveSheet": + self._app.oboundary.EditResistiveSheet(self._boundary_name, self._get_args()) + else: + return False # pragma: no cover + + self._app._boundaries[self.name] = self._app._boundaries.pop(self._boundary_name) + self._boundary_name = self.name + + return True + + @pyaedt_function_handler() + def update_assignment(self): + """Update the boundary assignment. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + + out = ["Name:" + self.name] + + if "Faces" in self.props: + faces = self.props["Faces"] + faces_out = [] + if not isinstance(faces, list): + faces = [faces] + for f in faces: + if isinstance(f, (EdgePrimitive, FacePrimitive, VertexPrimitive)): + faces_out.append(f.id) + else: + faces_out.append(f) + out += ["Faces:=", faces_out] + + if "Objects" in self.props: + pr = [] + for el in self.props["Objects"]: + try: + pr.append(self._app.modeler[el].name) + except (KeyError, AttributeError): + pass + out += ["Objects:=", pr] + + if len(out) == 1: + return False + + self._app.oboundary.ReassignBoundary(out) + + return True diff --git a/src/ansys/aedt/core/modules/boundary/hfss_boundary.py b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py new file mode 100644 index 00000000000..c1323e978d2 --- /dev/null +++ b/src/ansys/aedt/core/modules/boundary/hfss_boundary.py @@ -0,0 +1,521 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from ansys.aedt.core.generic.data_handlers import _dict2arg +from ansys.aedt.core.generic.general_methods import _dim_arg +from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode +from ansys.aedt.core.modules.boundary.common import BoundaryCommon +from ansys.aedt.core.modules.boundary.common import BoundaryProps + + +class FieldSetup(BoundaryCommon, BinaryTreeNode): + """Manages far field and near field component data and execution. + + Examples + -------- + In this example the sphere1 returned object is a ``ansys.aedt.core.modules.boundary.FarFieldSetup`` + >>> from ansys.aedt.core import Hfss + >>> hfss = Hfss() + >>> sphere1 = hfss.insert_infinite_sphere() + >>> sphere1.props["ThetaStart"] = "-90deg" + >>> sphere1.props["ThetaStop"] = "90deg" + >>> sphere1.props["ThetaStep"] = "2deg" + >>> sphere1.delete() + """ + + def __init__(self, app, component_name, props, component_type): + self.auto_update = False + self._app = app + self.type = component_type + self._name = component_name + self.__props = BoundaryProps(self, props) if props else {} + self.auto_update = True + child_object = self._app.get_oo_object(self._app.odesign, f"Radiation/{self._name}") + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) + + @property + def props(self): + if not self.__props and self._app.design_properties: + if ( + self.type == "FarFieldSphere" + and self._app.design_properties.get("RadField") + and self._app.design_properties["RadField"].get("FarFieldSetups") + ): + for val in self._app.design_properties["RadField"]["FarFieldSetups"]: + if val == self.name: + self.__props = self._app.design_properties["RadField"]["FarFieldSetups"][val] + elif self.type != "FarFieldSphere" and self._app.design_properties["RadField"].get("NearFieldSetups"): + for val in self._app.design_properties["RadField"]["NearFieldSetups"]: + if val == self.name: + self.__props = self._app.design_properties["RadField"]["NearFieldSetups"][val] + self.__props = BoundaryProps(self, self.__props) + return self.__props + + @property + def name(self): + """Variable name.""" + return self._name + + @name.setter + def name(self, value): + self._app.oradfield.RenameSetup(self._name, value) + self._name = value + + @pyaedt_function_handler() + def _get_args(self, props=None): + if props is None: + props = self.props + arg = ["NAME:" + self.name] + _dict2arg(props, arg) + return arg + + @pyaedt_function_handler() + def create(self): + """Create a Field Setup Component in HFSS. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + + if self.type == "FarFieldSphere": + self._app.oradfield.InsertInfiniteSphereSetup(self._get_args()) + elif self.type == "NearFieldBox": + self._app.oradfield.InsertBoxSetup(self._get_args()) + elif self.type == "NearFieldSphere": + self._app.oradfield.InsertSphereSetup(self._get_args()) + elif self.type == "NearFieldRectangle": + self._app.oradfield.InsertRectangleSetup(self._get_args()) + elif self.type == "NearFieldLine": + self._app.oradfield.InsertLineSetup(self._get_args()) + elif self.type == "AntennaOverlay": + self._app.oradfield.AddAntennaOverlay(self._get_args()) + elif self.type == "FieldSourceGroup": + self._app.oradfield.AddRadFieldSourceGroup(self._get_args()) + child_object = self._app.get_oo_object(self._app.odesign, f"Radiation/{self._name}") + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) + return True + + @pyaedt_function_handler() + def update(self): + """Update the Field Setup in AEDT. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + + if self.type == "FarFieldSphere": + self._app.oradfield.EditInfiniteSphereSetup(self.name, self._get_args()) + elif self.type == "NearFieldBox": + self._app.oradfield.EditBoxSetup(self.name, self._get_args()) + elif self.type == "NearFieldSphere": + self._app.oradfield.EditSphereSetup(self.name, self._get_args()) + elif self.type == "NearFieldRectangle": + self._app.oradfield.EditRectangleSetup(self.name, self._get_args()) + elif self.type == "NearFieldLine": + self._app.oradfield.EditLineSetup(self.name, self._get_args()) + elif self.type == "AntennaOverlay": + self._app.oradfield.EditAntennaOverlay(self.name, self._get_args()) + elif self.type == "FieldSourceGroup": + self._app.oradfield.EditRadFieldSourceGroup(self._get_args()) + return True + + @pyaedt_function_handler() + def delete(self): + """Delete the Field Setup in AEDT. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + self._app.oradfield.DeleteSetup([self.name]) + for el in self._app.field_setups: + if el.name == self.name: + self._app.field_setups.remove(el) + return True + + +class FarFieldSetup(FieldSetup, object): + """Manages Far Field Component data and execution. + + Examples + -------- + in this example the sphere1 returned object is a ``ansys.aedt.core.modules.boundary.FarFieldSetup`` + >>> from ansys.aedt.core import Hfss + >>> hfss = Hfss() + >>> sphere1 = hfss.insert_infinite_sphere() + >>> sphere1.props["ThetaStart"] = "-90deg" + >>> sphere1.props["ThetaStop"] = "90deg" + >>> sphere1.props["ThetaStep"] = "2deg" + >>> sphere1.delete() + """ + + def __init__(self, app, component_name, props, component_type, units="deg"): + FieldSetup.__init__(self, app, component_name, props, component_type) + self.units = units + + @property + def definition(self): + """Set/Get the Far Field Angle Definition.""" + return self.props["CSDefinition"] + + @definition.setter + def definition(self, value): + actual_value = self.props["CSDefinition"] + self.props["CSDefinition"] = value + actual_defs = None + defs = None + if actual_value != value and value == "Theta-Phi": + defs = ["ThetaStart", "ThetaStop", "ThetaStep", "PhiStart", "PhiStop", "PhiStep"] + actual_defs = [ + "AzimuthStart", + "AzimuthStop", + "AzimuthStep", + "ElevationStart", + "ElevationStop", + "ElevationStep", + ] + elif actual_value != value and value == "El Over Az": + defs = ["AzimuthStart", "AzimuthStop", "AzimuthStep", "ElevationStart", "ElevationStop", "ElevationStep"] + if actual_value == "Theta-Phi": + actual_defs = ["ThetaStart", "ThetaStop", "ThetaStep", "PhiStart", "PhiStop", "PhiStep"] + else: + actual_defs = [ + "AzimuthStart", + "AzimuthStop", + "AzimuthStep", + "ElevationStart", + "ElevationStop", + "ElevationStep", + ] + elif actual_value != value: + defs = ["ElevationStart", "ElevationStop", "ElevationStep", "AzimuthStart", "AzimuthStop", "AzimuthStep"] + if actual_value == "Theta-Phi": + actual_defs = ["ThetaStart", "ThetaStop", "ThetaStep", "PhiStart", "PhiStop", "PhiStep"] + else: + actual_defs = [ + "ElevationStart", + "ElevationStop", + "ElevationStep", + "AzimuthStart", + "AzimuthStop", + "AzimuthStep", + ] + if actual_defs != defs: + self.props[defs[0]] = self.props[actual_defs[0]] + self.props[defs[1]] = self.props[actual_defs[1]] + self.props[defs[2]] = self.props[actual_defs[2]] + self.props[defs[3]] = self.props[actual_defs[3]] + self.props[defs[4]] = self.props[actual_defs[4]] + self.props[defs[5]] = self.props[actual_defs[5]] + del self.props[actual_defs[0]] + del self.props[actual_defs[1]] + del self.props[actual_defs[2]] + del self.props[actual_defs[3]] + del self.props[actual_defs[4]] + del self.props[actual_defs[5]] + self.update() + + @property + def use_custom_radiation_surface(self): + """Set/Get the Far Field Radiation Surface Enable.""" + return self.props["UseCustomRadiationSurface"] + + @use_custom_radiation_surface.setter + def use_custom_radiation_surface(self, value): + self.props["UseCustomRadiationSurface"] = value + self.update() + + @property + def custom_radiation_surface(self): + """Set/Get the Far Field Radiation Surface FaceList.""" + return self.props["CustomRadiationSurface"] + + @custom_radiation_surface.setter + def custom_radiation_surface(self, value): + if value: + self.props["UseCustomRadiationSurface"] = True + self.props["CustomRadiationSurface"] = value + else: + self.props["UseCustomRadiationSurface"] = False + self.props["CustomRadiationSurface"] = "" + self.update() + + @property + def use_local_coordinate_system(self): + """Set/Get the usage of a custom Coordinate System.""" + return self.props["UseLocalCS"] + + @use_local_coordinate_system.setter + def use_local_coordinate_system(self, value): + self.props["UseLocalCS"] = value + self.update() + + @property + def local_coordinate_system(self): + """Set/Get the custom Coordinate System name.""" + try: + return self.properties["Coordinate System"] + except Exception: # pragma: no cover + return None + + @local_coordinate_system.setter + def local_coordinate_system(self, value): + if value: + self.props["UseLocalCS"] = True + self.props["CoordSystem"] = value + else: + self.props["UseLocalCS"] = False + self.props["CoordSystem"] = "" + self.update() + + @property + def polarization(self): + """Set/Get the Far Field Polarization.""" + return self.props["Polarization"] + + @polarization.setter + def polarization(self, value): + self.props["Polarization"] = value + self.update() + + @property + def slant_angle(self): + """Set/Get the Far Field Slant Angle if Polarization is Set to `Slant`.""" + + if self.props["Polarization"] == "Slant": + return self.props["SlantAngle"] + else: + return + + @slant_angle.setter + def slant_angle(self, value): + self.props["Polarization"] = "Slant" + self.props["SlantAngle"] = value + self.update() + + @property + def theta_start(self): + """Set/Get the Far Field Theta Start Angle if Definition is Set to `Theta-Phi`.""" + + if "ThetaStart" in self.props: + return self.props["ThetaStart"] + else: + return + + @property + def theta_stop(self): + """Set/Get the Far Field Theta Stop Angle if Definition is Set to `Theta-Phi`.""" + + if "ThetaStop" in self.props: + return self.props["ThetaStop"] + else: + return + + @property + def theta_step(self): + """Set/Get the Far Field Theta Step Angle if Definition is Set to `Theta-Phi`.""" + + if "ThetaStep" in self.props: + return self.props["ThetaStep"] + else: + return + + @property + def phi_start(self): + """Set/Get the Far Field Phi Start Angle if Definition is Set to `Theta-Phi`.""" + + if "PhiStart" in self.props: + return self.props["PhiStart"] + else: + return + + @property + def phi_stop(self): + """Set/Get the Far Field Phi Stop Angle if Definition is Set to `Theta-Phi`.""" + + if "PhiStop" in self.props: + return self.props["PhiStop"] + else: + return + + @property + def phi_step(self): + """Set/Get the Far Field Phi Step Angle if Definition is Set to `Theta-Phi`.""" + + if "PhiStep" in self.props: + return self.props["PhiStep"] + else: + return + + @property + def azimuth_start(self): + """Set/Get the Far Field Azimuth Start Angle if Definition is Set to `Az Over El` or `El Over Az`.""" + + if "AzimuthStart" in self.props: + return self.props["AzimuthStart"] + else: + return + + @property + def azimuth_stop(self): + """Set/Get the Far Field Azimuth Stop Angle if Definition is Set to `Az Over El` or `El Over Az`.""" + + if "AzimuthStop" in self.props: + return self.props["AzimuthStop"] + else: + return + + @property + def azimuth_step(self): + """Set/Get the Far Field Azimuth Step Angle if Definition is Set to `Az Over El` or `El Over Az`.""" + + if "AzimuthStep" in self.props: + return self.props["AzimuthStep"] + else: + return + + @property + def elevation_start(self): + """Set/Get the Far Field Elevation Start Angle if Definition is Set to `Az Over El` or `El Over Az`.""" + + if "ElevationStart" in self.props: + return self.props["ElevationStart"] + else: + return + + @property + def elevation_stop(self): + """Set/Get the Far Field Elevation Stop Angle if Definition is Set to `Az Over El` or `El Over Az`.""" + + if "ElevationStop" in self.props: + return self.props["ElevationStop"] + else: + return + + @property + def elevation_step(self): + """Set/Get the Far Field Elevation Step Angle if Definition is Set to `Az Over El` or `El Over Az`.""" + + if "ElevationStep" in self.props: + return self.props["ElevationStep"] + else: + return + + @theta_start.setter + def theta_start(self, value): + if "ThetaStart" in self.props: + self.props["ThetaStart"] = _dim_arg(value, self.units) + self.update() + + @theta_stop.setter + def theta_stop(self, value): + if "ThetaStop" in self.props: + self.props["ThetaStop"] = _dim_arg(value, self.units) + self.update() + + @theta_step.setter + def theta_step(self, value): + if "ThetaStep" in self.props: + self.props["ThetaStep"] = _dim_arg(value, self.units) + self.update() + + @phi_start.setter + def phi_start(self, value): + if "PhiStart" in self.props: + self.props["PhiStart"] = _dim_arg(value, self.units) + self.update() + + @phi_stop.setter + def phi_stop(self, value): + if "PhiStop" in self.props: + self.props["PhiStop"] = _dim_arg(value, self.units) + self.update() + + @phi_step.setter + def phi_step(self, value): + if "PhiStep" in self.props: + self.props["PhiStep"] = _dim_arg(value, self.units) + self.update() + + @azimuth_start.setter + def azimuth_start(self, value): + if "AzimuthStart" in self.props: + self.props["AzimuthStart"] = _dim_arg(value, self.units) + self.update() + + @azimuth_stop.setter + def azimuth_stop(self, value): + if "AzimuthStop" in self.props: + self.props["AzimuthStop"] = _dim_arg(value, self.units) + self.update() + + @azimuth_step.setter + def azimuth_step(self, value): + if "AzimuthStep" in self.props: + self.props["AzimuthStep"] = _dim_arg(value, self.units) + self.update() + + @elevation_start.setter + def elevation_start(self, value): + if "ElevationStart" in self.props: + self.props["ElevationStart"] = _dim_arg(value, self.units) + self.update() + + @elevation_stop.setter + def elevation_stop(self, value): + if "ElevationStop" in self.props: + self.props["ElevationStop"] = _dim_arg(value, self.units) + self.update() + + @elevation_step.setter + def elevation_step(self, value): + if "ElevationStep" in self.props: + self.props["ElevationStep"] = _dim_arg(value, self.units) + self.update() + + +class NearFieldSetup(FieldSetup, object): + """Manages Near Field Component data and execution. + + Examples + -------- + in this example the rectangle1 returned object is a ``ansys.aedt.core.modules.boundary.NearFieldSetup`` + >>> from ansys.aedt.core import Hfss + >>> hfss = Hfss() + >>> rectangle1 = hfss.insert_near_field_rectangle() + """ + + def __init__(self, app, component_name, props, component_type): + FieldSetup.__init__(self, app, component_name, props, component_type) diff --git a/src/ansys/aedt/core/modules/boundary/icepak_boundary.py b/src/ansys/aedt/core/modules/boundary/icepak_boundary.py new file mode 100644 index 00000000000..0ec6e021afd --- /dev/null +++ b/src/ansys/aedt/core/modules/boundary/icepak_boundary.py @@ -0,0 +1,263 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from abc import abstractmethod + +from ansys.aedt.core.generic.general_methods import pyaedt_function_handler + + +class BoundaryDictionary: + """Handles Icepak transient and temperature-dependent boundary condition assignments. + + Parameters + ---------- + assignment_type : str + Type of assignment represented by the class. Options are `"Temp Dep"`` + and ``"Transient"``. + function_type : str + Variation function to assign. If ``assignment_type=="Temp Dep"``, + the function can only be ``"Piecewise Linear"``. Otherwise, the function can be + ``"Exponential"``, ``"Linear"``, ``"Piecewise Linear"``, ``"Power Law"``, + ``"Sinusoidal"``, and ``"Square Wave"``. + """ + + def __init__(self, assignment_type, function_type): + if assignment_type not in ["Temp Dep", "Transient"]: # pragma : no cover + raise AttributeError(f"The argument {assignment_type} for ``assignment_type`` is not valid.") + if assignment_type == "Temp Dep" and function_type != "Piecewise Linear": # pragma : no cover + raise AttributeError( + "Temperature dependent assignments only support" + ' ``"Piecewise Linear"`` as ``function_type`` argument.' + ) + self.assignment_type = assignment_type + self.function_type = function_type + + @property + def props(self): + """Dictionary that defines all the boundary condition properties.""" + return { + "Type": self.assignment_type, + "Function": self.function_type, + "Values": self._parse_value(), + } + + @abstractmethod + def _parse_value(self): + pass # pragma : no cover + + @pyaedt_function_handler() + def __getitem__(self, k): + return self.props.get(k) + + +class LinearDictionary(BoundaryDictionary): + """Manages linear conditions assignments, which are children of the ``BoundaryDictionary`` class. + + This class applies a condition ``y`` dependent on the time ``t``: + ``y=a+b*t`` + + Parameters + ---------- + intercept : str + Value of the assignment condition at the initial time, which + corresponds to the coefficient ``a`` in the formula. + slope : str + Slope of the assignment condition, which + corresponds to the coefficient ``b`` in the formula. + """ + + def __init__(self, intercept, slope): + super().__init__("Transient", "Linear") + self.intercept = intercept + self.slope = slope + + @pyaedt_function_handler() + def _parse_value(self): + return [self.slope, self.intercept] + + +class PowerLawDictionary(BoundaryDictionary): + """Manages power law condition assignments, which are children of the ``BoundaryDictionary`` class. + + This class applies a condition ``y`` dependent on the time ``t``: + ``y=a+b*t^c`` + + Parameters + ---------- + intercept : str + Value of the assignment condition at the initial time, which + corresponds to the coefficient ``a`` in the formula. + coefficient : str + Coefficient that multiplies the power term, which + corresponds to the coefficient ``b`` in the formula. + scaling_exponent : str + Exponent of the power term, which + corresponds to the coefficient ``c`` in the formula. + """ + + def __init__(self, intercept, coefficient, scaling_exponent): + super().__init__("Transient", "Power Law") + self.intercept = intercept + self.coefficient = coefficient + self.scaling_exponent = scaling_exponent + + @pyaedt_function_handler() + def _parse_value(self): + return [self.intercept, self.coefficient, self.scaling_exponent] + + +class ExponentialDictionary(BoundaryDictionary): + """Manages exponential condition assignments, which are children of the ``BoundaryDictionary`` class. + + This class applies a condition ``y`` dependent on the time ``t``: + ``y=a+b*exp(c*t)`` + + Parameters + ---------- + vertical_offset : str + Vertical offset summed to the exponential law, which + corresponds to the coefficient ``a`` in the formula. + coefficient : str + Coefficient that multiplies the exponential term, which + corresponds to the coefficient ``b`` in the formula. + exponent_coefficient : str + Coefficient in the exponential term, which + corresponds to the coefficient ``c`` in the formula. + """ + + def __init__(self, vertical_offset, coefficient, exponent_coefficient): + super().__init__("Transient", "Exponential") + self.vertical_offset = vertical_offset + self.coefficient = coefficient + self.exponent_coefficient = exponent_coefficient + + @pyaedt_function_handler() + def _parse_value(self): + return [self.vertical_offset, self.coefficient, self.exponent_coefficient] + + +class SinusoidalDictionary(BoundaryDictionary): + """Manages sinusoidal condition assignments, which are children of the ``BoundaryDictionary`` class. + + This class applies a condition ``y`` dependent on the time ``t``: + ``y=a+b*sin(2*pi(t-t0)/T)`` + + Parameters + ---------- + vertical_offset : str + Vertical offset summed to the sinusoidal law, which + corresponds to the coefficient ``a`` in the formula. + vertical_scaling : str + Coefficient that multiplies the sinusoidal term, which + corresponds to the coefficient ``b`` in the formula. + period : str + Period of the sinusoid, which + corresponds to the coefficient ``T`` in the formula. + period_offset : str + Offset of the sinusoid, which + corresponds to the coefficient ``t0`` in the formula. + """ + + def __init__(self, vertical_offset, vertical_scaling, period, period_offset): + super().__init__("Transient", "Sinusoidal") + self.vertical_offset = vertical_offset + self.vertical_scaling = vertical_scaling + self.period = period + self.period_offset = period_offset + + @pyaedt_function_handler() + def _parse_value(self): + return [self.vertical_offset, self.vertical_scaling, self.period, self.period_offset] + + +class SquareWaveDictionary(BoundaryDictionary): + """Manages square wave condition assignments, which are children of the ``BoundaryDictionary`` class. + + Parameters + ---------- + on_value : str + Maximum value of the square wave. + initial_time_off : str + Time after which the square wave assignment starts. + on_time : str + Time for which the square wave keeps the maximum value during one period. + off_time : str + Time for which the square wave keeps the minimum value during one period. + off_value : str + Minimum value of the square wave. + """ + + def __init__(self, on_value, initial_time_off, on_time, off_time, off_value): + super().__init__("Transient", "Square Wave") + self.on_value = on_value + self.initial_time_off = initial_time_off + self.on_time = on_time + self.off_time = off_time + self.off_value = off_value + + @pyaedt_function_handler() + def _parse_value(self): + return [self.on_value, self.initial_time_off, self.on_time, self.off_time, self.off_value] + + +class PieceWiseLinearDictionary(BoundaryDictionary): + """ + Manages dataset condition assignments, which are children of the ``BoundaryDictionary`` class. + + Parameters + ---------- + assignment_type : str + Type of assignment represented by the class. + Options are ``"Temp Dep"`` and ``"Transient"``. + ds : str + Dataset name to assign. + scale : str + Scaling factor for the y values of the dataset. + """ + + def __init__(self, assignment_type, ds, scale): + super().__init__(assignment_type, "Piecewise Linear") + self.scale = scale + self._assignment_type = assignment_type + self.dataset = ds + + @pyaedt_function_handler() + def _parse_value(self): + return [self.scale, self.dataset.name] + + @property + def dataset_name(self): + """Dataset name that defines the piecewise assignment.""" + return self.dataset.name + + +def _create_boundary(bound): + try: + if bound.create(): + bound._app._boundaries[bound.name] = bound + return bound + else: # pragma : no cover + raise Exception + except Exception: # pragma: no cover + return None diff --git a/src/ansys/aedt/core/modules/boundary/layout_boundary.py b/src/ansys/aedt/core/modules/boundary/layout_boundary.py new file mode 100644 index 00000000000..e5c3088b45f --- /dev/null +++ b/src/ansys/aedt/core/modules/boundary/layout_boundary.py @@ -0,0 +1,1342 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from ansys.aedt.core.generic.data_handlers import _dict2arg +from ansys.aedt.core.generic.data_handlers import random_string +from ansys.aedt.core.generic.general_methods import GrpcApiError +from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode +from ansys.aedt.core.modules.boundary.common import BoundaryCommon +from ansys.aedt.core.modules.boundary.common import BoundaryProps +from ansys.aedt.core.modules.boundary.common import disable_auto_update + + +class NativeComponentObject(BoundaryCommon, BinaryTreeNode): + """Manages Native Component data and execution. + + Parameters + ---------- + app : object + An AEDT application from ``ansys.aedt.core.application``. + component_type : str + Type of the component. + component_name : str + Name of the component. + props : dict + Properties of the boundary. + + Examples + -------- + in this example the par_beam returned object is a ``ansys.aedt.core.modules.boundary.NativeComponentObject`` + >>> from ansys.aedt.core import Hfss + >>> hfss = Hfss(solution_type="SBR+") + >>> ffd_file ="path/to/ffdfile.ffd" + >>> par_beam = hfss.create_sbr_file_based_antenna(ffd_file) + >>> par_beam.native_properties["Size"] = "0.1mm" + >>> par_beam.update() + >>> par_beam.delete() + """ + + def __init__(self, app, component_type, component_name, props): + self.auto_update = False + self._app = app + self._name = component_name + + self.__props = BoundaryProps( + self, + { + "TargetCS": "Global", + "SubmodelDefinitionName": self.name, + "ComponentPriorityLists": {}, + "NextUniqueID": 0, + "MoveBackwards": False, + "DatasetType": "ComponentDatasetType", + "DatasetDefinitions": {}, + "BasicComponentInfo": { + "ComponentName": self.name, + "Company": "", + "Company URL": "", + "Model Number": "", + "Help URL": "", + "Version": "1.0", + "Notes": "", + "IconType": "", + }, + "GeometryDefinitionParameters": {"VariableOrders": {}}, + "DesignDefinitionParameters": {"VariableOrders": {}}, + "MaterialDefinitionParameters": {"VariableOrders": {}}, + "DefReferenceCSID": 1, + "MapInstanceParameters": "DesignVariable", + "UniqueDefinitionIdentifier": "89d26167-fb77-480e-a7ab-" + + random_string(12, char_set="abcdef0123456789"), + "OriginFilePath": "", + "IsLocal": False, + "ChecksumString": "", + "ChecksumHistory": [], + "VersionHistory": [], + "NativeComponentDefinitionProvider": {"Type": component_type}, + "InstanceParameters": {"GeometryParameters": "", "MaterialParameters": "", "DesignParameters": ""}, + }, + ) + if props: + self._update_props(self.__props, props) + self.native_properties = self.__props["NativeComponentDefinitionProvider"] + self.auto_update = True + child_object = self._app.get_oo_object(self._app.oeditor, self._name) + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) + + @property + def props(self): + return self.__props + + @property + def name(self): + """Name of the object. + + Returns + ------- + str + Name of the object. + + """ + return self._name + + @name.setter + def name(self, component_name): + if component_name != self._name: + if component_name not in self._app.native_component_names: + self.properties["Name"] = component_name + self._app.native_components.update({component_name: self}) + del self._app.native_components[self._name] + del self._app.modeler.user_defined_components[self._name] + self._name = component_name + else: # pragma: no cover + self._app._logger.warning("Name %s already assigned in the design", component_name) + + @property + def definition_name(self): + """Definition name of the native component. + + Returns + ------- + str + Name of the native component. + + """ + definition_name = None + if self.props and "SubmodelDefinitionName" in self.props: + definition_name = self.props["SubmodelDefinitionName"] + return definition_name + + @property + def targetcs(self): + """Native Component Coordinate System. + + Returns + ------- + str + Native Component Coordinate System. + """ + if "TargetCS" in list(self.props.keys()): + return self.props["TargetCS"] + else: + return "Global" + + @targetcs.setter + def targetcs(self, cs): + self.props["TargetCS"] = cs + + def _update_props(self, d, u): + for k, v in u.items(): + if isinstance(v, dict): + if k not in d: + d[k] = {} + d[k] = self._update_props(d[k], v) + else: + d[k] = v + return d + + @pyaedt_function_handler() + def _get_args(self, props=None): + if props is None: + props = self.props + arg = ["NAME:InsertNativeComponentData"] + _dict2arg(props, arg) + return arg + + @pyaedt_function_handler() + def create(self): + """Create a Native Component in AEDT. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + try: + names = [i for i in self._app.excitations] + except GrpcApiError: # pragma: no cover + names = [] + self._name = self._app.modeler.oeditor.InsertNativeComponent(self._get_args()) + try: + a = [i for i in self._app.excitations if i not in names] + self.excitation_name = a[0].split(":")[0] + except (GrpcApiError, IndexError): + self.excitation_name = self.name + child_object = self._app.get_oo_object(self._app.oeditor, self._name) + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) + return True + + @pyaedt_function_handler() + def update(self): + """Update the Native Component in AEDT. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + + self.update_props = {} + self.update_props["DefinitionName"] = self.props["SubmodelDefinitionName"] + self.update_props["GeometryDefinitionParameters"] = self.props["GeometryDefinitionParameters"] + self.update_props["DesignDefinitionParameters"] = self.props["DesignDefinitionParameters"] + self.update_props["MaterialDefinitionParameters"] = self.props["MaterialDefinitionParameters"] + self.update_props["NextUniqueID"] = self.props["NextUniqueID"] + self.update_props["MoveBackwards"] = self.props["MoveBackwards"] + self.update_props["DatasetType"] = self.props["DatasetType"] + self.update_props["DatasetDefinitions"] = self.props["DatasetDefinitions"] + self.update_props["NativeComponentDefinitionProvider"] = self.props["NativeComponentDefinitionProvider"] + self.update_props["ComponentName"] = self.props["BasicComponentInfo"]["ComponentName"] + self.update_props["Company"] = self.props["BasicComponentInfo"]["Company"] + self.update_props["Model Number"] = self.props["BasicComponentInfo"]["Model Number"] + self.update_props["Help URL"] = self.props["BasicComponentInfo"]["Help URL"] + self.update_props["Version"] = self.props["BasicComponentInfo"]["Version"] + self.update_props["Notes"] = self.props["BasicComponentInfo"]["Notes"] + self.update_props["IconType"] = self.props["BasicComponentInfo"]["IconType"] + self._app.modeler.oeditor.EditNativeComponentDefinition(self._get_args(self.update_props)) + + return True + + @pyaedt_function_handler() + def delete(self): + """Delete the Native Component in AEDT. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + self._app.modeler.oeditor.Delete(["NAME:Selections", "Selections:=", self.name]) + for el in self._app._native_components: + if el.name == self.name: + self._app._native_components.remove(el) + del self._app.modeler.user_defined_components[self.name] + self._app.modeler.cleanup_objects() + return True + + +class BoundaryObject3dLayout(BoundaryCommon, BinaryTreeNode): + """Manages boundary data and execution for Hfss3dLayout. + + Parameters + ---------- + app : object + An AEDT application from ``ansys.aedt.core.application``. + name : str + Name of the boundary. + props : dict + Properties of the boundary. + boundarytype : str + Type of the boundary. + """ + + def __init__(self, app, name, props, boundarytype): + self.auto_update = False + self._app = app + self._name = name + self.__props = None + if props: + self.__props = BoundaryProps(self, props) + self.type = boundarytype + self._boundary_name = self.name + self.auto_update = True + if self._child_object: + BinaryTreeNode.__init__(self, self.name, self._child_object, False) + + @property + def _child_object(self): + + cc = self._app.odesign.GetChildObject("Excitations") + child_object = None + if self.name in cc.GetChildNames(): + child_object = self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) + elif self.name in self._app.odesign.GetChildObject("Excitations").GetChildNames(): + child_object = self._app.odesign.GetChildObject("Excitations").GetChildObject(self.name) + + if "Boundaries" in self._app.odesign.GetChildNames(): + cc = self._app.odesign.GetChildObject("Boundaries") + if self.name in cc.GetChildNames(): + child_object = self._app.odesign.GetChildObject("Boundaries").GetChildObject(self.name) + elif self.name in self._app.odesign.GetChildObject("Boundaries").GetChildNames(): + child_object = self._app.odesign.GetChildObject("Boundaries").GetChildObject(self.name) + + return child_object + + @property + def name(self): + """Boundary Name.""" + return self._name + + @name.setter + def name(self, value): + if "Port" in self.props: + self.auto_update = False + self.props["Port"] = value + self.auto_update = True + self.update() + self._name = value + + @property + def props(self): + """Excitation data. + + Returns + ------- + :class:BoundaryProps + """ + if self.__props: + return self.__props + props = self._get_boundary_data(self.name) + + if props: + self.__props = BoundaryProps(self, props[0]) + self._type = props[1] + return self.__props + + @pyaedt_function_handler() + def _get_args(self, props=None): + """Retrieve arguments. + + Parameters + ---------- + props : + The default is ``None``. + + Returns + ------- + list + List of boundary properties. + + """ + if props is None: + props = self.props + arg = ["NAME:" + self.name] + _dict2arg(props, arg) + return arg + + @pyaedt_function_handler() + def _refresh_properties(self): + if len(self._app.oeditor.GetProperties("EM Design", f"Excitations:{self.name}")) != len(self.props): + propnames = self._app.oeditor.GetProperties("EM Design", f"Excitations:{self.name}") + props = {} + for prop in propnames: + props[prop] = self._app.oeditor.GetPropertyValue("EM Design", f"Excitations:{self.name}", prop) + self.__props = BoundaryProps(self, props) + + @pyaedt_function_handler() + def update(self): + """Update the boundary. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + updated = False + for el in list(self.props.keys()): + if el == "Port" and self._name != self.props[el]: + self._app.oeditor.SetPropertyValue("EM Design", "Excitations:" + self.name, el, self.props[el]) + self._name = self.props[el] + elif el in self._app.oeditor.GetProperties("EM Design", f"Excitations:{self.name}") and self.props[ + el + ] != self._app.oeditor.GetPropertyValue("EM Design", "Excitations:" + self.name, el): + self._app.oeditor.SetPropertyValue("EM Design", "Excitations:" + self.name, el, self.props[el]) + updated = True + + if updated: + self._refresh_properties() + + return True + + +class NativeComponentPCB(NativeComponentObject, object): + """Manages native component PCB data and execution. + + Parameters + ---------- + app : object + AEDT application from the ``pyaedt.application`` class. + component_type : str + Type of the component. + component_name : str + Name of the component. + props : dict + Properties of the boundary. + """ + + def __init__(self, app, component_type, component_name, props): + NativeComponentObject.__init__(self, app, component_type, component_name, props) + + @pyaedt_function_handler() + @disable_auto_update + def set_resolution(self, resolution): + """Set metal fraction mapping resolution. + + Parameters + ------- + resolution : int + Resolution level. Accepted variables between 1 and 5. + + Returns + ------- + bool + True if successful, else False. + """ + if resolution < 1 or resolution > 5: + self._app.logger.error("Valid resolution values are between 1 and 5.") + return False + self.props["NativeComponentDefinitionProvider"]["Resolution"] = resolution + self.props["NativeComponentDefinitionProvider"]["CustomResolution"] = False + return True + + @pyaedt_function_handler() + @disable_auto_update + def set_custom_resolution(self, row, col): + """Set custom metal fraction mapping resolution. + + Parameters + ---------- + row : int + Resolution level in rows direction. + col : int + Resolution level in columns direction. + + Returns + ------- + bool + True if successful, else False. + """ + self.props["NativeComponentDefinitionProvider"]["CustomResolutionRow"] = row + self.props["NativeComponentDefinitionProvider"]["CustomResolutionCol"] = col + self.props["NativeComponentDefinitionProvider"]["CustomResolution"] = True + return True + + @property + def power(self): + """Power dissipation assigned to the PCB.""" + return self.props["NativeComponentDefinitionProvider"].get("Power", "0W") + + @pyaedt_function_handler() + @disable_auto_update + def set_high_side_radiation( + self, + enabled, + surface_material="Steel-oxidised-surface", + radiate_to_ref_temperature=False, + view_factor=1, + ref_temperature="AmbientTemp", + ): + """Set high side radiation properties. + + Parameters + ---------- + enabled : bool + Whether high side radiation is enabled. + surface_material : str, optional + Surface material to apply. Default is ``"Steel-oxidised-surface"``. + radiate_to_ref_temperature : bool, optional + Whether to radiate to a reference temperature instead of objects in the model. + Default is ``False``. + view_factor : float, optional + View factor to use for radiation computation if ``radiate_to_ref_temperature`` + is set to ``True``. Default is 1. + ref_temperature : str, optional + Reference temperature to use for radiation computation if + ``radiate_to_ref_temperature`` is set to True. Default is ``"AmbientTemp"``. + + Returns + ------- + bool + ``True`` if successful, else ``False``. + """ + high_rad = { + "Radiate": enabled, + "RadiateTo - High": "RefTemperature - High" if radiate_to_ref_temperature else "AllObjects - High", + "Surface Material - High": surface_material, + } + if radiate_to_ref_temperature: + high_rad["Ref. Temperature - High"] = (ref_temperature,) + high_rad["View Factor - High"] = view_factor + self.props["NativeComponentDefinitionProvider"]["HighSide"] = high_rad + return True + + @power.setter + @disable_auto_update + def power(self, value): + """Assign power dissipation to the PCB. + + Parameters + ---------- + value : str + Power to apply to the PCB. + """ + self.props["NativeComponentDefinitionProvider"]["Power"] = value + + @property + def force_source_solve(self): + """Force source solution option.""" + return self.props["NativeComponentDefinitionProvider"].get("DefnLink", {}).get("ForceSourceToSolve", False) + + @force_source_solve.setter + @disable_auto_update + def force_source_solve(self, val): + """Set Whether to force source solution. + + Parameters + ---------- + value : bool + Whether to force source solution. + """ + if not isinstance(val, bool): + self._app.logger.error("Only Boolean value can be accepted.") + return + return self.props["NativeComponentDefinitionProvider"]["DefnLink"].update({"ForceSourceToSolve": val}) + + @property + def preserve_partner_solution(self): + """Preserve parner solution option.""" + return self.props["NativeComponentDefinitionProvider"].get("DefnLink", {}).get("PreservePartnerSoln", False) + + @preserve_partner_solution.setter + @disable_auto_update + def preserve_partner_solution(self, val): + """Set Whether to preserve partner solution. + + Parameters + ---------- + val : bool + Whether to preserve partner solution. + """ + if not isinstance(val, bool): + self._app.logger.error("Only boolean can be accepted.") + return + return self.props["NativeComponentDefinitionProvider"]["DefnLink"].update({"PreservePartnerSoln": val}) + + @property + def included_parts(self): + """Parts options.""" + p = self.props["NativeComponentDefinitionProvider"].get("PartsChoice", 0) + if p == 0: + return None + elif p == 1: + return PCBSettingsDeviceParts(self, self._app) + elif p == 2: + return PCBSettingsPackageParts(self, self._app) + + @included_parts.setter + @disable_auto_update + def included_parts(self, value): + """Set PCB parts incusion option. + + Parameters + ---------- + value : str or int + Valid options are ``"None"``, ``"Device"``, and ``"Package"`` (or 0, 1, and 2 respectivaly) + """ + if value is None: + value = "None" + part_map = {"None": 0, "Device": 1, "Package": 2} + if not isinstance(value, int): + value = part_map.get(value, None) + if value is not None: + self.props["NativeComponentDefinitionProvider"]["PartsChoice"] = value + else: + self._app.logger.error( + 'Invalid part choice. Valid options are "None", "Device", and "Package" (or 0, 1, and 2 respectively).' + ) + + @pyaedt_function_handler() + @disable_auto_update + def set_low_side_radiation( + self, + enabled, + surface_material="Steel-oxidised-surface", + radiate_to_ref_temperature=False, + view_factor=1, + ref_temperature="AmbientTemp", + ): + """Set low side radiation properties. + + Parameters + ---------- + enabled : bool + Whether high side radiation is enabled. + surface_material : str, optional + Surface material to apply. Default is ``"Steel-oxidised-surface"``. + radiate_to_ref_temperature : bool, optional + Whether to radiate to a reference temperature instead of objects in the model. + Default is ``False``. + view_factor : float, optional + View factor to use for radiation computation if ``radiate_to_ref_temperature`` + is set to True. Default is 1. + ref_temperature : str, optional + Reference temperature to use for radiation computation if + ``radiate_to_ref_temperature`` is set to ``True``. Default is ``"AmbientTemp"``. + + Returns + ------- + bool + ``True`` if successful, else ``False``. + """ + low_side = { + "Radiate": enabled, + "RadiateTo": "RefTemperature - High" if radiate_to_ref_temperature else "AllObjects", + "Surface Material": surface_material, + } + if radiate_to_ref_temperature: + low_side["Ref. Temperature"] = (ref_temperature,) + low_side["View Factor"] = view_factor + self.props["NativeComponentDefinitionProvider"]["LowSide"] = low_side + return True + + @power.setter + @disable_auto_update + def power(self, value): + """Assign power dissipation to the PCB. + + Parameters + ---------- + value : str + Power to apply to the PCB. + """ + self.props["NativeComponentDefinitionProvider"]["Power"] = value + + @force_source_solve.setter + @disable_auto_update + def force_source_solve(self, val): + """Set Whether to force source solution. + + Parameters + ---------- + value : bool + Whether to force source solution. + """ + if not isinstance(val, bool): + self._app.logger.error("Only Boolean value can be accepted.") + return + return self.props["NativeComponentDefinitionProvider"]["DefnLink"].update({"ForceSourceToSolve": val}) + + @preserve_partner_solution.setter + @disable_auto_update + def preserve_partner_solution(self, val): + """Set Whether to preserve partner solution. + + Parameters + ---------- + val : bool + Whether to preserve partner solution. + """ + if not isinstance(val, bool): + self._app.logger.error("Only boolean can be accepted.") + return + return self.props["NativeComponentDefinitionProvider"]["DefnLink"].update({"PreservePartnerSoln": val}) + + @included_parts.setter + @disable_auto_update + def included_parts(self, value): + """Set PCB parts incusion option. + + Parameters + ---------- + value : str or int + Valid options are ``"None"``, ``"Device"``, and ``"Package"`` (or 0, 1, and 2 respectivaly) + """ + if value is None: + value = "None" + part_map = {"None": 0, "Device": 1, "Package": 2} + if not isinstance(value, int): + value = part_map.get(value, None) + if value is not None: + self.props["NativeComponentDefinitionProvider"]["PartsChoice"] = value + else: + self._app.logger.error( + 'Invalid part choice. Valid options are "None", "Device", and "Package" (or 0, 1, and 2 respectively).' + ) + + @pyaedt_function_handler() + def identify_extent_poly(self): + """Get polygon that defines board extent. + + Returns + ------- + str + Name of the polygon to include. + """ + from ansys.aedt.core import Hfss3dLayout + + prj = self.props["NativeComponentDefinitionProvider"]["DefnLink"]["Project"] + if prj == "This Project*": + prj = self._app.project_name + layout = Hfss3dLayout(project=prj, design=self.props["NativeComponentDefinitionProvider"]["DefnLink"]["Design"]) + layer = [o for o in layout.modeler.stackup.drawing_layers if o.type == "outline"][0] + outlines = [p for p in layout.modeler.polygons.values() if p.placement_layer == layer.name] + if len(outlines) > 1: + self._app.logger.info( + f"{outlines[0].name} automatically selected as ``extent_polygon``, " + f"pass ``extent_polygon`` argument explixitly to select a different one. " + f"Available choices are: {', '.join([o.name for o in outlines])}" + ) + elif len(outlines) == 0: + self._app.logger.error("No polygon found in the Outline layer.") + return False + return outlines[0].name + + @property + def board_cutout_material(self): + """Material applied to cutout regions.""" + return self.props["NativeComponentDefinitionProvider"].get("BoardCutoutMaterial", "air ") + + @property + def via_holes_material(self): + """Material applied to via hole regions.""" + return self.props["NativeComponentDefinitionProvider"].get("ViaHoleMaterial", "copper") + + @board_cutout_material.setter + @disable_auto_update + def board_cutout_material(self, value): + """Set material to apply to cutout regions. + + Parameters + ---------- + value : str + Material to apply to cutout regions. + """ + self.props["NativeComponentDefinitionProvider"]["BoardCutoutMaterial"] = value + + @via_holes_material.setter + @disable_auto_update + def via_holes_material(self, value): + """Set material to apply to via hole regions. + + Parameters + ---------- + value : str + Material to apply to via hole regions. + """ + self.props["NativeComponentDefinitionProvider"]["ViaHoleMaterial"] = value + + @pyaedt_function_handler() + @disable_auto_update + def set_board_extents(self, extent_type=None, extent_polygon=None): + """Set board extent. + + Parameters + ---------- + extent_type : str, optional + Extent definition of the PCB. Default is ``None`` in which case the 3D Layout extent + will be used. Other possible options are: ``"Bounding Box"`` or ``"Polygon"``. + extent_polygon : str, optional + Polygon name to use in the extent definition of the PCB. Default is ``None``. This + argument is mandatory if ``extent_type`` is ``"Polygon"``. + + Returns + ------- + bool + ``True`` if successful. ``False`` otherwise. + """ + if extent_type is None: + self.props["NativeComponentDefinitionProvider"]["Use3DLayoutExtents"] = True + else: + allowed_extent_types = ["Bounding Box", "Polygon"] + if extent_type not in allowed_extent_types: + self._app.logger.error( + f"Accepted argument for ``extent_type`` are:" + f" {', '.join(allowed_extent_types)}. {extent_type} provided" + ) + return False + self.props["NativeComponentDefinitionProvider"]["ExtentsType"] = extent_type + if extent_type == "Polygon": + if extent_polygon is None: + extent_polygon = self.identify_extent_poly() + if not extent_polygon: + return False + self.props["NativeComponentDefinitionProvider"]["OutlinePolygon"] = extent_polygon + return True + + +class PCBSettingsPackageParts(object): + """Handle package part settings of the PCB component. + + Parameters + ---------- + pcb_obj : :class:`ansys.aedt.core.modules.layout_boundary.NativeComponentPCB` + Inherited pcb object. + app : :class:`pyaedt.Icepak` + Inherited application object. + """ + + def __init__(self, pcb_obj, app): + self._app = app + self.pcb = pcb_obj + self._solderbumps_map = {"Lumped": "SbLumped", "Cylinders": "SbCylinder", "Boxes": "SbBlock"} + + def __eq__(self, other): + if isinstance(other, str): + return other == "Package" + elif isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + @pyaedt_function_handler() + @disable_auto_update + def set_solderballs_modeling(self, modeling=None): + """Set how to model solderballs. + + Parameters + ---------- + modeling : str, optional + Method for modeling solderballs located below the stackup. The default is + ``None``, in which case they are not modeled. Options for modeling are + ``"Boxes"``, ``"Cylinders"``, and ``"Lumped"``. + + Returns + ------- + bool + ``True`` if successful, ``False`` otherwise. + """ + update_properties = { + "CreateBottomSolderballs": modeling is not None, + "BottomSolderballsModelType": self._solderbumps_map[modeling], + } + + self.pcb.props["NativeComponentDefinitionProvider"].update(update_properties) + return True + + @pyaedt_function_handler() + @disable_auto_update + def set_connectors_modeling( + self, + modeling=None, + solderbumps_modeling="Boxes", + bondwire_material="Au-Typical", + bondwire_diameter="0.05mm", + ): + """Set how to model connectors. + + Parameters + ---------- + modeling : str, optional + Method for modeling connectors located above the stackup. The default is + ``None``, in which case they are not modeled. Options for modeling are + ``"Bondwire"`` and ``"Solderbump"``. + solderbumps_modeling : str, optional + Method for modeling solderbumps if ``modeling="Solderbump"``. + The default is ```"Boxes"``. Options are ``"Boxes"``, ``"Cylinders"``, + and ``"Lumped"``. + bondwire_material : str, optional + Bondwire material if ``modeling="Bondwire"``. The default is + ``"Au-Typical"``. + bondwire_diameter : str, optional + Bondwires diameter if ``modeling="Bondwire". + The default is ``"0.05mm"``. + + Returns + ------- + bool + ``True`` if successful, ``False`` otherwise. + """ + valid_connectors = ["Solderbump", "Bondwire"] + if modeling is not None and modeling not in valid_connectors: + self._app.logger.error( + f"{modeling} option is not supported. Use one of the following: {', '.join(valid_connectors)}" + ) + return False + if bondwire_material not in self._app.materials.mat_names_aedt: + self._app.logger.error(f"{bondwire_material} material is not present in the library.") + return False + if self._solderbumps_map.get(solderbumps_modeling, None) is None: + self._app.logger.error( + f"Solderbumps modeling option {solderbumps_modeling} is not valid. " + f"Available options are: {', '.join(list(self._solderbumps_map.keys()))}." + ) + return False + + update_properties = { + "CreateTopSolderballs": modeling is not None, + "TopConnectorType": modeling, + "TopSolderballsModelType": self._solderbumps_map[solderbumps_modeling], + "BondwireMaterial": bondwire_material, + "BondwireDiameter": bondwire_diameter, + } + + self.pcb.props["NativeComponentDefinitionProvider"].update(update_properties) + return True + + def __repr__(self): + return "Package" + + +class PCBSettingsDeviceParts(object): + """Handle device part settings of the PCB component. + + Parameters + ---------- + pcb_obj : :class:`ansys.aedt.core.modules.layout_boundary.NativeComponentPCB` + Inherited pcb object. + app : :class:`pyaedt.Icepak` + Inherited application object. + """ + + def __init__(self, pcb_obj, app): + self._app = app + self.pcb = pcb_obj + self._filter_map2name = {"Cap": "Capacitors", "Ind": "Inductors", "Res": "Resistors"} + + def __eq__(self, other): + if isinstance(other, str): + return other == "Device" + elif isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "Device" + + @property + @pyaedt_function_handler() + def simplify_parts(self): + """Get whether parts are simplified as cuboid.""" + return self.pcb.props["NativeComponentDefinitionProvider"]["ModelDeviceAsRect"] + + @simplify_parts.setter + @pyaedt_function_handler() + def simplify_parts(self, value): + """Set whether parts are simplified as cuboid. + + Parameters + ---------- + value : bool + Whether parts are simplified as cuboid. + """ + self.pcb.props["NativeComponentDefinitionProvider"]["ModelDeviceAsRect"] = value + + @property + @pyaedt_function_handler() + def surface_material(self): + """Surface material to apply to parts.""" + return self.pcb.props["NativeComponentDefinitionProvider"]["DeviceSurfaceMaterial"] + + @surface_material.setter + @pyaedt_function_handler() + def surface_material(self, value): + """Set surface material to apply to parts. + + Parameters + ---------- + value : str + Surface material to apply to parts. + """ + self.pcb.props["NativeComponentDefinitionProvider"]["DeviceSurfaceMaterial"] = value + + @property + @pyaedt_function_handler() + def footprint_filter(self): + """Minimum component footprint for filtering.""" + if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return None + if self._app.settings.aedt_version < "2024.2": + return None + return self.filters.get("FootPrint", {}).get("Value", None) + + @footprint_filter.setter + @pyaedt_function_handler() + @disable_auto_update + def footprint_filter(self, minimum_footprint): + """Set minimum component footprint for filtering. + + Parameters + ---------- + minimum_footprint : str + Value with unit of the minimum component footprint for filtering. + """ + if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return + if self._app.settings.aedt_version < "2024.2": + return + new_filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) + if "FootPrint" in new_filters: + new_filters.remove("FootPrint") + if minimum_footprint is not None: + new_filters.append("FootPrint") + self.pcb.props["NativeComponentDefinitionProvider"]["FootPrint"] = minimum_footprint + self.pcb.props["NativeComponentDefinitionProvider"]["Filters"] = new_filters + + @property + @pyaedt_function_handler() + def power_filter(self): + """Minimum component power for filtering.""" + if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return None + return self.filters.get("Power", {}).get("Value") + + @power_filter.setter + @pyaedt_function_handler() + @disable_auto_update + def power_filter(self, minimum_power): + """Set minimum component power for filtering. + + Parameters + ---------- + minimum_power : str + Value with unit of the minimum component power for filtering. + """ + if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return + new_filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) + if "Power" in new_filters: + new_filters.remove("Power") + if minimum_power is not None: + new_filters.append("Power") + self.pcb.props["NativeComponentDefinitionProvider"]["PowerVal"] = minimum_power + self.pcb.props["NativeComponentDefinitionProvider"]["Filters"] = new_filters + + @property + @pyaedt_function_handler() + def type_filters(self): + """Types of component that are filtered.""" + if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return None + return self.filters.get("Types") + + @type_filters.setter + @pyaedt_function_handler() + @disable_auto_update + def type_filters(self, object_type): + """Set types of component to filter. + + Parameters + ---------- + object_type : str or list + Types of object to filter. Options are ``"Capacitors"``, ``"Inductors"``, and ``"Resistors"``. + """ + if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return + if not isinstance(object_type, list): + object_type = [object_type] + if not all(o in self._filter_map2name.values() for o in object_type): + self._app.logger.error( + f"Accepted elements of the list are: {', '.join(list(self._filter_map2name.values()))}" + ) + else: + new_filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) + map2arg = {v: k for k, v in self._filter_map2name.items()} + for f, _ in self._filter_map2name.items(): + if f in new_filters: + new_filters.remove(f) + new_filters += [map2arg[o] for o in object_type] + self.pcb.props["NativeComponentDefinitionProvider"]["Filters"] = new_filters + + @property + @pyaedt_function_handler() + def height_filter(self): + """Minimum component height for filtering.""" + if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return None + return self.filters.get("Height", {}).get("Value", None) + + @height_filter.setter + @pyaedt_function_handler() + @disable_auto_update + def height_filter(self, minimum_height): + """Set minimum component height for filtering and whether to filter 2D objects. + + Parameters + ---------- + minimum_height : str + Value with unit of the minimum component power for filtering. + """ + if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return + new_filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) + if "Height" in new_filters: + new_filters.remove("Height") + if minimum_height is not None: + new_filters.append("Height") + self.pcb.props["NativeComponentDefinitionProvider"]["HeightVal"] = minimum_height + self.pcb.props["NativeComponentDefinitionProvider"]["Filters"] = new_filters + + @property + @pyaedt_function_handler() + def objects_2d_filter(self): + """Whether 2d objects are filtered.""" + if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return None + return self.filters.get("Exclude2DObjects", False) + + @objects_2d_filter.setter + @pyaedt_function_handler(filter="enable") + @disable_auto_update + def objects_2d_filter(self, enable): + """Set whether 2d objects are filtered. + + Parameters + ---------- + enable : bool + Whether 2d objects are filtered. + """ + if self.pcb.props["NativeComponentDefinitionProvider"]["PartsChoice"] != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return + new_filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) + if "HeightExclude2D" in new_filters: + new_filters.remove("HeightExclude2D") + if enable: + new_filters.append("HeightExclude2D") + self.pcb.props["NativeComponentDefinitionProvider"]["Filters"] = new_filters + + @property + @pyaedt_function_handler() + def filters(self): + """All active filters.""" + if self.pcb.props["NativeComponentDefinitionProvider"].get("PartsChoice", None) != 1: + self._app.logger.error( + "Device parts modeling is not active. No filtering or override option is available." + ) + return None + out_filters = {"Type": {"Capacitors": False, "Inductors": False, "Resistors": False}} + filters = self.pcb.props["NativeComponentDefinitionProvider"].get("Filters", []) + filter_map2type = { + "Cap": "Type", + "FootPrint": "FootPrint", + "Height": "Height", + "HeightExclude2D": None, + "Ind": "Type", + "Power": "Power", + "Res": "Type", + } + filter_map2val = {"FootPrint": "FootPrint", "Height": "HeightVal", "Power": "PowerVal"} + for f in filters: + if filter_map2type[f] == "Type": + out_filters["Type"][self._filter_map2name[f]] = True + elif filter_map2type[f] is not None: + out_filters[f] = {"Value": filter_map2val[f]} + if "HeightExclude2D" in filters: + out_filters["Exclude2DObjects"] = True + return out_filters + + @property + @pyaedt_function_handler() + def overridden_components(self): + """All overridden components.""" + override_component = ( + self.pcb.props["NativeComponentDefinitionProvider"] + .get("instanceOverridesMap", {}) + .get("oneOverrideBlk", []) + ) + return {o["overrideName"]: o["overrideProps"] for o in override_component} + + @pyaedt_function_handler() + def _override_common( + self, + map_name, + package=None, + part=None, + reference_designator=None, + filter_component=False, + power=None, + r_jb=None, + r_jc=None, + height=None, + ): + override_component = ( + self.pcb.props["NativeComponentDefinitionProvider"] + .get(map_name, {}) # "instanceOverridesMap" + .get("oneOverrideBlk", []) + ) + if map_name == "instanceOverridesMap": + for o in override_component: + if o["overrideName"] == reference_designator: + override_component.remove(o) + elif map_name == "definitionOverridesMap": # pragma: no cover + for o in override_component: + if o["overridePartNumberName"] == part: + override_component.remove(o) + new_filter = {} + if filter_component or any(override_val is not None for override_val in [power, r_jb, r_jc, height]): + if map_name == "instanceOverridesMap": + new_filter.update({"overrideName": reference_designator}) + elif map_name == "definitionOverridesMap": # pragma: no cover + new_filter.update({"overridePartNumberName": part, "overrideGeometryName": package}) + new_filter.update( + { + "overrideProps": { + "isFiltered": filter_component, + "isOverridePower": power is not None, + "isOverrideThetaJb": r_jb is not None, + "isOverrideThetaJc": r_jc is not None, + "isOverrideHeight": height is not None, + "powerOverride": power if power is not None else "nan", + "thetaJbOverride": r_jb if r_jb is not None else "nan", + "thetaJcOverride": r_jc if r_jc is not None else "nan", + "heightOverride": height if height is not None else "nan", + }, + } + ) + override_component.append(new_filter) + self.pcb.props["NativeComponentDefinitionProvider"][map_name] = {"oneOverrideBlk": override_component} + return True + + @pyaedt_function_handler() + @disable_auto_update + def override_definition(self, package, part, filter_component=False, power=None, r_jb=None, r_jc=None, height=None): + """Set component override. + + Parameters + ---------- + package : str + Package name of the definition to override. + part : str + Part name of the definition to override. + filter_component : bool, optional + Whether to filter out the component. The default is ``False``. + power : str, optional + Override component power. Default is ``None``, in which case the power is not overridden. + r_jb : str, optional + Override component r_jb value. Default is ``None``, in which case the resistance is not overridden. + r_jc : str, optional + Override component r_jc value. Default is ``None``, in which case the resistance is not overridden. + height : str, optional + Override component height value. Default is ``None``, in which case the height is not overridden. + + Returns + ------- + bool + ``True`` if successful, ``False`` otherwise. + """ + if self._app.settings.aedt_version < "2024.2": + self._app.logger.error( + "This method is available only with AEDT 2024 R2 or later. Use 'override_instance()' method instead." + ) + return False + return self._override_common( # pragma : no cover + "definitionOverridesMap", + package=package, + part=part, + filter_component=filter_component, + power=power, + r_jb=r_jb, + r_jc=r_jc, + height=height, + ) + + @pyaedt_function_handler() + @disable_auto_update + def override_instance( + self, reference_designator, filter_component=False, power=None, r_jb=None, r_jc=None, height=None + ): + """Set instance override. + + Parameters + ---------- + reference_designator : str + Reference designator of the instance to override. + filter_component : bool, optional + Whether to filter out the component. The default is ``False``. + power : str, optional + Override component power. The default is ``None``, in which case the power is not overridden. + r_jb : str, optional + Override component r_jb value. The default is ``None``, in which case the resistance is not overridden. + r_jc : str, optional + Override component r_jc value. The default is ``None``, in which case the resistance is not overridden. + height : str, optional + Override component height value. The default is ``None``, in which case the height is not overridden. + + Returns + ------- + bool + ``True`` if successful, ``False`` otherwise. + """ + return self._override_common( + "instanceOverridesMap", + reference_designator=reference_designator, + filter_component=filter_component, + power=power, + r_jb=r_jb, + r_jc=r_jc, + height=height, + ) diff --git a/src/ansys/aedt/core/modules/boundary/maxwell_boundary.py b/src/ansys/aedt/core/modules/boundary/maxwell_boundary.py new file mode 100644 index 00000000000..28feac03a5c --- /dev/null +++ b/src/ansys/aedt/core/modules/boundary/maxwell_boundary.py @@ -0,0 +1,352 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from ansys.aedt.core.generic.data_handlers import _dict2arg +from ansys.aedt.core.generic.general_methods import generate_unique_name +from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode +from ansys.aedt.core.modules.boundary.common import BoundaryCommon +from ansys.aedt.core.modules.boundary.common import BoundaryProps + + +class MaxwellParameters(BoundaryCommon, BinaryTreeNode): + """Manages parameters data and execution. + + Parameters + ---------- + app : :class:`ansys.aedt.core.maxwell.Maxwell3d`, :class:`ansys.aedt.core.maxwell.Maxwell2d` + Either ``Maxwell3d`` or ``Maxwell2d`` application. + name : str + Name of the boundary. + props : dict, optional + Properties of the boundary. + boundarytype : str, optional + Type of the boundary. + + Examples + -------- + + Create a matrix in Maxwell3D return a ``ansys.aedt.core.modules.boundary.BoundaryObject`` + + >>> from ansys.aedt.core import Maxwell2d + >>> maxwell_2d = Maxwell2d() + >>> coil1 = maxwell_2d.modeler.create_rectangle([8.5,1.5, 0],[8, 3],True,"Coil_1","vacuum") + >>> coil2 = maxwell_2d.modeler.create_rectangle([8.5,1.5, 0],[8, 3],True,"Coil_2","vacuum") + >>> maxwell_2d.assign_matrix(["Coil_1", "Coil_2"]) + """ + + def __init__(self, app, name, props=None, boundarytype=None): + self.auto_update = False + self._app = app + self._name = name + self.__props = BoundaryProps(self, props) if props else {} + self.type = boundarytype + self._boundary_name = self.name + self.auto_update = True + self.__reduced_matrices = None + self.matrix_assignment = None + if self._child_object: + BinaryTreeNode.__init__(self, self.name, self._child_object, False) + + @property + def reduced_matrices(self): + """List of reduced matrix groups for the parent matrix. + + Returns + ------- + dict + Dictionary of reduced matrices where the key is the name of the parent matrix + and the values are in a list of reduced matrix groups. + """ + if self._app.solution_type == "EddyCurrent": + self.__reduced_matrices = {} + cc = self._app.odesign.GetChildObject("Parameters") + parents = cc.GetChildNames() + if self.name in parents: + parent_object = self._app.odesign.GetChildObject("Parameters").GetChildObject(self.name) + parent_type = parent_object.GetPropValue("Type") + if parent_type == "Matrix": + self.matrix_assignment = parent_object.GetPropValue("Selection").split(",") + child_names = parent_object.GetChildNames() + self.__reduced_matrices = [] + for r in child_names: + self.__reduced_matrices.append(MaxwellMatrix(self._app, self.name, r)) + return self.__reduced_matrices + + @property + def _child_object(self): + + cc = self._app.odesign.GetChildObject("Parameters") + child_object = None + if self.name in cc.GetChildNames(): + child_object = self._app.odesign.GetChildObject("Parameters").GetChildObject(self.name) + elif self.name in self._app.odesign.GetChildObject("Parameters").GetChildNames(): + child_object = self._app.odesign.GetChildObject("Parameters").GetChildObject(self.name) + + return child_object + + @property + def props(self): + """Maxwell parameter data. + + Returns + ------- + :class:BoundaryProps + """ + if self.__props: + return self.__props + props = self._get_boundary_data(self.name) + + if props: + self.__props = BoundaryProps(self, props[0]) + self._type = props[1] + return self.__props + + @property + def name(self): + """Boundary name.""" + return self._name + + @name.setter + def name(self, value): + self._name = value + self.update() + + @pyaedt_function_handler() + def _get_args(self, props=None): + """Retrieve arguments. + + Parameters + ---------- + props : + The default is ``None``. + + Returns + ------- + list + List of boundary properties. + + """ + if props is None: + props = self.props + arg = ["NAME:" + self.name] + _dict2arg(props, arg) + return arg + + @pyaedt_function_handler() + def create(self): + """Create a boundary. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + if self.type == "Matrix": + self._app.o_maxwell_parameters.AssignMatrix(self._get_args()) + elif self.type == "Torque": + self._app.o_maxwell_parameters.AssignTorque(self._get_args()) + elif self.type == "Force": + self._app.o_maxwell_parameters.AssignForce(self._get_args()) + elif self.type == "LayoutForce": + self._app.o_maxwell_parameters.AssignLayoutForce(self._get_args()) + else: + return False + if self._child_object: + BinaryTreeNode.__init__(self, self.name, self._child_object, False) + return True + + @pyaedt_function_handler() + def update(self): + """Update the boundary. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + """ + if self.type == "Matrix": + self._app.o_maxwell_parameters.EditMatrix(self._boundary_name, self._get_args()) + elif self.type == "Force": + self._app.o_maxwell_parameters.EditForce(self._boundary_name, self._get_args()) + elif self.type == "Torque": + self._app.o_maxwell_parameters.EditTorque(self._boundary_name, self._get_args()) + else: + return False + self._boundary_name = self.name + return True + + @pyaedt_function_handler() + def _create_matrix_reduction(self, red_type, sources, matrix_name=None, join_name=None): + if not self._app.solution_type == "EddyCurrent": + self._app.logger.error("Matrix reduction is possible only in Eddy current solvers.") + return False, False + if not matrix_name: + matrix_name = generate_unique_name("ReducedMatrix", n=3) + if not join_name: + join_name = generate_unique_name("Join" + red_type, n=3) + try: + self._app.o_maxwell_parameters.AddReduceOp( + self.name, + matrix_name, + ["NAME:" + join_name, "Type:=", "Join in " + red_type, "Sources:=", ",".join(sources)], + ) + return matrix_name, join_name + except Exception: + self._app.logger.error("Failed to create Matrix Reduction") + return False, False + + @pyaedt_function_handler() + def join_series(self, sources, matrix_name=None, join_name=None): + """ + + Parameters + ---------- + sources : list + Sources to be included in matrix reduction. + matrix_name : str, optional + name of the string to create. + join_name : str, optional + Name of the Join operation. + + Returns + ------- + (str, str) + Matrix name and Joint name. + + """ + return self._create_matrix_reduction( + red_type="Series", sources=sources, matrix_name=matrix_name, join_name=join_name + ) + + @pyaedt_function_handler() + def join_parallel(self, sources, matrix_name=None, join_name=None): + """ + + Parameters + ---------- + sources : list + Sources to be included in matrix reduction. + matrix_name : str, optional + name of the string to create. + join_name : str, optional + Name of the Join operation. + + Returns + ------- + (str, str) + Matrix name and Joint name. + + """ + return self._create_matrix_reduction( + red_type="Parallel", sources=sources, matrix_name=matrix_name, join_name=join_name + ) + + +class MaxwellMatrix(object): + def __init__(self, app, parent_name, reduced_name): + self._app = app + self.parent_matrix = parent_name + self.name = reduced_name + self.__sources = None + + @property + def sources(self): + """List of matrix sources.""" + if self._app.solution_type == "EddyCurrent": + sources = ( + self._app.odesign.GetChildObject("Parameters") + .GetChildObject(self.parent_matrix) + .GetChildObject(self.name) + .GetChildNames() + ) + self.__sources = {} + for s in sources: + excitations = ( + self._app.odesign.GetChildObject("Parameters") + .GetChildObject(self.parent_matrix) + .GetChildObject(self.name) + .GetChildObject(s) + .GetPropValue("Source") + ) + self.__sources[s] = excitations + return self.__sources + + @pyaedt_function_handler() + def update(self, old_source, source_type, new_source=None, new_excitations=None): + """Update the reduced matrix. + + Parameters + ---------- + old_source : str + Original name of the source to update. + source_type : str + Source type, which can be ``Series`` or ``Parallel``. + new_source : str, optional + New name of the source to update. + The default value is the old source name. + new_excitations : str, optional + List of excitations to include in the matrix reduction. + The default values are excitations included in the source to update. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ + if old_source not in self.sources: + self._app.logger.error("Source does not exist.") + return False + else: + new_excitations = self.sources[old_source] if not new_excitations else new_excitations + if source_type.lower() not in ["series", "parallel"]: + self._app.logger.error("Join type not valid.") + return False + if not new_source: + new_source = old_source + args = ["NAME:" + new_source, "Type:=", "Join in " + source_type, "Sources:=", new_excitations] + self._app.o_maxwell_parameters.EditReduceOp(self.parent_matrix, self.name, old_source, args) + return True + + @pyaedt_function_handler() + def delete(self, source): + """Delete a specified source in a reduced matrix. + + Parameters + ---------- + source : string + Name of the source to delete. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ + if source not in self.sources: + self._app.logger.error("Invalid source name.") + return False + self._app.o_maxwell_parameters.DeleteReduceOp(self.parent_matrix, self.name, source) + return True diff --git a/src/ansys/aedt/core/modules/boundary/q3d_boundary.py b/src/ansys/aedt/core/modules/boundary/q3d_boundary.py new file mode 100644 index 00000000000..5745ca23a28 --- /dev/null +++ b/src/ansys/aedt/core/modules/boundary/q3d_boundary.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from ansys.aedt.core.generic.constants import CATEGORIESQ3D +from ansys.aedt.core.generic.general_methods import filter_tuple +from ansys.aedt.core.generic.general_methods import generate_unique_name +from ansys.aedt.core.generic.general_methods import pyaedt_function_handler + + +class Matrix(object): + """Manages Matrix in Q3d and Q2d Projects. + + Examples + -------- + + + """ + + def __init__(self, app, name, operations=None): + self._app = app + self.omatrix = self._app.omatrix + self.name = name + self._sources = [] + if operations: + if isinstance(operations, list): + self._operations = operations + else: + self._operations = [operations] + self.CATEGORIES = CATEGORIESQ3D() + + @pyaedt_function_handler() + def sources(self, is_gc_sources=True): + """List of matrix sources. + + Parameters + ---------- + is_gc_sources : bool, + In Q3d, define if to return GC sources or RL sources. Default `True`. + + Returns + ------- + List + """ + if self.name in list(self._app.omatrix.ListReduceMatrixes()): + if self._app.design_type == "Q3D Extractor": + self._sources = list(self._app.omatrix.ListReduceMatrixReducedSources(self.name, is_gc_sources)) + else: + self._sources = list(self._app.omatrix.ListReduceMatrixReducedSources(self.name)) + return self._sources + + @pyaedt_function_handler() + def get_sources_for_plot( + self, + get_self_terms=True, + get_mutual_terms=True, + first_element_filter=None, + second_element_filter=None, + category="C", + ): + """Return a list of source of specified matrix ready to be used in plot reports. + + Parameters + ---------- + get_self_terms : bool + Either if self terms have to be returned or not. + get_mutual_terms : bool + Either if mutual terms have to be returned or not. + first_element_filter : str, optional + Filter to apply to first element of equation. It accepts `*` and `?` as special characters. + second_element_filter : str, optional + Filter to apply to second element of equation. It accepts `*` and `?` as special characters. + category : str + Plot category name as in the report. Eg. "C" is category Capacitance. + Matrix `CATEGORIES` property can be used to map available categories. + + Returns + ------- + list + + Examples + -------- + >>> from ansys.aedt.core import Q3d + >>> q3d = Q3d(project_path) + >>> q3d.matrices[0].get_sources_for_plot(first_element_filter="Bo?1", + ... second_element_filter="GND*", category="DCL") + """ + if not first_element_filter: + first_element_filter = "*" + if not second_element_filter: + second_element_filter = "*" + is_cg = False + if category in [self.CATEGORIES.Q3D.C, self.CATEGORIES.Q3D.G]: + is_cg = True + list_output = [] + if get_self_terms: + for el in self.sources(is_gc_sources=is_cg): + value = f"{category}({el},{el})" + if filter_tuple(value, first_element_filter, second_element_filter): + list_output.append(value) + if get_mutual_terms: + for el1 in self.sources(is_gc_sources=is_cg): + for el2 in self.sources(is_gc_sources=is_cg): + if el1 != el2: + value = f"{category}({el1},{el2})" + if filter_tuple(value, first_element_filter, second_element_filter): + list_output.append(value) + return list_output + + @property + def operations(self): + """List of matrix operations. + + Returns + ------- + List + """ + if self.name in list(self._app.omatrix.ListReduceMatrixes()): + self._operations = self._app.omatrix.ListReduceMatrixOperations(self.name) + return self._operations + + @pyaedt_function_handler() + def create( + self, + source_names=None, + new_net_name=None, + new_source_name=None, + new_sink_name=None, + ): + """Create a new matrix. + + Parameters + ---------- + source_names : str, list + List or str containing the content of the matrix reduction (eg. source name). + new_net_name : str, optional + Name of the new net. The default is ``None``. + new_source_name : str, optional + Name of the new source. The default is ``None``. + new_sink_name : str, optional + Name of the new sink. The default is ``None``. + + Returns + ------- + bool + `True` if succeeded. + """ + if not isinstance(source_names, list) and source_names: + source_names = [source_names] + + command = self._write_command(source_names, new_net_name, new_source_name, new_sink_name) + self.omatrix.InsertRM(self.name, command) + return True + + @pyaedt_function_handler() + def delete(self): + """Delete current matrix. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + """ + self.omatrix.DeleteRM(self.name) + for el in self._app.matrices: + if el.name == self.name: + self._app.matrices.remove(el) + return True + + @pyaedt_function_handler() + def add_operation( + self, + operation_type, + source_names=None, + new_net_name=None, + new_source_name=None, + new_sink_name=None, + ): + """Add a new operation to existing matrix. + + Parameters + ---------- + operation_type : str + Operation to perform + source_names : str, list + List or str containing the content of the matrix reduction (eg. source name). + new_net_name : str, optional + Name of the new net. The default is ``None``. + new_source_name : str, optional + Name of the new source. The default is ``None``. + new_sink_name : str, optional + Name of the new sink. The default is ``None``. + + Returns + ------- + bool + `True` if succeeded. + """ + self._operations.append(operation_type) + if not isinstance(source_names, list) and source_names: + source_names = [source_names] + + if not new_net_name: + new_net_name = generate_unique_name("Net") + + if not new_source_name: + new_source_name = generate_unique_name("Source") + + if not new_sink_name: + new_sink_name = generate_unique_name("Sink") + + command = self._write_command(source_names, new_net_name, new_source_name, new_sink_name) + self.omatrix.RMAddOp(self.name, command) + return True + + @pyaedt_function_handler() + def _write_command(self, source_names, new_name, new_source, new_sink): + if self._operations[-1] == "JoinSeries": + command = f"""{self._operations[-1]}('{new_name}', '{"', '".join(source_names)}')""" + elif self._operations[-1] == "JoinParallel": + command = ( + f"""{self._operations[-1]}('{new_name}', '{new_source}', '{new_sink}', '{"', '".join(source_names)}')""" + ) + elif self._operations[-1] == "JoinSelectedTerminals": + command = f"""{self._operations[-1]}('', '{"', '".join(source_names)}')""" + elif self._operations[-1] == "FloatInfinity": + command = "FloatInfinity()" + elif self._operations[-1] == "AddGround": + command = f"""{self._operations[-1]}(SelectionArray[{len(source_names)}: '{"', '".join(source_names)}'], + OverrideInfo())""" + elif ( + self._operations[-1] == "SetReferenceGround" + or self._operations[-1] == "SetReferenceGround" + or self._operations[-1] == "Float" + ): + command = f"""{self._operations[-1]}(SelectionArray[{len(source_names)}: '{"', '".join(source_names)}'], + OverrideInfo())""" + elif self._operations[-1] == "Parallel" or self._operations[-1] == "DiffPair": + id_ = 0 + for el in self._app.boundaries: + if el.name == source_names[0]: + id_ = self._app.modeler[el.props["Objects"][0]].id + command = f"""{self._operations[-1]}(SelectionArray[{len(source_names)}: '{"', '".join(source_names)}'], + OverrideInfo({id_}, '{new_name}'))""" + else: + command = f"""{self._operations[-1]}('{"', '".join(source_names)}')""" + return command diff --git a/src/ansys/aedt/core/modules/material.py b/src/ansys/aedt/core/modules/material.py index 9d44c929e83..160c1ef3777 100644 --- a/src/ansys/aedt/core/modules/material.py +++ b/src/ansys/aedt/core/modules/material.py @@ -50,8 +50,7 @@ class MatProperties(object): - """Contains a list of constant names for all materials with - mappings to their internal XML names. + """Contains a list of constant names for all materials with mappings to their internal XML names. Internal names are used in scripts, and XML names are used in the XML syntax. """ @@ -150,7 +149,8 @@ class MatProperties(object): @classmethod def wb_to_aedt_name(cls, wb_name): """Retrieve the corresponding AEDT property name for the specified Workbench property name. - The workbench names are specified in ``MatProperties.workbench_name``. + + The Workbench names are specified in ``MatProperties.workbench_name``. The AEDT names are specified in ``MatProperties.aedtname``. Parameters @@ -162,7 +162,6 @@ def wb_to_aedt_name(cls, wb_name): ------- str AEDT name of the property. - """ return cls.aedtname[cls.workbench_name.index(wb_name)] @@ -179,7 +178,6 @@ def get_defaultunit(cls, aedtname=None): ------- str Default unit if it exists. - """ if aedtname: return cls.defaultunit[cls.aedtname.index(aedtname)] @@ -199,7 +197,6 @@ def get_defaultvalue(cls, aedtname): ------- float Default value if it exists. - """ if aedtname: return cls.defaultvalue[cls.aedtname.index(aedtname)] @@ -212,7 +209,6 @@ class SurfMatProperties(object): mappings to their internal XML names. Internal names are used in scripts, and XML names are used in the XML syntax. - """ aedtname = [ @@ -789,18 +785,15 @@ def add_thermal_modifier_closed_form( References ---------- - >>> oDefinitionManager.EditMaterial Examples -------- - >>> from ansys.aedt.core import Hfss >>> hfss = Hfss(version="2021.2") >>> mat1 = hfss.materials.add_material("new_copper2") >>> mat1.permittivity.add_thermal_modifier_closed_form(c1 = 1e-3) """ - if index > len(self._property_value): self.logger.error( "Wrong index number. Index must be 0 for simple or nonlinear properties," @@ -942,6 +935,7 @@ def add_thermal_modifier_closed_form( @pyaedt_function_handler() def _set_non_linear(self, x_unit=None, y_unit=None): """Enable non-linear material. + This is a private method, and should not be used directly. Parameters @@ -965,7 +959,6 @@ def _set_non_linear(self, x_unit=None, y_unit=None): >>> mat = hfss.materials.add_material("newMat") >>> b_h_dataset = [[b, h] for b, h in zip(B_value, H_value)] >>> mat.permeability = b_h_dataset - """ if self.name not in ["permeability", "conductivity", "permittivity"]: self.logger.error( @@ -1225,18 +1218,15 @@ def add_spatial_modifier_dataset(self, dataset, index=0): References ---------- - >>> oDefinitionManager.EditMaterial Examples -------- - >>> from ansys.aedt.core import Hfss >>> hfss = Hfss(version="2021.2") >>> mat1 = hfss.materials.add_material("new_copper2") >>> mat1.add_spatial_modifier_dataset("$ds1") """ - formula = f"clp({dataset}, X,Y,Z)" self._property_value[index].spatialmodifier = formula return self._add_spatial_modifier(formula, index) @@ -1335,7 +1325,6 @@ def _update_props(self, propname, propvalue, update_aedt=True): update_aedt : bool, optional Whether to update the property in AEDT. The default is ``True``. """ - try: material_props = getattr(self, propname) material_props_type = material_props.type @@ -1518,8 +1507,9 @@ def __init__(self, materiallib, name, props=None, material_update=True): @property def material_appearance(self): - """Material appearance specified as a list where the first three items are - RGB color and the fourth one is transparency. + """Material appearance specified as a list. + + The first three items are RGB color and the fourth one is transparency. Returns ------- @@ -2765,7 +2755,6 @@ def set_djordjevic_sarkar_model( bool ``True`` if successful, ``False`` otherwise. """ - # K = f"({dk} * {df} - {sigma_dc} / (2 * pi * {i_freq} * e0)) / atan({freq_hi} / {i_freq})" K = f"({dk} * {df} - {sigma_dc} / (2 * pi * {frequency} * e0)) / atan({freq_hi} / {frequency})" epsilon_inf = f"({dk} - {K} / 2 * ln({freq_hi}**2 / {frequency}**2 + 1))" @@ -2790,11 +2779,9 @@ def update(self): References ---------- - >>> oDefinitionManager.AddMaterial >>> oDefinitionManager.EditMaterial """ - args = self._get_args() if not self._does_material_exists(self.name): self.odefinition_manager.AddMaterial(args) diff --git a/src/ansys/aedt/core/modules/material_lib.py b/src/ansys/aedt/core/modules/material_lib.py index 1037e233f76..1d72f91cf7d 100644 --- a/src/ansys/aedt/core/modules/material_lib.py +++ b/src/ansys/aedt/core/modules/material_lib.py @@ -22,9 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -""" -This module contains the `Materials` class. -""" +"""Module containing the `Materials` class.""" from __future__ import absolute_import # noreorder import copy @@ -735,7 +733,7 @@ def _aedmattolibrary(self, matname): @pyaedt_function_handler(full_json_path="output_file") def export_materials_to_file(self, output_file): - """Export all materials to a JSON or TOML file. + """Export all materials to a JSON or TOML file. Parameters ---------- @@ -809,9 +807,7 @@ def import_materials_from_file(self, input_file=None, **kwargs): Returns ------- List of :class:`ansys.aedt.core.modules.material.Material` - """ - if "full_json_path" in kwargs and kwargs["full_json_path"] is not None: # pragma: no cover warnings.warn( "``full_json_path`` was deprecated in 0.8.1. Use ``full_path`` instead.", diff --git a/src/ansys/aedt/core/modules/mesh.py b/src/ansys/aedt/core/modules/mesh.py index 69a4c34696d..5121f2857d9 100644 --- a/src/ansys/aedt/core/modules/mesh.py +++ b/src/ansys/aedt/core/modules/mesh.py @@ -22,9 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -""" -This module contains the `Mesh` class. -""" +"""This module contains the `Mesh` class.""" from __future__ import absolute_import # noreorder @@ -38,6 +36,7 @@ from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.general_methods import settings from ansys.aedt.core.generic.load_aedt_file import load_keyword_in_aedt_file +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode from ansys.aedt.core.modeler.cad.elements_3d import EdgePrimitive from ansys.aedt.core.modeler.cad.elements_3d import FacePrimitive from ansys.aedt.core.modeler.cad.elements_3d import VertexPrimitive @@ -108,7 +107,7 @@ def _setitem_without_update(self, key, value): dict.__setitem__(self, key, value) -class MeshOperation(object): +class MeshOperation(BinaryTreeNode): """MeshOperation class. Parameters @@ -120,16 +119,70 @@ class MeshOperation(object): def __init__(self, mesh, name, props, meshoptype): self._mesh = mesh self._app = self._mesh._app - self.props = MeshProps(self, props) - self.type = meshoptype + self._legacy_props = None + if props is not None: + self._legacy_props = MeshProps(self, props) + self._type = meshoptype self._name = name self.auto_update = True + child_object = self._app.get_oo_object(self._app.odesign, f"Mesh/{self._name}") + + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) + + @property + def type(self): + if not self._type: + self._type = self.props.get("Type", None) + return self._type + + @property + def props(self): + if not self._legacy_props: + props = {} + for k, v in self.properties.items(): + props[k] = v + if "Assignment" in props: + assignment = props["Assignment"] + if "Face_" in assignment: + props["Faces"] = [ + int(i.replace("Face_", "")) for i in assignment.split("(")[1].split(")")[0].split(",") + ] + elif "Edge_" in assignment: + props["Edges"] = [ + int(i.replace("Edge_", "")) for i in assignment.split("(")[1].split(")")[0].split(",") + ] + else: + props["Objects"] = assignment + else: + props["Objects"] = [] + props["Faces"] = [] + props["Edges"] = [] + assigned_id = self._mesh.omeshmodule.GetMeshOpAssignment(self.name) + for comp_id in assigned_id: + if int(comp_id) in self._app.modeler.objects.keys(): + props["Objects"].append(self._app.oeditor.GetObjectNameByID(comp_id)) + continue + for comp in self._app.modeler.object_list: + faces = comp.faces + face_ids = [face.id for face in faces] + if int(comp_id) in face_ids: + props["Faces"].append(int(comp_id)) + continue + edges = comp.edges + edge_ids = [edge.id for edge in edges] + if int(comp_id) in edge_ids: + props["Edges"].append(int(comp_id)) + continue + self._legacy_props = MeshProps(self, props) + return self._legacy_props + @pyaedt_function_handler() def _get_args(self): """Retrieve arguments.""" props = self.props - arg = ["NAME:" + self.name] + arg = ["NAME:" + self._name] _dict2arg(props, arg) return arg @@ -143,14 +196,17 @@ def name(self): Name of the mesh operation. """ + try: + self._name = self.properties["Name"] + except KeyError: + pass return self._name @name.setter def name(self, meshop_name): - if meshop_name not in self._mesh._app.odesign.GetChildObject("Mesh").GetChildNames(): - self._mesh._app.odesign.GetChildObject("Mesh").GetChildObject(self.name).SetPropValue("Name", meshop_name) - self._name = meshop_name - else: + try: + self.properties["Name"] = meshop_name + except KeyError: self._mesh.logger.warning("Name %s already assigned in the design", meshop_name) @pyaedt_function_handler() @@ -189,15 +245,21 @@ def create(self): self._mesh.omeshmodule.AssignCylindricalGapOp(self._get_args()) else: return False + child_object = self._app.get_oo_object(self._app.odesign, f"Mesh/{self._name}") + + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) return True @pyaedt_function_handler() def update(self, key_name=None, value=None): """Update the mesh. + Returns ------- bool ``True`` when successful, ``False`` when failed. + References ---------- >>> oModule.EditTrueSurfOp @@ -355,13 +417,16 @@ def __init__(self, app): app.logger.reset_timer() self._app = app self._odesign = self._app.odesign - self.modeler = self._app.modeler self.logger = self._app.logger self.id = 0 self._meshoperations = None self._globalmesh = None app.logger.info_timer("Mesh class has been initialized!") + @property + def _modeler(self): + return self._app.modeler + @pyaedt_function_handler() def __getitem__(self, part_id): """Get the object ``Mesh`` for a given mesh operation name. @@ -516,47 +581,8 @@ def _get_design_global_mesh(self): def _get_design_mesh_operations(self): """ """ meshops = [] - try: - for ds in self.meshoperation_names: - props = {} - design_mesh = self._app.odesign.GetChildObject("Mesh") - for i in design_mesh.GetChildObject(ds).GetPropNames(): - props[i] = design_mesh.GetChildObject(ds).GetPropValue(i) - if self._app._desktop.GetVersion()[0:6] < "2023.1": - if self._app.design_properties: - props_parsed = self._app.design_properties["MeshSetup"]["MeshOperations"][ds] - if "Edges" in props_parsed.keys(): - props["Edges"] = props_parsed["Edges"] - if "Faces" in props_parsed.keys(): - props["Faces"] = props_parsed["Faces"] - if "Objects" in props_parsed.keys(): - props["Objects"] = [] - for comp in props_parsed["Objects"]: - props["Objects"].append(comp) - else: - props["Objects"] = [] - props["Faces"] = [] - props["Edges"] = [] - assigned_id = self.omeshmodule.GetMeshOpAssignment(ds) - for comp_id in assigned_id: - if int(comp_id) in self._app.modeler.objects.keys(): - props["Objects"].append(self._app.modeler.oeditor.GetObjectNameByID(comp_id)) - continue - for comp in self._app.modeler.object_list: - faces = comp.faces - face_ids = [face.id for face in faces] - if int(comp_id) in face_ids: - props["Faces"].append(int(comp_id)) - continue - edges = comp.edges - edge_ids = [edge.id for edge in edges] - if int(comp_id) in edge_ids: - props["Edges"].append(int(comp_id)) - continue - - meshops.append(MeshOperation(self, ds, props, props["Type"])) - except Exception: - self.logger.debug("An error occurred while accessing design mesh operations.") # pragma: no cover + for ds in self.meshoperation_names: + meshops.append(MeshOperation(self, ds, {}, "")) return meshops @pyaedt_function_handler(names="assignment", meshop_name="name") @@ -591,7 +617,7 @@ def assign_surface_mesh(self, assignment, level, name=None): >>> o = hfss.modeler.create_cylinder(0,[0, 0, 0],3,20,0) >>> surface = hfss.mesh.assign_surface_mesh(o.id,3,"Surface") """ - assignment = self.modeler.convert_to_selections(assignment, True) + assignment = self._modeler.convert_to_selections(assignment, True) if name: for m in self.meshoperations: if name == m.name: @@ -656,7 +682,7 @@ def assign_surface_mesh_manual( >>> o = hfss.modeler.create_cylinder(0,[0, 0, 0],3,20,0) >>> surface = hfss.mesh.assign_surface_mesh_manual(o.id,1e-6,aspect_ratio=3,name="Surface_Manual") """ - assignment = self.modeler.convert_to_selections(assignment, True) + assignment = self._modeler.convert_to_selections(assignment, True) if name: for m in self.meshoperations: if name == m.name: @@ -733,7 +759,7 @@ def assign_model_resolution(self, assignment, defeature_length=None, name=None): >>> o = hfss.modeler.create_cylinder(0,[0, 0, 0],3,20,0) >>> surface = hfss.mesh.assign_model_resolution(o,1e-4,"ModelRes1") """ - assignment = self.modeler.convert_to_selections(assignment, True) + assignment = self._modeler.convert_to_selections(assignment, True) if name: for m in self.meshoperations: if name == m.name: @@ -931,10 +957,8 @@ def delete_mesh_operations(self, mesh_type=None): References ---------- - >>> oModule.DeleteOp """ - mesh_op_types = ["Length Based", "Surface Approximation Based"] if mesh_type: @@ -986,7 +1010,7 @@ def assign_length_mesh(self, assignment, inside_selection=True, maximum_length=1 >>> oModule.AssignLengthOp """ - assignment = self.modeler.convert_to_selections(assignment, True) + assignment = self._modeler.convert_to_selections(assignment, True) if name: for m in self.meshoperations: if name == m.name: @@ -998,7 +1022,7 @@ def assign_length_mesh(self, assignment, inside_selection=True, maximum_length=1 restrictlength = False else: restrictlength = True - length = self.modeler.modeler_variable(maximum_length) + length = self._modeler.modeler_variable(maximum_length) if maximum_elements is None: restrictel = False @@ -1087,7 +1111,7 @@ def assign_skin_depth( >>> oModule.AssignSkinDepthOp """ - assignment = self.modeler.convert_to_selections(assignment, True) + assignment = self._modeler.convert_to_selections(assignment, True) if self._app.design_type not in ["HFSS", "Maxwell 3D", "Maxwell 2D"]: raise MethodNotSupportedError @@ -1159,7 +1183,7 @@ def assign_curvilinear_elements(self, assignment, enable=True, name=None): >>> oModule.AssignApplyCurvlinearElementsOp """ - assignment = self.modeler.convert_to_selections(assignment, True) + assignment = self._modeler.convert_to_selections(assignment, True) if self._app.design_type != "HFSS" and self._app.design_type != "Maxwell 3D": raise MethodNotSupportedError @@ -1192,18 +1216,18 @@ def assign_curvilinear_elements(self, assignment, enable=True, name=None): def assign_curvature_extraction(self, assignment, disabled_for_faceted=True, name=None): """Assign curvature extraction. - Parameters - ---------- - assignment : list - List of objects or faces. - disabled_for_faceted : bool, optional - Whether curvature extraction is enabled for faceted surfaces. - The default is ``True``. - name : str, optional - Name of the mesh operation. The default is ``None``. + Parameters + ---------- + assignment : list + List of objects or faces. + disabled_for_faceted : bool, optional + Whether curvature extraction is enabled for faceted surfaces. + The default is ``True``. + name : str, optional + Name of the mesh operation. The default is ``None``. - Returns - ------- + Returns + ------- :class:`ansys.aedt.core.modules.mesh.MeshOperation` Mesh operation object. @@ -1212,7 +1236,7 @@ def assign_curvature_extraction(self, assignment, disabled_for_faceted=True, nam >>> oModule.AssignCurvatureExtractionOp """ - assignment = self.modeler.convert_to_selections(assignment, True) + assignment = self._modeler.convert_to_selections(assignment, True) if self._app.solution_type != "SBR+": raise MethodNotSupportedError @@ -1266,7 +1290,7 @@ def assign_rotational_layer(self, assignment, layers_number=3, total_thickness=" >>> oModule.AssignRotationalLayerOp """ - assignment = self.modeler.convert_to_selections(assignment, True) + assignment = self._modeler.convert_to_selections(assignment, True) if self._app.design_type != "Maxwell 3D": raise MethodNotSupportedError @@ -1315,7 +1339,7 @@ def assign_edge_cut(self, assignment, layer_thickness="1mm", name=None): >>> oModule.AssignRotationalLayerOp """ - assignment = self.modeler.convert_to_selections(assignment, True) + assignment = self._modeler.convert_to_selections(assignment, True) if self._app.design_type != "Maxwell 3D": raise MethodNotSupportedError @@ -1367,7 +1391,7 @@ def assign_density_control( >>> oModule.AssignDensityControlOp """ - assignment = self.modeler.convert_to_selections(assignment, True) + assignment = self._modeler.convert_to_selections(assignment, True) if self._app.design_type != "Maxwell 3D": raise MethodNotSupportedError @@ -1464,7 +1488,7 @@ def assign_cylindrical_gap( if self._app.design_type != "Maxwell 2D" and self._app.design_type != "Maxwell 3D": raise MethodNotSupportedError - entity = self.modeler.convert_to_selections(entity, True) + entity = self._modeler.convert_to_selections(entity, True) if len(entity) > 1: self.logger.error("Cylindrical gap treatment cannot be assigned to multiple objects.") raise ValueError diff --git a/src/ansys/aedt/core/modules/mesh_icepak.py b/src/ansys/aedt/core/modules/mesh_icepak.py index f57cfb4b181..c66d6f55f2b 100644 --- a/src/ansys/aedt/core/modules/mesh_icepak.py +++ b/src/ansys/aedt/core/modules/mesh_icepak.py @@ -32,6 +32,7 @@ from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.modeler.cad.components_3d import UserDefinedComponent +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode from ansys.aedt.core.modeler.cad.object_3d import Object3d from ansys.aedt.core.modules.mesh import MeshOperation from ansys.aedt.core.modules.mesh import meshers @@ -62,9 +63,9 @@ def padding_types(self): @property def padding_values(self): - """ - Get a list of padding values (string or float) used, - one for each direction, in the following order: + """Get a list of padding values (string or float) used. + + Get one for each direction, in the following order: +X, -X, +Y, -Y, +Z, -Z. Returns @@ -312,7 +313,7 @@ def _set_region_data(self, value, direction=None, padding_type=True): region = self.object create_region = region.history() set_type = ["Data", "Type"][int(padding_type)] - create_region.props[f"{direction} Padding {set_type}"] = value + create_region.properties[f"{direction} Padding {set_type}"] = value def _update_region_data(self): region = self.object @@ -320,9 +321,9 @@ def _update_region_data(self): self._padding_type = [] self._padding_value = [] for padding_direction in ["+X", "-X", "+Y", "-Y", "+Z", "-Z"]: - self._padding_type.append(create_region.props[f"{padding_direction} Padding Type"]) - self._padding_value.append(create_region.props[f"{padding_direction} Padding Data"]) - self._coordinate_system = create_region.props["Coordinate System"] + self._padding_type.append(create_region.properties[f"{padding_direction} Padding Type"]) + self._padding_value.append(create_region.properties[f"{padding_direction} Padding Data"]) + self._coordinate_system = create_region.properties["Coordinate System"] def _get_region_data(self, direction=None, padding_type=True): self._update_region_data() @@ -394,7 +395,7 @@ def delete(self): """ Delete the subregion object. - Returns + Returns ------- bool ``True`` when successful, ``False`` when failed. @@ -413,26 +414,23 @@ def parts(self): """ Parts included in the subregion. - Returns + Returns ------- dict Dictionary with the part names as keys and ::class::modeler.cad.object_3d.Object3d as values. """ if self.object: - return { - obj_name: self._app.modeler[obj_name] - for obj_name in self.object.history().props["Part Names"].split(",") - } + history = self.object.history().properties + return {obj_name: self._app.modeler[obj_name] for obj_name in history["Part Names"].split(",")} else: return {} @parts.setter def parts(self, parts): - """ - Parts included in the subregion. + """Parts included in the subregion. Parameters - ------- + ---------- parts : List[str] List of strings containing all the parts that must be included in the subregion. """ @@ -440,8 +438,7 @@ def parts(self, parts): class MeshSettings(object): - """ - Class for managing mesh settings. + """Manages mesh settings. It can be used like a dictionary. Available keys change according to the type of settings chosen (manual or automatic). @@ -497,8 +494,7 @@ def __init__(self, mesh_class, app): del self._instance_settings[arg] def parse_settings_as_args(self): - """ - Parse mesh region settings. + """Parse mesh region settings. Returns ------- @@ -515,8 +511,7 @@ def parse_settings_as_args(self): return out def parse_settings_as_dictionary(self): - """ - Parse mesh region settings. + """Parse mesh region settings. Returns ------- @@ -532,8 +527,7 @@ def parse_settings_as_dictionary(self): return out def keys(self): - """ - Get mesh region settings keys. + """Get mesh region settings keys. Returns ------- @@ -546,8 +540,7 @@ def keys(self): return set(self._automatic_mesh_settings.keys()) | set(self._common_mesh_settings.keys()) def values(self): - """ - Get mesh region settings values. + """Get mesh region settings values. Returns ------- @@ -633,24 +626,22 @@ def __init__(self, units, app, name): self._name = name self._model_units = units self._app = app + child_object = self._app.get_oo_object(self._app.odesign, f"Mesh/{self._name}") + + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) @abstractmethod def update(self): - """ - Update the mesh region object. - """ + """Update the mesh region object.""" @abstractmethod def delete(self): - """ - Delete the mesh region object. - """ + """Delete the mesh region object.""" @abstractmethod def create(self): - """ - Create the mesh region object. - """ + """Create the mesh region object.""" # backward compatibility def __getattr__(self, name): @@ -708,7 +699,6 @@ def update(self): References ---------- - >>> oModule.EditGlobalMeshRegion """ args = ["NAME:Settings"] @@ -724,25 +714,23 @@ def update(self): @property def Objects(self): - """ - Get the region object from the modeler. - """ + """Get the region object from the modeler.""" return self.global_region.name def delete(self): - """ - Delete the region object in the modeler. - """ + """Delete the region object in the modeler.""" self.global_region.object.delete() self.global_region = None def create(self): - """ - Create the region object in the modeler. - """ + """Create the region object in the modeler.""" self.delete() self.global_region = Region(self._app) self.global_region.create(self.padding_types, self.padding_values) + child_object = self._app.get_oo_object(self._app.odesign, f"Mesh/{self._name}") + + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) class MeshRegion(MeshRegionCommon): @@ -900,16 +888,18 @@ def assignment(self): for sr in sub_regions: p1 = [] p2 = [] - if "Part Names" in self._app.modeler[sr].history().props: - p1 = self._app.modeler[sr].history().props.get("Part Names", None) + history = self._app.modeler[sr].history() + history_props = history.properties + if "Part Names" in history_props: + p1 = history_props.get("Part Names", None) if not isinstance(p1, list): p1 = [p1] - elif "Submodel Names" in self._app.modeler[sr].history().props: - p2 = self._app.modeler[sr].history().props.get("Submodel Names", None) + elif "Submodel Names" in history_props: + p2 = history_props.get("Submodel Names", None) if not isinstance(p2, list): p2 = [p2] p1 += p2 - if "CreateSubRegion" == self._app.modeler[sr].history().command and all(p in p1 for p in parts): + if "CreateSubRegion" == history.command and all(p in p1 for p in parts): self._assignment.name = sr return self._assignment elif isinstance(self._assignment, list): @@ -950,6 +940,11 @@ def create(self): self._app.mesh.meshregions.append(self) self._app.modeler.refresh_all_ids() self._assignment = self.assignment + child_object = self._app.get_oo_object(self._app.odesign, f"Mesh/{self._name}") + + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) + return True # backward compatibility @@ -1035,13 +1030,10 @@ def __init__(self, app): self._app = app self._odesign = self._app._odesign - self.modeler = self._app.modeler design_type = self._odesign.GetDesignType() if design_type not in meshers: raise RuntimeError(f"Invalid design type {design_type}") # pragma: no cover self.id = 0 - self._oeditor = self.modeler.oeditor - self._model_units = self.modeler.model_units self.meshoperations = self._get_design_mesh_operations() self.meshregions = self._get_design_mesh_regions() try: @@ -1050,6 +1042,18 @@ def __init__(self, app): self.global_mesh_region = GlobalMeshRegion(app) self._priorities_args = [] + @property + def _modeler(self): + return self._app.modeler + + @property + def _oeditor(self): + return self._app.oeditor + + @property + def _model_units(self): + return self._modeler.model_units + @property def meshregions_dict(self): """ @@ -1065,7 +1069,6 @@ def meshregions_dict(self): @pyaedt_function_handler() def _refresh_mesh_operations(self): """Refresh all mesh operations.""" - self._meshoperations = self._get_design_mesh_operations() return len(self.meshoperations) @@ -1083,7 +1086,7 @@ def omeshmodule(self): @property def boundingdimension(self): """Bounding dimension.""" - return self.modeler.get_bounding_dimension() + return self._modeler.get_bounding_dimension() @pyaedt_function_handler() def _get_design_mesh_operations(self): @@ -1349,24 +1352,22 @@ def assign_priorities(self, assignment): References ---------- - >>> oEditor.UpdatePriorityList Examples -------- - >>> ipk.mesh.assign_priorities([["Box1", "Rectangle1"], ["Box2", "Fan1_1"], ["Heatsink1_1"]]) """ if not assignment or not isinstance(assignment, list) or not isinstance(assignment[0], list): raise AttributeError("``assignment`` input must be a list of lists.") props = {"PriorityListParameters": []} self._app.logger.info("Parsing input objects information for priority assignment. This operation can take time") - udc = self.modeler.user_defined_components + udc = self._modeler.user_defined_components udc._parse_objs() for level, objects in enumerate(assignment): level += 1 if isinstance(objects[0], str): - objects = [self.modeler.objects_by_name.get(o, udc.get(o, None)) for o in objects] + objects = [self._modeler.objects_by_name.get(o, udc.get(o, None)) for o in objects] obj_3d = [ o for o in objects @@ -1409,7 +1410,7 @@ def assign_priorities(self, assignment): self._app.logger.info("Input objects information for priority assignment completed.") args = [] _dict2arg(props, args) - self.modeler.oeditor.UpdatePriorityList(args[0]) + self._modeler.oeditor.UpdatePriorityList(args[0]) return True @pyaedt_function_handler(obj_list="assignment", comp_name="component") @@ -1439,12 +1440,10 @@ def add_priority(self, entity_type, assignment=None, component=None, priority=3) References ---------- - >>> oEditor.UpdatePriorityList Examples -------- - >>> from ansys.aedt.core import Icepak >>> app = Icepak() >>> app.mesh.add_priority(entity_type=1,assignment=app.modeler.object_names,priority=3) @@ -1477,7 +1476,7 @@ def add_priority(self, entity_type, assignment=None, component=None, priority=3) self._priorities_args.append(prio) args += self._priorities_args elif entity_type == 2: - o = self.modeler.user_defined_components[component] + o = self._modeler.user_defined_components[component] if (all(part.is3d for part in o.parts.values()) is False) and ( any(part.is3d for part in o.parts.values()) is True ): @@ -1533,8 +1532,8 @@ def add_priority(self, entity_type, assignment=None, component=None, priority=3) self._priorities_args.append(prio_2d) args += self._priorities_args - self.modeler.oeditor.UpdatePriorityList(["NAME:UpdatePriorityListData"]) - self.modeler.oeditor.UpdatePriorityList(args) + self._modeler.oeditor.UpdatePriorityList(["NAME:UpdatePriorityListData"]) + self._modeler.oeditor.UpdatePriorityList(args) return True @pyaedt_function_handler(objectlist="assignment") @@ -1564,15 +1563,15 @@ def assign_mesh_region(self, assignment=None, level=5, name=None, **kwargs): if not name: name = generate_unique_name("MeshRegion") if assignment is None: - assignment = [i for i in self.modeler.object_names] + assignment = [i for i in self._modeler.object_names] meshregion = MeshRegion(self._app, assignment, name) meshregion.manual_settings = False meshregion.settings["MeshRegionResolution"] = level - all_objs = [i for i in self.modeler.object_names] + all_objs = [i for i in self._modeler.object_names] created = bool(meshregion) if created: if settings.aedt_version < "2024.1": - objectlist2 = self.modeler.object_names + 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 assignment] diff --git a/src/ansys/aedt/core/modules/solve_setup.py b/src/ansys/aedt/core/modules/solve_setup.py index efcd68cd16f..8e0d590accc 100644 --- a/src/ansys/aedt/core/modules/solve_setup.py +++ b/src/ansys/aedt/core/modules/solve_setup.py @@ -27,7 +27,6 @@ This module provides all functionalities for creating and editing setups in AEDT. It is based on templates to allow for easy creation and modification of setup properties. - """ from __future__ import absolute_import # noreorder @@ -44,6 +43,7 @@ from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.settings import settings +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode from ansys.aedt.core.modules.setup_templates import SetupKeys from ansys.aedt.core.modules.solve_sweeps import SetupProps from ansys.aedt.core.modules.solve_sweeps import SweepHFSS @@ -52,7 +52,7 @@ from ansys.aedt.core.modules.solve_sweeps import identify_setup -class CommonSetup(PropsManager, object): +class CommonSetup(PropsManager, BinaryTreeNode): def __init__(self, app, solution_type, name="MySetupAuto", is_new_setup=True): self.auto_update = False self._app = None @@ -66,10 +66,45 @@ def __init__(self, app, solution_type, name="MySetupAuto", is_new_setup=True): else: self.setuptype = self.p_app.design_solutions._solution_options[solution_type]["default_setup"] self._name = name - self.props = {} - self.sweeps = [] - self._init_props(is_new_setup) + self._legacy_props = {} + self._sweeps = [] + self._is_new_setup = is_new_setup + # self._init_props(is_new_setup) self.auto_update = True + child_object = self._app.get_oo_object(self._app.odesign, f"Analysis/{self._name}") + + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) + + @property + def sweeps(self): + if self._sweeps: + return self._sweeps + try: + setups_data = self.p_app.design_properties["AnalysisSetup"]["SolveSetups"] + if self.name in setups_data: + setup_data = setups_data[self.name] + if "Sweeps" in setup_data and self.setuptype not in [ + 0, + 7, + ]: # 0 and 7 represent setup HFSSDrivenAuto + if self.setuptype <= 4: + app = setup_data["Sweeps"] + app.pop("NextUniqueID", None) + app.pop("MoveBackForward", None) + app.pop("MoveBackwards", None) + for el in app: + if isinstance(app[el], dict): + self._sweeps.append(SweepHFSS(self, el, props=app[el])) + else: + app = setup_data["Sweeps"] + for el in app: + if isinstance(app[el], dict): + self._sweeps.append(SweepMatrix(self, el, props=app[el])) + setup_data.pop("Sweeps", None) + except (TypeError, KeyError): + pass + return self._sweeps @property def default_intrinsics(self): @@ -78,7 +113,8 @@ def default_intrinsics(self): Returns ------- dict - Dictionary which keys are typically Freq, Phase or Time.""" + Dictionary which keys are typically Freq, Phase or Time. + """ intr = {} if "HFSS 3D Layout" in self._app.design_type: # pragma no cover try: @@ -170,38 +206,32 @@ def analyze( blocking=blocking, ) - @pyaedt_function_handler() - def _init_props(self, is_new_setup=False): - if is_new_setup: + @property + def props(self): + if self._legacy_props: + return self._legacy_props + if self._is_new_setup: setup_template = SetupKeys.get_setup_templates()[self.setuptype] - self.props = SetupProps(self, setup_template) + setup_template["Name"] = self._name + self._legacy_props = SetupProps(self, setup_template) + self._is_new_setup = False else: try: if "AnalysisSetup" in self.p_app.design_properties.keys(): setups_data = self.p_app.design_properties["AnalysisSetup"]["SolveSetups"] if self.name in setups_data: setup_data = setups_data[self.name] - if "Sweeps" in setup_data and self.setuptype != 0: # 0 represents setup HFSSDrivenAuto - if self.setuptype <= 4: - app = setup_data["Sweeps"] - app.pop("NextUniqueID", None) - app.pop("MoveBackForward", None) - app.pop("MoveBackwards", None) - for el in app: - if isinstance(app[el], dict): - self.sweeps.append(SweepHFSS(self, el, props=app[el])) - else: - app = setup_data["Sweeps"] - for el in app: - if isinstance(app[el], dict): - self.sweeps.append(SweepMatrix(self, el, props=app[el])) - setup_data.pop("Sweeps", None) - self.props = SetupProps(self, setup_data) + self._legacy_props = SetupProps(self, setup_data) elif "SimSetups" in self.p_app.design_properties.keys(): setup_data = self.p_app.design_properties["SimSetups"]["SimSetup"] - self.props = SetupProps(self, setup_data) + self._legacy_props = SetupProps(self, setup_data) except Exception: - self.props = SetupProps(self, {}) + self._legacy_props = SetupProps(self, {}) + return self._legacy_props + + @props.setter + def props(self, value): + self._legacy_props = SetupProps(self, value) @property def is_solved(self): @@ -515,9 +545,14 @@ def create(self): >>> oModule.InsertSetup """ soltype = SetupKeys.SetupNames[self.setuptype] - arg = ["NAME:" + self.name] + arg = ["NAME:" + self._name] _dict2arg(self.props, arg) self.omodule.InsertSetup(soltype, arg) + child_object = self._app.get_oo_object(self._app.odesign, f"Analysis/{self._name}") + + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) + return arg @pyaedt_function_handler(update_dictionary="properties") @@ -1026,9 +1061,7 @@ def start_continue_from_previous_setup( >>> m2d = ansys.aedt.core.Maxwell2d() >>> setup = m2d.get_setup("Setup1") >>> setup.start_continue_from_previous_setup(design="IM",solution="Setup1 : Transient") - """ - auto_update = self.auto_update try: self.auto_update = False @@ -1078,14 +1111,17 @@ class SetupCircuit(CommonSetup): def __init__(self, app, solution_type, name="MySetupAuto", is_new_setup=True): CommonSetup.__init__(self, app, solution_type, name, is_new_setup) - @pyaedt_function_handler(isnewsetup="is_new_setup") - def _init_props(self, is_new_setup=False): - props = {} - if is_new_setup: + @property + def props(self): + if self._legacy_props: + return self._legacy_props + if self._is_new_setup: setup_template = SetupKeys.get_setup_templates()[self.setuptype] - self.props = SetupProps(self, setup_template) + setup_template["Name"] = self.name + self._legacy_props = SetupProps(self, setup_template) + self._is_new_setup = False else: - self.props = SetupProps(self, {}) + self._legacy_props = SetupProps(self, {}) try: setups_data = self.p_app.design_properties["SimSetups"]["SimSetup"] if not isinstance(setups_data, list): @@ -1094,10 +1130,10 @@ def _init_props(self, is_new_setup=False): if self.name == setup["Name"]: setup_data = setup setup_data.pop("Sweeps", None) - self.props = SetupProps(self, setup_data) + self._legacy_props = SetupProps(self, setup_data) except Exception: - self.props = SetupProps(self, {}) - self.props["Name"] = self.name + self._legacy_props = SetupProps(self, {}) + return self._legacy_props @property def _odesign(self): @@ -1127,6 +1163,10 @@ def create(self): arg = ["NAME:SimSetup"] _dict2arg(self.props, arg) self._setup(soltype, arg) + child_object = self._app.get_oo_object(self._app.odesign, f"Analysis/{self._name}") + + if child_object: + BinaryTreeNode.__init__(self, self._name, child_object, False) return arg @pyaedt_function_handler() @@ -1410,9 +1450,7 @@ def _expression_cache( ------- list List of the data. - """ - if isrelativeconvergence: userelative = 1 else: @@ -1604,6 +1642,7 @@ def get_solution_data( sweep=None, ): """Get a simulation result from a solved setup and cast it in a ``SolutionData`` object. + Data to be retrieved from Electronics Desktop are any simulation results available in that specific simulation context. Most of the argument have some defaults which works for most of the ``Standard`` report quantities. @@ -1768,26 +1807,47 @@ class Setup3DLayout(CommonSetup): def __init__(self, app, solution_type, name="MySetupAuto", is_new_setup=True): CommonSetup.__init__(self, app, solution_type, name, is_new_setup) - @pyaedt_function_handler(isnewsetup="is_new_setup") - def _init_props(self, is_new_setup=False): - if is_new_setup: + @property + def sweeps(self): + if self._sweeps: + return self._sweeps + try: + setups_data = self._app.design_properties["Setup"]["Data"] + if self.name in setups_data: + setup_data = setups_data[self.name] + if "Data" in setup_data: # 0 and 7 represent setup HFSSDrivenAuto + app = setup_data["Data"] + for el in app: + if isinstance(app[el], dict): + self._sweeps.append(SweepHFSS3DLayout(self, el, props=app[el])) + except (KeyError, TypeError): + pass + return self._sweeps + + @property + def props(self): + if self._legacy_props: + return self._legacy_props + if self._is_new_setup: setup_template = SetupKeys.get_setup_templates()[self.setuptype] - self.props = SetupProps(self, setup_template) + setup_template["Name"] = self.name + self._legacy_props = SetupProps(self, setup_template) + self._is_new_setup = False + else: try: setups_data = self._app.design_properties["Setup"]["Data"] if self.name in setups_data: setup_data = setups_data[self.name] - if "Data" in setup_data: # 0 and 7 represent setup HFSSDrivenAuto - app = setup_data["Data"] - for el in app: - if isinstance(app[el], dict): - self.sweeps.append(SweepHFSS3DLayout(self, el, props=app[el])) - - self.props = SetupProps(self, setup_data) + self._legacy_props = SetupProps(self, setup_data) except Exception: - self.props = SetupProps(self, {}) + self._legacy_props = SetupProps(self, {}) settings.logger.error("Unable to set props.") + return self._legacy_props + + @props.setter + def props(self, value): + self._legacy_props = SetupProps(self, value) @property def is_solved(self): @@ -1798,22 +1858,28 @@ def is_solved(self): bool `True` if solutions are available. """ - if self.props.get("SolveSetupType", "HFSS") == "HFSS": + if self.properties: + props = self.properties + key = "Solver" + else: + props = self.props + key = "SolveSetupType" + if props.get(key, "HFSS") == "HFSS": combined_name = f"{self.name} : Last Adaptive" expressions = [i for i in self.p_app.post.available_report_quantities(solution=combined_name)] sol = self._app.post.reports_by_category.standard(expressions=expressions[0], setup=combined_name) - elif self.props.get("SolveSetupType", "HFSS") == "SIwave": + elif props.get(key, "HFSS") == "SIwave": combined_name = f"{self.name} : {self.sweeps[0].name}" expressions = [i for i in self.p_app.post.available_report_quantities(solution=combined_name)] sol = self._app.post.reports_by_category.standard(expressions=expressions[0], setup=combined_name) - elif self.props.get("SolveSetupType", "HFSS") == "SIwaveDCIR": + elif props.get(key, "HFSS") == "SIwaveDCIR": expressions = self.p_app.post.available_report_quantities(solution=self.name, is_siwave_dc=True) sol = self._app.post.reports_by_category.standard(expressions=expressions[0], setup=self.name) else: expressions = [i for i in self.p_app.post.available_report_quantities(solution=self.name)] sol = self._app.post.reports_by_category.standard(expressions=expressions[0], setup=self.name) - if identify_setup(self.props): + if identify_setup(props): sol.domain = "Time" return True if sol.get_solution_data() else False @@ -1826,10 +1892,13 @@ def solver_type(self): type Setup type. """ - - if "SolveSetupType" in self.props: + try: + return self.properties["Solver"] + except Exception: + pass + try: return self.props["SolveSetupType"] - else: + except Exception: return None @pyaedt_function_handler() @@ -1948,10 +2017,8 @@ def export_to_hfss(self, output_file, keep_net_name=False, unite=True): References ---------- - >>> oModule.ExportToHfss """ - output_file = output_file if not os.path.isdir(os.path.dirname(output_file)): return False @@ -2214,10 +2281,8 @@ def export_to_q3d(self, output_file, keep_net_name=False, unite=True): References ---------- - >>> oModule.ExportToQ3d """ - if not os.path.isdir(os.path.dirname(output_file)): return False output_file = os.path.splitext(output_file)[0] + ".aedt" @@ -2944,7 +3009,7 @@ def delete_sweep(self, name): >>> setup1.delete_sweep("Sweep1") """ if name in self.get_sweep_names(): - self.sweeps = [sweep for sweep in self.sweeps if sweep.name != name] + self._sweeps = [sweep for sweep in self._sweeps if sweep.name != name] self.omodule.DeleteSweep(self.name, name) return True return False @@ -4198,7 +4263,6 @@ def start_continue_from_previous_setup( References ---------- - >>> oModule.EditSetup Examples @@ -4206,9 +4270,7 @@ def start_continue_from_previous_setup( >>> ipk = ansys.aedt.core.Icepak() >>> setup = ipk.get_setup("Setup1") >>> setup.start_continue_from_previous_setup(design="IcepakDesign1",solution="Setup1 : SteadyState") - """ - auto_update = self.auto_update try: self.auto_update = False diff --git a/src/ansys/aedt/core/modules/solve_sweeps.py b/src/ansys/aedt/core/modules/solve_sweeps.py index 100799142da..5e7045deb68 100644 --- a/src/ansys/aedt/core/modules/solve_sweeps.py +++ b/src/ansys/aedt/core/modules/solve_sweeps.py @@ -145,6 +145,7 @@ def is_solved(self): @property def frequencies(self): """List of all frequencies of the active sweep. + To see values, the project must be saved and solved. Returns @@ -161,6 +162,7 @@ def frequencies(self): @property def basis_frequencies(self): """List of all frequencies that have fields available. + To see values, the project must be saved and solved. Returns @@ -659,6 +661,7 @@ def is_solved(self): @property def frequencies(self): """List of all frequencies of the active sweep. + To see values, the project must be saved and solved. Returns @@ -675,6 +678,7 @@ def frequencies(self): @property def basis_frequencies(self): """Get the list of all frequencies that have fields available. + The project has to be saved and solved to see values. Returns diff --git a/src/ansys/aedt/core/q3d.py b/src/ansys/aedt/core/q3d.py index 01b42c8aed7..bffbb98be48 100644 --- a/src/ansys/aedt/core/q3d.py +++ b/src/ansys/aedt/core/q3d.py @@ -38,8 +38,8 @@ from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.modeler.geometry_operators import GeometryOperators as go -from ansys.aedt.core.modules.boundary import BoundaryObject -from ansys.aedt.core.modules.boundary import Matrix +from ansys.aedt.core.modules.boundary.common import BoundaryObject +from ansys.aedt.core.modules.boundary.q3d_boundary import Matrix from ansys.aedt.core.modules.setup_templates import SetupKeys try: @@ -157,7 +157,7 @@ def insert_reduced_matrix( Returns ------- - :class:`ansys.aedt.core.modules.boundary.Matrix` + :class:`ansys.aedt.core.modules.q3d_boundary.Matrix` Matrix object. """ if not reduced_matrix: diff --git a/src/ansys/aedt/core/rmxprt.py b/src/ansys/aedt/core/rmxprt.py index f8275315932..9187023884d 100644 --- a/src/ansys/aedt/core/rmxprt.py +++ b/src/ansys/aedt/core/rmxprt.py @@ -63,16 +63,17 @@ def properties(self): @pyaedt_function_handler() def __setitem__(self, parameter_name, value): def _apply_val(dict_in, name, value): - if name in dict_in.props: + if name in dict_in.properties: if ( - isinstance(dict_in.props[name], list) - and ":=" in dict_in.props[name][0] + isinstance(dict_in.properties[name], list) + and isinstance(dict_in.properties[name][0], str) + and ":=" in dict_in.properties[name][0] and not isinstance(value, list) ): - prps = dict_in.props[name][::] + prps = dict_in.properties[name][::] prps[1] = value value = prps - dict_in.props[name] = value + dict_in.properties[name] = value return True else: for _, child in dict_in.children.items(): @@ -88,8 +89,8 @@ def _apply_val(dict_in, name, value): @pyaedt_function_handler() def __getitem__(self, parameter_name): def _get_val(dict_in, name): - if name in dict_in.props: - return dict_in.props[name] + if name in dict_in.properties: + return dict_in.properties[name] else: for _, child in dict_in.children.items(): return _get_val(child, name) @@ -347,7 +348,7 @@ def export_configuration(self, output_file): def jsonalize(dict_in, dict_out): dict_out[dict_in.node] = {} - for k, v in dict_in.props.items(): + for k, v in dict_in.properties.items(): if not k.endswith("/Choices"): dict_out[dict_in.node][k] = v for _, c in dict_in.children.items(): diff --git a/src/ansys/aedt/core/rpc/rpyc_services.py b/src/ansys/aedt/core/rpc/rpyc_services.py index 93a596a86de..f7b87be4bea 100644 --- a/src/ansys/aedt/core/rpc/rpyc_services.py +++ b/src/ansys/aedt/core/rpc/rpyc_services.py @@ -167,14 +167,18 @@ def pathexists(self, remotepath): if self.client.root.pathexists(remotepath): return True return False + def unlink(self, remotepath): if self.client.root.unlink(remotepath): return True return False + def normpath(self, remotepath): return self.client.root.normpath(remotepath) + def isdir(self, remotepath): return self.client.root.isdir(remotepath) + def temp_dir(self): return self.client.root.temp_dir() @@ -309,7 +313,7 @@ def exposed_edb( edbversion="2021.2", use_ppe=False, ): - """Starts a new Hfss session. + """Start a new Hfss session. Parameters ---------- @@ -351,7 +355,7 @@ def exposed_hfss( version=None, non_graphical=True, ): - """Starts a new Hfss session. + """Start a new Hfss session. Parameters ---------- @@ -406,7 +410,7 @@ def exposed_hfss3dlayout( version=None, non_graphical=True, ): - """Starts a new Hfss3dLayout session. + """Start a new Hfss3dLayout session. Parameters ---------- @@ -461,7 +465,7 @@ def exposed_maxwell3d( version=None, non_graphical=True, ): - """Starts a new Maxwell3d session. + """Start a new Maxwell3d session. Parameters ---------- @@ -516,7 +520,7 @@ def exposed_maxwell2d( version=None, non_graphical=True, ): - """Starts a new Maxwell32 session. + """Start a new Maxwell32 session. Parameters ---------- @@ -571,7 +575,7 @@ def exposed_icepak( version=None, non_graphical=True, ): - """Starts a new Icepak session. + """Start a new Icepak session. Parameters ---------- @@ -626,7 +630,7 @@ def exposed_circuit( version=None, non_graphical=True, ): - """Starts a new Circuit session. + """Start a new Circuit session. Parameters ---------- @@ -681,7 +685,7 @@ def exposed_mechanical( version=None, non_graphical=True, ): - """Starts a new Mechanical session. + """Start a new Mechanical session. Parameters ---------- @@ -736,7 +740,7 @@ def exposed_q3d( version=None, non_graphical=True, ): - """Starts a new Q3d session. + """Start a new Q3d session. Parameters ---------- @@ -791,7 +795,7 @@ def exposed_q2d( version=None, non_graphical=True, ): - """Starts a new Q2d session. + """Start a new Q2d session. Parameters ---------- @@ -842,12 +846,14 @@ class GlobalService(rpyc.Service): """Global class to manage rpyc Server of PyAEDT.""" def on_connect(self, connection): + """Initialize the service when the connection is created.""" # code that runs when a connection is created # (to init the service, if needed) self.connection = connection pass def on_disconnect(self, connection): + """Finalize the service when the connection is closed.""" # code that runs after the connection has already closed # (to finalize the service, if needed) if is_windows: @@ -869,7 +875,7 @@ def exposed_restore(self): @staticmethod def aedt_grpc(port=None, beta_options=None, use_aedt_relative_path=False, non_graphical=True): - """Starts a new AEDT session on a specified gRPC port. + """Start a new AEDT session on a specified gRPC port. Returns ------- @@ -1091,6 +1097,7 @@ class ServiceManager(rpyc.Service): """Global class to manage rpyc Server of PyAEDT.""" def on_connect(self, connection): + """Initiate the service when a connection is created.""" # code that runs when a connection is created # (to init the service, if needed) self.connection = connection @@ -1099,6 +1106,7 @@ def on_connect(self, connection): pass def on_disconnect(self, connection): + """Finalize the service when the connection is closed.""" # code that runs after the connection has already closed # (to finalize the service, if needed) if is_windows: @@ -1150,7 +1158,7 @@ def exposed_stop_service(self, port): Parameters ---------- port : int - port id on which there is the service to kill. + Port id on which there is the service to kill. Returns ------- diff --git a/src/ansys/aedt/core/visualization/advanced/farfield_visualization.py b/src/ansys/aedt/core/visualization/advanced/farfield_visualization.py index 488780ba25b..e215eceb3c3 100644 --- a/src/ansys/aedt/core/visualization/advanced/farfield_visualization.py +++ b/src/ansys/aedt/core/visualization/advanced/farfield_visualization.py @@ -92,7 +92,6 @@ class FfdSolutionData(object): Examples -------- - >>> from ansys.aedt.core >>> from ansys.aedt.core.visualization.advanced.farfield_visualization import FfdSolutionData >>> app = ansys.aedt.core.Hfss(version="2023.2", design="Antenna") @@ -1329,7 +1328,6 @@ def __init_ffd(self, element_info): bool ``True`` when successful, ``False`` when failed. """ - for element, element_data in element_info.items(): self.__raw_data[element] = {} self.__frequencies = [] diff --git a/src/ansys/aedt/core/visualization/advanced/misc.py b/src/ansys/aedt/core/visualization/advanced/misc.py index 3e57a615442..99157dc6800 100644 --- a/src/ansys/aedt/core/visualization/advanced/misc.py +++ b/src/ansys/aedt/core/visualization/advanced/misc.py @@ -58,6 +58,7 @@ def __init__(self): self.im = {"Ex": [], "Ey": [], "Ez": [], "Hx": [], "Hy": [], "Hz": []} def set_xyz_points(self, x, y, z): + """Set X, Y, Z coordinates.""" self.x = x self.y = y self.z = z @@ -75,6 +76,7 @@ def set_field_component(self, field_component, real, imag, invert): print("Error in set_field_component function.") def fill_empty_data(self): + """Fill empty data with zeros.""" for el, val in self.re.items(): if not val: zero_field_z_faces = [0] * len(self.x) @@ -730,6 +732,7 @@ def simplify_stl(input_file, output_file=None, decimation=0.5, preview=False): original size and will remove 90% of the input triangles. preview : bool, optional Whether to preview the model in pyvista or skip it. + Returns ------- str diff --git a/src/ansys/aedt/core/visualization/advanced/rcs_visualization.py b/src/ansys/aedt/core/visualization/advanced/rcs_visualization.py index 4fa04fbd338..dd2670248ae 100644 --- a/src/ansys/aedt/core/visualization/advanced/rcs_visualization.py +++ b/src/ansys/aedt/core/visualization/advanced/rcs_visualization.py @@ -261,6 +261,7 @@ def frequency(self, val): @property def data_conversion_function(self): """RCS data conversion function. + The available functions are: - `"dB10"`: Converts the data to decibels using base 10 logarithm. @@ -283,6 +284,7 @@ def data_conversion_function(self, val): @property def window(self): """Window function. + The available functions are: Options are ``"Flat"``, ``"Hamming``", and ``"Hann"``. """ return self.__window @@ -432,7 +434,6 @@ def waterfall(self): @property def isar_2d(self): """ISAR 2D.""" - phis = self.available_incident_wave_phi thetas = self.available_incident_wave_theta nfreq = len(self.frequencies) @@ -713,7 +714,6 @@ def plot_rcs( :class:`ansys.aedt.core.visualization.plot.matplotlib.ReportPlotter` PyAEDT matplotlib figure object. """ - curves = [] all_secondary_sweep_value = secondary_sweep_value if primary_sweep.lower() == "freq" or primary_sweep.lower() == "frequency": @@ -811,7 +811,6 @@ def plot_rcs_3d(self, title="Monostatic RCS 3D", output_file=None, show=True, si If ``show=True``, a Matplotlib figure instance of the plot is returned. If ``show=False``, the plotted curve is returned. """ - data = self.rcs_data.rcs_active_frequency rcs = data["Data"] @@ -880,7 +879,6 @@ def plot_range_profile( :class:`ansys.aedt.core.visualization.plot.matplotlib.ReportPlotter` PyAEDT matplotlib figure object. """ - data_range_profile = self.rcs_data.range_profile ranges = np.unique(data_range_profile["Range"]) @@ -926,7 +924,6 @@ def plot_waterfall(self, title="Waterfall", output_file=None, show=True, is_pola :class:`ansys.aedt.core.visualization.plot.matplotlib.ReportPlotter` PyAEDT matplotlib figure object. """ - data_range_waterfall = self.rcs_data.waterfall ranges = np.unique(data_range_waterfall["Range"]) @@ -993,7 +990,6 @@ def plot_isar_2d(self, title="ISAR", output_file=None, show=True, size=(1920, 14 :class:`ansys.aedt.core.visualization.plot.matplotlib.ReportPlotter` PyAEDT matplotlib figure object. """ - data_isar = self.rcs_data.isar_2d ranges = np.unique(data_isar["Down-range"]) @@ -1052,7 +1048,6 @@ def plot_scene(self, show=True): Returns the ``Plotter`` object if ``show`` is set to ``False``. If ``show`` is ``True``, the plot is displayed and no value is returned. """ - pv_backend = PyVistaBackend(allow_picking=True, plot_picked_names=True) plotter = Plotter(backend=pv_backend) @@ -1887,6 +1882,7 @@ def __init__(self): @property def mesh(self): + """Get the mesh object.""" return self.__mesh @mesh.setter @@ -1896,6 +1892,7 @@ def mesh(self, val): @property def z_offset(self): + "Offset in the Z direction." return self.__z_offset @z_offset.setter diff --git a/src/ansys/aedt/core/visualization/advanced/touchstone_parser.py b/src/ansys/aedt/core/visualization/advanced/touchstone_parser.py index 8f46baa50d3..5689a698f79 100644 --- a/src/ansys/aedt/core/visualization/advanced/touchstone_parser.py +++ b/src/ansys/aedt/core/visualization/advanced/touchstone_parser.py @@ -113,13 +113,14 @@ def __init__(self, solution_data=None, touchstone_file=None): @pyaedt_function_handler() def get_insertion_loss_index(self, threshold=-3): - """Get all insertion losses. The first frequency point is used to determine whether two - ports are shorted. + """Get all insertion losses. + + The first frequency point is used to determine whether two ports are shorted. Parameters ---------- threshold : float, int, optional - Threshold to determine shorted ports in dB. + Threshold to determine shorted ports in dB. Default value is ``3``. Returns ------- @@ -141,13 +142,14 @@ def get_insertion_loss_index(self, threshold=-3): return temp_list def plot_insertion_losses(self, threshold=-3, plot=True): - """Plot all insertion losses. The first frequency point is used to determine whether two - ports are shorted. + """Plot all insertion losses. + + The first frequency point is used to determine whether two ports are shorted. Parameters ---------- threshold : float, int, optional - Threshold to determine shorted ports in dB. + Threshold to determine shorted ports in dB. Default value is ``3``. plot : bool, optional Whether to plot. The default is ``True``. @@ -177,7 +179,6 @@ def plot(self, index_couples=None, show=True): ------- :class:`matplotlib.plt` """ - if not index_couples: index_couples = self.port_tuples[:] @@ -249,7 +250,7 @@ def get_mixed_mode_touchstone_data(self, num_of_diff_ports=None, port_ordering=" @pyaedt_function_handler() def get_return_loss_index(self, excitation_name_prefix=""): - """Get the list of all the Returnloss from a list of exctitations. + """Get the list of all the Returnloss from a list of excitations. If no excitation is provided it will provide a full list of return losses. @@ -258,9 +259,8 @@ def get_return_loss_index(self, excitation_name_prefix=""): Parameters ---------- - - excitation_name_prefix : - (Default value = '') + excitation_name_prefix :str, optional + Prefix of the excitation. Default value is ``""``. Returns ------- @@ -306,20 +306,20 @@ def get_insertion_loss_index_from_prefix(self, tx_prefix, rx_prefix): @pyaedt_function_handler() def get_next_xtalk_index(self, tx_prefix=""): - """Get the list of all the Near End XTalk a list of excitation. Optionally prefix can - be used to retrieve driver names. + """Get the list of all the Near End XTalk a list of excitation. + + Optionally prefix can be used to retrieve driver names. Example: excitation_names ["1", "2", "3"] output ["S(1,2)", "S(1,3)", "S(2,3)"]. Parameters ---------- - tx_prefix : - prefix for TX (eg. "DIE") (Default value = "") + tx_prefix :str, optional + Prefix for TX (eg. "DIE"). Default value is ``""``. Returns ------- list List of index couples representing Near End XTalks. - """ if tx_prefix: trlist = [i for i in self.port_names if tx_prefix in i] @@ -335,7 +335,7 @@ def get_next_xtalk_index(self, tx_prefix=""): @pyaedt_function_handler() def get_fext_xtalk_index_from_prefix(self, tx_prefix, rx_prefix, skip_same_index_couples=True): - """Get the list of all the Far End XTalk from a list of exctitations and a prefix that will + """Get the list of all the Far End XTalk from a list of excitations and a prefix that will be used to retrieve driver and receivers names. If skip_same_index_couples is true, the tx and rx with same index position will be considered insertion losses and excluded from the list. @@ -353,7 +353,6 @@ def get_fext_xtalk_index_from_prefix(self, tx_prefix, rx_prefix, skip_same_index ------- list List of index couples representing Far End XTalks. - """ trlist = [i for i in self.port_names if tx_prefix in i] reclist = [i for i in self.port_names if rx_prefix in i] @@ -369,6 +368,9 @@ def plot_next_xtalk_losses(self, tx_prefix=""): Parameters ---------- + tx_prefix: str, optional + Prefix for TX. Default value is ``""``. + Returns ------- bool @@ -386,10 +388,10 @@ def plot_fext_xtalk_losses(self, tx_prefix, rx_prefix, skip_same_index_couples=T Parameters ---------- tx_prefix : str - prefix for TX (eg. "DIE") + Prefix for TX (eg. "DIE"). rx_prefix : str - prefix for RX (eg. "BGA") - skip_same_index_couples : bool + Prefix for RX (eg. "BGA"). + skip_same_index_couples : bool, optional Boolean ignore TX and RX couple with same index. The default value is ``True``. Returns @@ -426,9 +428,7 @@ def get_worst_curve(self, freq_min=None, freq_max=None, worst_is_higher=True, cu ------- tuple Worst element, dictionary of ordered expression. - """ - return_loss_freq = [float(i.center) for i in list(self.frequency)] if not freq_min: lower_id = 0 @@ -492,7 +492,7 @@ def check_touchstone_files(input_dir="", passivity=True, causality=True): Whether the causality check is enabled. The default is ``True``. Returns - ---------- + ------- dict Dictionary with the SNP file name as the key and a list if the passivity and/or causality checks are enabled. The first element in the list is a str with ``"passivity"`` or ``"causality"`` as a value. The second element @@ -560,7 +560,7 @@ def find_touchstone_files(input_dir): Folder path. The default is ``""``. Returns - ---------- + ------- dict Dictionary with the SNP file names as the key and the absolute path as the value. """ diff --git a/src/ansys/aedt/core/visualization/plot/pdf.py b/src/ansys/aedt/core/visualization/plot/pdf.py index 031c70bc01d..a220b967568 100644 --- a/src/ansys/aedt/core/visualization/plot/pdf.py +++ b/src/ansys/aedt/core/visualization/plot/pdf.py @@ -301,8 +301,8 @@ def add_project_info(self, design): @property def template_name(self): """Name of the template to use. - It can be a full json path or a string of a json contained in ``"Images"`` folder. + It can be a full json path or a string of a json contained in ``"Images"`` folder. Returns ------- @@ -505,6 +505,7 @@ def add_table( col_widths=None, ): """Add a new table from a list of data. + Data shall be a list of list where every line is either a row or a column. Parameters @@ -520,7 +521,6 @@ def add_table( col_widths : list, optional List of column widths. """ - self.set_font(self.report_specs.font.lower(), size=self.report_specs.text_font_size) self.set_font(self.report_specs.font.lower(), size=self.report_specs.table_font_size) with self.table( diff --git a/src/ansys/aedt/core/visualization/plot/pyvista.py b/src/ansys/aedt/core/visualization/plot/pyvista.py index 9fcc9da268c..036f005d126 100644 --- a/src/ansys/aedt/core/visualization/plot/pyvista.py +++ b/src/ansys/aedt/core/visualization/plot/pyvista.py @@ -583,6 +583,7 @@ def focal_point(self, value): @property def camera_position(self): """Get or set the camera position value. This parameter disables the default iso view. + Value for the camera position. The value is for ``"xy"``, ``"xz"`` or ``"yz"``. Returns @@ -710,7 +711,9 @@ def set_orientation(self, camera_position="xy", roll_angle=0, azimuth_angle=45, @property def background_color(self): """Background color. - It can be a tuple of (r,g,b) or color name.""" + + It can be a tuple of (r,g,b) or color name. + """ return self._background_color @background_color.setter @@ -838,6 +841,7 @@ def add_field_from_file( show_edges=True, ): """Add a field file to the scenario. + It can be aedtplt, fld or csv file or any txt file with 4 column [x,y,z,field]. If text file they have to be space separated column. @@ -910,6 +914,7 @@ def add_frames_from_file( Delauny tolerance value used for interpolating points. header_lines : int Number of lines to of the file containing header info that has to be removed. + Returns ------- bool @@ -1314,7 +1319,6 @@ def plot(self, export_image_path=None, show=True): Parameters ---------- - export_image_path : str, optional Path to image to save. Default is None show : bool, optional @@ -1337,7 +1341,7 @@ def plot(self, export_image_path=None, show=True): root_name = "Image" # pragma: no cover def s_callback(): # pragma: no cover - """save screenshots""" + """Save screenshots.""" exp = os.path.join(path_image, f'{root_name}{datetime.now().strftime("%Y_%M_%d_%H-%M-%S")}{format}') self.pv.screenshot(exp, return_img=False) @@ -1431,17 +1435,17 @@ def animate(self): self.pv.open_gif(self.gif_file) def q_callback(): - """exit when user wants to leave""" + """Exit when user wants to leave.""" self._animating = False self._pause = False def p_callback(): - """exit when user wants to leave""" + """Exit when user wants to leave.""" self._pause = not self._pause self.pv.add_text( - "Press p for Play/Pause, Press q to exit ", font_size=8, position="upper_left", color=tuple(axes_color) + "Press p for Play/Pause, Press q to exit.", font_size=8, position="upper_left", color=tuple(axes_color) ) self.pv.add_text(" ", font_size=10, position=[0, 0], color=tuple(axes_color)) self.pv.add_key_event("q", q_callback) diff --git a/src/ansys/aedt/core/visualization/post/common.py b/src/ansys/aedt/core/visualization/post/common.py index ab2d0b2b313..4fecd217008 100644 --- a/src/ansys/aedt/core/visualization/post/common.py +++ b/src/ansys/aedt/core/visualization/post/common.py @@ -477,6 +477,7 @@ def get_all_report_quantities( @pyaedt_function_handler() def available_report_solutions(self, report_category=None): """Get the list of available solutions that can be used for the reports. + This list differs from the one obtained with ``app.existing_analysis_sweeps``, because it includes additional elements like "AdaptivePass". @@ -514,7 +515,7 @@ def _get_plot_inputs(self): report = TEMPLATES_BY_NAME.get(report_type, TEMPLATES_BY_NAME["Standard"]) plots.append(report(self, report_type, None)) - plots[-1]._props["plot_name"] = name + plots[-1]._legacy_props["plot_name"] = name plots[-1]._is_created = True plots[-1].report_type = obj.GetPropValue("Display Type") return plots @@ -644,7 +645,6 @@ def delete_report(self, plot_name=None): References ---------- - >>> oModule.DeleteReports """ try: @@ -778,7 +778,8 @@ def export_report_to_file( step=None, use_trace_number_format=False, ): - """Export a 2D Plot data to a file. + r""" + Export a 2D Plot data to a file. This method leaves the data in the plot (as data) as a reference for the Plot after the loops. @@ -1307,7 +1308,7 @@ def create_report( else: report.matrix = context elif report_category == "Far Fields": - if not context and self._app._field_setups: + if not context and self._app.field_setups: report.far_field_sphere = self._app.field_setups[0].name else: if isinstance(context, dict): @@ -1345,6 +1346,7 @@ def get_solution_data( math_formula=None, ): """Get a simulation result from a solved setup and cast it in a ``SolutionData`` object. + Data to be retrieved from Electronics Desktop are any simulation results available in that specific simulation context. Most of the argument have some defaults which works for most of the ``Standard`` report quantities. @@ -1538,6 +1540,11 @@ def get_solution_data( elif report_category == "Far Fields": if not context and self._app.field_setups: report.far_field_sphere = self._app.field_setups[0].name + if "Theta" not in report.variations: + report.variations["Theta"] = ["All"] + if "Phi" not in report.variations: + report.variations["Phi"] = ["All"] + report.primary_sweep = "Theta" else: if isinstance(context, dict): if "Context" in context.keys() and "SourceContext" in context.keys(): @@ -1601,7 +1608,6 @@ def create_report_from_configuration( Examples -------- - Create report from JSON file. >>> from ansys.aedt.core import Hfss >>> hfss = Hfss() @@ -1688,15 +1694,15 @@ def _update_props(prop_in, props_out): props.get("context", {"context": {}}).get("secondary_sweep", "") == "" and props.get("report_type", "") != "Rectangular Contour Plot" ): - report._props["context"]["secondary_sweep"] = "" - _update_props(props, report._props) + report._legacy_props["context"]["secondary_sweep"] = "" + _update_props(props, report._legacy_props) for el, k in self._app.available_variations.nominal_w_values_dict.items(): if ( - report._props.get("context", None) - and report._props["context"].get("variations", None) - and el not in report._props["context"]["variations"] + report._legacy_props.get("context", None) + and report._legacy_props["context"].get("variations", None) + and el not in report._legacy_props["context"]["variations"] ): - report._props["context"]["variations"][el] = k + report._legacy_props["context"]["variations"][el] = k _ = report.expressions if matplotlib: if props.get("report_type", "").lower() in ["eye diagram", "statistical eye"]: # pragma: no cover @@ -1716,40 +1722,42 @@ def _update_props(prop_in, props_out): def _report_plotter(self, report): sols = report.get_solution_data() report_plotter = ReportPlotter() - report_plotter.title = report._props.get("plot_name", "PyAEDT Report") + report_plotter.title = report._legacy_props.get("plot_name", "PyAEDT Report") try: report_plotter.general_back_color = [ - i / 255 for i in report._props["general"]["appearance"]["background_color"] + i / 255 for i in report._legacy_props["general"]["appearance"]["background_color"] ] except KeyError: pass try: - report_plotter.general_plot_color = [i / 255 for i in report._props["general"]["appearance"]["plot_color"]] + report_plotter.general_plot_color = [ + i / 255 for i in report._legacy_props["general"]["appearance"]["plot_color"] + ] except KeyError: pass try: - report_plotter.grid_enable_major_x = report._props["general"]["grid"]["major_x"] + report_plotter.grid_enable_major_x = report._legacy_props["general"]["grid"]["major_x"] except KeyError: pass try: - report_plotter.grid_enable_minor_x = report._props["general"]["grid"]["minor_x"] + report_plotter.grid_enable_minor_x = report._legacy_props["general"]["grid"]["minor_x"] except KeyError: pass try: - report_plotter.grid_enable_major_y = report._props["general"]["grid"]["major_y"] + report_plotter.grid_enable_major_y = report._legacy_props["general"]["grid"]["major_y"] except KeyError: pass try: - report_plotter.grid_enable_minor_yi = report._props["general"]["grid"]["minor_y"] + report_plotter.grid_enable_minor_yi = report._legacy_props["general"]["grid"]["minor_y"] except KeyError: pass try: - report_plotter.grid_color = [i / 255 for i in report._props["general"]["grid"]["major_color"]] + report_plotter.grid_color = [i / 255 for i in report._legacy_props["general"]["grid"]["major_color"]] except KeyError: pass try: - report_plotter.show_legend = True if report._props["general"]["legend"] else False + report_plotter.show_legend = True if report._legacy_props["general"]["legend"] else False except KeyError: pass sw = sols.primary_sweep_values @@ -1758,7 +1766,7 @@ def _report_plotter(self, report): "x_label": sols.primary_sweep, "y_label": curve, } - pp = [i for i in report._props["expressions"] if i["name"] == curve] + pp = [i for i in report._legacy_props["expressions"] if i["name"] == curve] if pp: pp = pp[0] try: @@ -1795,7 +1803,7 @@ def _report_plotter(self, report): except KeyError: pass report_plotter.add_trace([sw, sols.data_real(curve)], 0, properties=props, name=curve) - for name, line in report._props.get("limitLines", {}).items(): + for name, line in report._legacy_props.get("limitLines", {}).items(): props = {} try: props["trace_width"] = line["width"] @@ -1809,16 +1817,16 @@ def _report_plotter(self, report): report_plotter.add_limit_line([line["xpoints"], line["ypoints"]], 0, properties=props, name=name) except KeyError: self.logger.warning("Equation lines not supported yet.") - if report._props.get("report_type", "Rectangular Plot") == "Rectangular Plot": + if report._legacy_props.get("report_type", "Rectangular Plot") == "Rectangular Plot": _ = report_plotter.plot_2d() return report_plotter - elif report._props.get("report_type", "Rectangular Plot") == "Polar Plot": + elif report._legacy_props.get("report_type", "Rectangular Plot") == "Polar Plot": _ = report_plotter.plot_polar() return report_plotter - elif report._props.get("report_type", "Rectangular Plot") == "Rectangular Contour Plot": + elif report._legacy_props.get("report_type", "Rectangular Plot") == "Rectangular Contour Plot": _ = report_plotter.plot_contour() return report_plotter - elif report._props.get("report_type", "Rectangular Plot") in ["3D Polar Plot", "3D Spherical Plot"]: + elif report._legacy_props.get("report_type", "Rectangular Plot") in ["3D Polar Plot", "3D Spherical Plot"]: _ = report_plotter.plot_3d() return report_plotter @@ -2017,7 +2025,6 @@ def cg_fields(self, expressions=None, setup=None, polyline=None): Examples -------- - >>> from ansys.aedt.core import Q3d >>> q3d = Q3d(my_project) >>> report = q3d.post.reports_by_category.cg_fields("SmoothQ", "Setup : LastAdaptive", "Polyline1") @@ -2058,7 +2065,6 @@ def dc_fields(self, expressions=None, setup=None, polyline=None): Examples -------- - >>> from ansys.aedt.core import Q3d >>> q3d = Q3d(my_project) >>> report = q3d.post.reports_by_category.dc_fields("Mag_VolumeJdc", "Setup : LastAdaptive", "Polyline1") @@ -2099,7 +2105,6 @@ def rl_fields(self, expressions=None, setup=None, polyline=None): Examples -------- - >>> from ansys.aedt.core import Q3d >>> q3d = Q3d(my_project) >>> report = q3d.post.reports_by_category.rl_fields("Mag_SurfaceJac", "Setup : LastAdaptive", "Polyline1") @@ -2143,7 +2148,6 @@ def far_field(self, expressions=None, setup=None, sphere_name=None, source_conte Examples -------- - >>> from ansys.aedt.core import Hfss >>> hfss = Hfss(my_project) >>> report = hfss.post.reports_by_category.far_field("GainTotal", "Setup : LastAdaptive", "3D_Sphere") @@ -2184,7 +2188,6 @@ def antenna_parameters(self, expressions=None, setup=None, infinite_sphere=None) Examples -------- - >>> from ansys.aedt.core import Hfss >>> hfss = Hfss(my_project) >>> report = hfss.post.reports_by_category.antenna_parameters("GainTotal", "Setup : LastAdaptive", "3D_Sphere") @@ -2222,7 +2225,6 @@ def near_field(self, expressions=None, setup=None): Examples -------- - >>> from ansys.aedt.core import Hfss >>> hfss = Hfss(my_project) >>> report = hfss.post.reports_by_category.near_field("GainTotal", "Setup : LastAdaptive", "NF_1") @@ -2259,7 +2261,6 @@ def modal_solution(self, expressions=None, setup=None): Examples -------- - >>> from ansys.aedt.core import Hfss >>> hfss = Hfss(my_project) >>> report = hfss.post.reports_by_category.modal_solution("dB(S(1,1))") @@ -2295,7 +2296,6 @@ def terminal_solution(self, expressions=None, setup=None): Examples -------- - >>> from ansys.aedt.core import Hfss >>> hfss = Hfss(my_project) >>> report = hfss.post.reports_by_category.terminal_solution("dB(S(1,1))") @@ -2333,7 +2333,6 @@ def eigenmode(self, expressions=None, setup=None): Examples -------- - >>> from ansys.aedt.core import Hfss >>> hfss = Hfss(my_project) >>> report = hfss.post.reports_by_category.eigenmode("dB(S(1,1))") @@ -2376,7 +2375,6 @@ def statistical_eye_contour(self, expressions=None, setup=None, quantity_type=3) Examples -------- - >>> from ansys.aedt.core import Circuit >>> cir= Circuit() >>> new_eye = cir.post.reports_by_category.statistical_eye_contour("V(Vout)") @@ -2434,14 +2432,12 @@ def eye_diagram( Examples -------- - >>> from ansys.aedt.core import Circuit >>> cir= Circuit() >>> new_eye = cir.post.reports_by_category.eye_diagram("V(Vout)") >>> new_eye.unit_interval = "1e-9s" >>> new_eye.time_stop = "100ns" >>> new_eye.create() - """ if not setup: setup = self._post_app._app.nominal_sweep @@ -2487,7 +2483,6 @@ def spectral(self, expressions=None, setup=None): Examples -------- - >>> from ansys.aedt.core import Circuit >>> cir= Circuit() >>> new_eye = cir.post.reports_by_category.spectral("V(Vout)") @@ -2523,12 +2518,10 @@ def emi_receiver(self, expressions=None, setup_name=None): Examples -------- - >>> from ansys.aedt.core import Circuit >>> cir= Circuit() >>> new_eye = cir.post.emi_receiver() >>> new_eye.create() - """ if not setup_name: setup_name = self._post_app._app.nominal_sweep diff --git a/src/ansys/aedt/core/visualization/post/compliance.py b/src/ansys/aedt/core/visualization/post/compliance.py index 19fed86b555..96b29cd80ca 100644 --- a/src/ansys/aedt/core/visualization/post/compliance.py +++ b/src/ansys/aedt/core/visualization/post/compliance.py @@ -1082,6 +1082,7 @@ def _add_eye_measurement(self, report, pdf_report, image_name): @pyaedt_function_handler() def add_specs_to_report(self, folder): """Add specs to the report from a given folder. + All images in such folder will be added to the report. Parameters diff --git a/src/ansys/aedt/core/visualization/post/farfield_exporter.py b/src/ansys/aedt/core/visualization/post/farfield_exporter.py index 8edd492fe29..f81f88a8d66 100644 --- a/src/ansys/aedt/core/visualization/post/farfield_exporter.py +++ b/src/ansys/aedt/core/visualization/post/farfield_exporter.py @@ -142,7 +142,6 @@ def metadata_file(self): @pyaedt_function_handler() def export_farfield(self): """Export far field solution data of each element.""" - # Output directory exported_name_map = "element.txt" solution_setup_name = self.setup_name.replace(":", "_").replace(" ", "") diff --git a/src/ansys/aedt/core/visualization/post/field_data.py b/src/ansys/aedt/core/visualization/post/field_data.py index 384ef3800c3..5ee20b03e71 100644 --- a/src/ansys/aedt/core/visualization/post/field_data.py +++ b/src/ansys/aedt/core/visualization/post/field_data.py @@ -127,7 +127,8 @@ def map_type(self, value): def color(self): """Get the color based on the map type. - Returns: + Returns + ------- str or list of float: The color scheme based on the map type. """ if self.map_type == "Spectrum": @@ -387,14 +388,14 @@ def format_type(self): def format_type(self, v): """Set the numeric format type of the scale. - Parameters: - ----------- - v (str): The new format type to be set. Must be one of the accepted values + Parameters + ---------- + v (str): The new format type to be set. Must be one of the accepted values ("Automatic", "Scientific" or "Decimal"). - Raises: - ------- - ValueError: If the provided value is not in the list of accepted values. + Raises + ------ + ValueError: If the provided value is not in the list of accepted values. """ if v is not None and v in self._accepted: self._format_type = v @@ -633,7 +634,7 @@ def marker_type(self): def marker_type(self, v): """Set the type of maker to use. - Parameters: + Parameters ---------- v : str Marker type. Must be one of the allowed types @@ -981,7 +982,8 @@ def __init__( def _parse_folder_settings(self): """Parse the folder settings for the field plot from the AEDT file. - Returns: + Returns + ------- FolderPlotSettings or None: An instance of FolderPlotSettings if found, otherwise None. """ folder_settings_data = load_keyword_in_aedt_file( diff --git a/src/ansys/aedt/core/visualization/post/fields_calculator.py b/src/ansys/aedt/core/visualization/post/fields_calculator.py index 7114c734c2a..218bbad8ad9 100644 --- a/src/ansys/aedt/core/visualization/post/fields_calculator.py +++ b/src/ansys/aedt/core/visualization/post/fields_calculator.py @@ -504,7 +504,6 @@ def validate_expression(self, expression): dict or bool Expression if the input expression is valid, ``False`` otherwise. """ - if not isinstance(expression, dict): self.__app.logger.error("Incorrect data type.") return False @@ -780,6 +779,7 @@ def export( The default is ``[0, 0, 0]``. is_vector : bool, optional Whether the quantity is a vector. The default is ``False``. + Returns ------- bool or str diff --git a/src/ansys/aedt/core/visualization/post/monitor_icepak.py b/src/ansys/aedt/core/visualization/post/monitor_icepak.py index fb86e2a96cd..9d299e997a2 100644 --- a/src/ansys/aedt/core/visualization/post/monitor_icepak.py +++ b/src/ansys/aedt/core/visualization/post/monitor_icepak.py @@ -422,7 +422,6 @@ def assign_surface_monitor(self, surface_name, monitor_quantity="Temperature", m Examples -------- - Create a rectangle named ``"Surface1"`` and assign a temperature monitor to that surface. >>> surface = icepak.modeler.create_rectangle(icepak.PLANE.XY,[0, 0, 0],[10, 20],name="Surface1") @@ -520,12 +519,10 @@ def assign_point_monitor_in_object(self, name, monitor_quantity="Temperature", m References ---------- - >>> oModule.AssignPointMonitor Examples -------- - Create a box named ``"BlockBox1"`` and assign a temperature monitor point to that object. >>> box = icepak.modeler.create_box([1, 1, 1],[3, 3, 3],"BlockBox1","copper") @@ -782,9 +779,9 @@ def type(self): @pyaedt_function_handler(setup_name="setup") def value(self, quantity=None, setup=None, design_variation_dict=None, si_out=True): - """ - Get a list of values obtained from the monitor object. If the simulation is steady state, - the list will contain just one element. + """Get a list of values obtained from the monitor object. + + If the simulation is steady state, the list will contain just one element. Parameters ---------- diff --git a/src/ansys/aedt/core/visualization/post/post_3dlayout.py b/src/ansys/aedt/core/visualization/post/post_3dlayout.py index 9f1d0b918af..cf47a90f200 100644 --- a/src/ansys/aedt/core/visualization/post/post_3dlayout.py +++ b/src/ansys/aedt/core/visualization/post/post_3dlayout.py @@ -21,6 +21,9 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from pathlib import Path +import re + from ansys.aedt.core import settings from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.visualization.post.post_common_3d import PostProcessor3D @@ -43,44 +46,124 @@ class PostProcessor3DLayout(PostProcessor3D): def __init__(self, app): PostProcessor3D.__init__(self, app) - def _check_inputs(self, layers=None, nets=None, solution=None): - if layers is None: - layers = list(self._app.modeler.edb.stackup.signal_layers.keys()) - if nets is None: - nets = [] - for k in self._app.modeler.edb.sources.values(): - nets.append(k.net_name) - try: - nets.append(k.reference_net_name) - except AttributeError: - pass - try: - nets.append(k.ref_terminal.net_name) - except AttributeError: - pass - nets = list(set(nets)) + @pyaedt_function_handler + def _compute_power_loss(self, net_filter=None, layer_filter=None, solution=None): if solution is None: for setup in self._app.setups: if setup.solver_type == "SIwaveDCIR": solution = setup.name + break + if solution is None: # pragma: no cover + self._app.logger.error("No SIwave DC IR setup exist.") + return else: - for setup in self._app.setups: - if setup.name == solution and setup.solver_type != "SIwaveDCIR": - self._app.logger.error("Wrong Setup. It has to be an SIwave DCIR solution.") - solution = None - return layers, nets, solution + solution_obj = [i for i in self._app.setups if i.name == solution] + if len(solution_obj): + if solution_obj[0].solver_type != "SIwaveDCIR": # pragma: no cover + self._app.logger.error(f"Solution {solution} is not a SIwave DC IR setup.") + return + else: # pragma: no cover + self._app.logger.error(f"Solution {solution} doesn't exist.") + return + + solution_data_dir = Path(self._app.project_file).with_suffix(".aedtresults") / "main" + subfolders = [f for f in solution_data_dir.iterdir() if f.is_dir()] + dcir_solution_folder = None + for folder in subfolders: + file_exec = folder / "SIwave.exec" + if not file_exec.exists(): + continue # pragma: no cover + with open(file_exec, "r") as f: + siwave_exec = f.read() + match = re.search(r'SetupName\s+"(.*?)"', siwave_exec) + if match.group(1) == solution: + dcir_solution_folder = folder + break + if dcir_solution_folder is None: # pragma: no cover + self._app.logger.error(f"Solution {solution} has no result.") + else: + file_net = None + for i in dcir_solution_folder.iterdir(): + if i.suffix == ".net": + file_net = i + break + with open(file_net, "r") as f: + file_net_text = f.read() + match = re.search(r"B_NET_CLASSIFICATION\s+(.*?)\s+E_NET_CLASSIFICATION", file_net_text, re.DOTALL) + nets = [] + for i in match.group(1).split("\n"): + net_name = i.lstrip(" ").split(" ")[1] + nets.append(net_name) + + if net_filter is not None: + nets = [i for i in nets if i in net_filter] + + edbapp = self._app.modeler.edb + + if layer_filter is None: + net_per_layer_names = {i: [] for i in edbapp.stackup.signal_layers.keys()} + else: + net_per_layer_names = {i: [] for i in edbapp.stackup.signal_layers.keys() if i in layer_filter} + + for net_name in nets: + net_obj = edbapp.nets[net_name] + layer_names = [i.layer_name for i in net_obj.primitives] + layer_names = list(set(layer_names)) + for i in layer_names: + if i in net_per_layer_names: + net_per_layer_names[i].append(net_name) + + power_loss_per_layer = [] + operations = [] + for layer_name, net_names in net_per_layer_names.items(): + if not net_names: + continue + thickness = edbapp.stackup[layer_name].thickness + for net_name in net_names: + assignment = f"{layer_name}_{net_name}" + operations.extend( + [ + "Fundamental_Quantity('P')", + f"EnterSurface('{assignment}')", + "Operation('SurfaceValue')", + "Operation('Integrate')", + ] + ) + + operations.extend([f"Scalar_Constant({thickness})", "Operation('*')"]) + + solution_type = ["DC Fields"] if settings.aedt_version < "2025.1" else ["DCIR Fields"] + my_expression = { + "name": f"Power_{layer_name}", + "description": "Power Density", + "design_type": ["HFSS 3D Layout Design"], + "fields_type": solution_type, + "solution_type": "", + "primary_sweep": "", + "assignment": "", + "assignment_type": ["Surface"], + "operations": operations, + "report": ["Data Table", "Rectangular Plot"], + } + if self._app.post.fields_calculator.is_expression_defined(my_expression["name"]): + self._app.post.fields_calculator.delete_expression(my_expression["name"]) + self._app.post.fields_calculator.add_expression(my_expression, "") + + loss = self._app.post.fields_calculator.evaluate(my_expression["name"], solution, intrinsics={}) + + power_loss_per_layer.append({"layer": layer_name, "net": net_name, "loss": float(loss)}) + + return power_loss_per_layer @pyaedt_function_handler() - def compute_power_by_layer(self, layers=None, nets=None, solution=None): + def compute_power_by_layer(self, layers=None, solution=None): """Computes the power by layer. This applies only to SIwave DC Analysis. Parameters ---------- - layers : list + layers : list, optional Layers to include in power calculation. - nets : list - Nets to include in power calculation. - solution : str + solution : str, optional SIwave DCIR solution. Returns @@ -88,69 +171,26 @@ def compute_power_by_layer(self, layers=None, nets=None, solution=None): dict Power by layer. """ - layers, nets, solution = self._check_inputs(layers, nets, solution) - if layers is None or nets is None or solution is None: - self._app.logger.error("Check inputs.") - return False power_by_layers = {} - solution_type = ["DC Fields"] if settings.aedt_version < "2025.1" else ["DCIR Fields"] - for layer in layers: - thickness = self._app.modeler.edb.stackup[layer].thickness - operations = [] - idx = 0 - for net in nets: - try: - get_ids = self._app.odesign.GetGeometryIdsForNetLayerCombination(net, layer, solution) - except Exception: # pragma no cover - get_ids = [] - if not get_ids: - continue - assignment = f"{layer}_{net}" - operations.extend( - [ - "Fundamental_Quantity('P')", - f"EnterSurface('{assignment}')", - "Operation('SurfaceValue')", - "Operation('Integrate')", - ] - ) - idx += 1 - if idx > 1: - operations.append("Operation('+')") - operations.extend([f"Scalar_Constant({thickness})", "Operation('*')"]) - if idx == 0: - continue - my_expression = { - "name": f"Power_{layer}", - "description": "Power Density", - "design_type": ["HFSS 3D Layout Design"], - "fields_type": solution_type, - "solution_type": "", - "primary_sweep": "", - "assignment": "", - "assignment_type": ["Surface"], - "operations": operations, - "report": ["Data Table", "Rectangular Plot"], - } - if self._app.post.fields_calculator.is_expression_defined(my_expression["name"]): - self._app.post.fields_calculator.delete_expression(my_expression["name"]) - self._app.post.fields_calculator.add_expression(my_expression, "") - power_by_layers[layer] = self._app.post.fields_calculator.evaluate( - my_expression["name"], solution, intrinsics={} - ) + power_loss = self._compute_power_loss(layer_filter=layers, solution=solution) + for i in power_loss: + layer_name = i["layer"] + loss = i["loss"] + if layer_name not in power_by_layers: + power_by_layers[layer_name] = loss + else: + power_by_layers[layer_name] += loss return power_by_layers @pyaedt_function_handler() - def compute_power_by_nets(self, nets=None, layers=None, solution=None): + def compute_power_by_net(self, nets=None, solution=None): """Computes the power by nets. This applies only to SIwave DC Analysis. Parameters ---------- - layers : list - List of layers to include in power calculation. - nets : list - List of nets to include in power calculation. - solution : str + nets : list, optional + Layers to include in power calculation. + solution : str, optional SIwave DCIR solution. Returns @@ -158,54 +198,13 @@ def compute_power_by_nets(self, nets=None, layers=None, solution=None): dict Power by nets. """ - layers, nets, solution = self._check_inputs(layers, nets, solution) - if layers is None or nets is None or solution is None: - self._app.logger.error("Check inputs.") - return False power_by_nets = {} - solution_type = ["DC Fields"] if settings.aedt_version < "2025.1" else ["DCIR Fields"] - for net in nets: - operations = [] - idx = 0 - for layer in layers: - thickness = self._app.modeler.edb.stackup[layer].thickness - try: - get_ids = self._app.odesign.GetGeometryIdsForNetLayerCombination(net, layer, solution) - except Exception: # pragma no cover - get_ids = [] - if not get_ids: - continue - assignment = f"{layer}_{net}" - operations.extend( - [ - "Fundamental_Quantity('P')", - f"EnterSurface('{assignment}')", - "Operation('SurfaceValue')", - "Operation('Integrate')", - ] - ) - idx += 1 - if idx > 1: - operations.append("Operation('+')") - operations.extend([f"Scalar_Constant({thickness})", "Operation('*')"]) - if idx == 0: - continue - my_expression = { - "name": f"Power_{net}", - "description": "Power Density", - "design_type": ["HFSS 3D Layout Design"], - "fields_type": solution_type, - "solution_type": "", - "primary_sweep": "", - "assignment": "", - "assignment_type": ["Surface"], - "operations": operations, - "report": ["Data Table", "Rectangular Plot"], - } - if self._app.post.fields_calculator.is_expression_defined(my_expression["name"]): - self._app.post.fields_calculator.delete_expression(my_expression["name"]) - self._app.post.fields_calculator.add_expression(my_expression, "") - power_by_nets[layer] = self._app.post.fields_calculator.evaluate( - my_expression["name"], solution, intrinsics={} - ) + power_loss = self._compute_power_loss(net_filter=nets, solution=solution) + for i in power_loss: + net_name = i["net"] + loss = i["loss"] + if net_name not in power_by_nets: + power_by_nets[net_name] = loss + else: + power_by_nets[net_name] += loss return power_by_nets diff --git a/src/ansys/aedt/core/visualization/post/post_circuit.py b/src/ansys/aedt/core/visualization/post/post_circuit.py index cc4de54953b..eb596e62e53 100644 --- a/src/ansys/aedt/core/visualization/post/post_circuit.py +++ b/src/ansys/aedt/core/visualization/post/post_circuit.py @@ -196,7 +196,6 @@ def create_ami_statistical_eye_plot( References ---------- - >>> oModule.CreateReport """ if not plot_name: @@ -298,7 +297,6 @@ def create_statistical_eye_plot(self, setup, probe_names, variation_list_w_value References ---------- - >>> oModule.CreateReport """ if not plot_name: @@ -410,9 +408,7 @@ def sample_waveform( >>> from ansys.aedt.core import Circuit >>> circuit = Circuit() >>> circuit.post.sample_ami_waveform(name,probe_name,source_name,circuit.available_variations.nominal) - """ - new_tic = [] for tic in clock_tics: new_tic.append(unit_converter(tic, unit_system="Time", input_units="s", output_units=waveform_sweep_unit)) diff --git a/src/ansys/aedt/core/visualization/post/post_common_3d.py b/src/ansys/aedt/core/visualization/post/post_common_3d.py index 238b148b3a5..9bc4b86a3cb 100644 --- a/src/ansys/aedt/core/visualization/post/post_common_3d.py +++ b/src/ansys/aedt/core/visualization/post/post_common_3d.py @@ -23,7 +23,7 @@ # SOFTWARE. """ -This module contains this class: `PostProcessor3D`. +Module containing the class: `PostProcessor3D`. This module provides all functionalities for creating and editing plots in the 3D tools. @@ -440,7 +440,6 @@ def get_scalar_field_value( References ---------- - >>> oModule.EnterQty >>> oModule.CopyNamedExprToStack >>> oModule.CalcOp @@ -1416,6 +1415,7 @@ def create_fieldplot_layers( ): # type: (list, str, str, list, bool, dict, str) -> FieldPlot """Create a field plot of stacked layer plot. + This plot is valid from AEDT 2023 R2 and later in HFSS 3D Layout. Nets can be used as a filter. Dielectrics will be included into the plot. It works when a layout components in 3d modeler is used. @@ -1591,6 +1591,7 @@ def create_fieldplot_layers_nets( ): # type: (list, str, str, dict, bool, str) -> FieldPlot """Create a field plot of stacked layer plot on specified matrix of layers and nets. + This plot is valid from AEDT 2023 R2 and later in HFSS 3D Layout and any modeler where a layout component is used. @@ -1966,7 +1967,6 @@ def export_field_jpg( References ---------- - >>> oModule.ExportPlotImageToFile >>> oModule.ExportModelImageToFile """ @@ -2180,7 +2180,6 @@ def get_far_field_data(self, expressions="GainTotal", setup_sweep_name="", domai References ---------- - >>> oModule.GetSolutionDataPerVariation """ if not isinstance(expressions, list): @@ -2233,7 +2232,7 @@ def export_model_obj(self, assignment=None, export_path=None, export_as_multiple if not assignment: self._app.modeler.refresh_all_ids() non_model = self._app.modeler.non_model_objects[:] - assignment = [i for i in self._app.modeler.object_names if i not in non_model] + assignment = [i for i in self._app.modeler.object_names if i not in non_model and "PML_" not in i] if not air_objects: assignment = [ i @@ -2269,6 +2268,7 @@ def export_model_obj(self, assignment=None, export_path=None, export_as_multiple @pyaedt_function_handler(setup_name="setup") def export_mesh_obj(self, setup=None, intrinsics=None, export_air_objects=False, on_surfaces=True): """Export the mesh in AEDTPLT format. + The mesh has to be available in the selected setup. If a parametric model is provided, you can choose the mesh to export by providing a specific set of variations. This method applies only to ``Hfss``, ``Q3d``, ``Q2D``, ``Maxwell3d``, ``Maxwell2d``, ``Icepak`` @@ -2979,20 +2979,19 @@ def nb_display(self, show_axis=True, show_grid=True, show_ruler=True): .. note:: .assign_curvature_extraction Jupyter Notebook is not supported by IronPython. - Parameters - ---------- - show_axis : bool, optional - Whether to show the axes. The default is ``True``. - show_grid : bool, optional - Whether to show the grid. The default is ``True``. - show_ruler : bool, optional - Whether to show the ruler. The default is ``True``. + Parameters + ---------- + show_axis : bool, optional + Whether to show the axes. The default is ``True``. + show_grid : bool, optional + Whether to show the grid. The default is ``True``. + show_ruler : bool, optional + Whether to show the ruler. The default is ``True``. Returns ------- :class:`IPython.core.display.Image` Jupyter notebook image. - """ try: from IPython.display import Image @@ -3117,7 +3116,6 @@ def get_model_plotter_geometries( :class:`ansys.aedt.core.generic.plot.ModelPlotter` Model Object. """ - if self._app._aedt_version < "2021.2": raise RuntimeError("Object is supported from AEDT 2021 R2.") # pragma: no cover @@ -3804,7 +3802,6 @@ def plot_scene( ): """Plot the current model 3D scene with overlapping animation coming from a file list and save the gif. - Parameters ---------- frames : list or str @@ -3834,7 +3831,6 @@ def plot_scene( Returns ------- - """ if isinstance(frames, str) and os.path.exists(frames): with open_file(frames, "r") as f: diff --git a/src/ansys/aedt/core/visualization/post/post_icepak.py b/src/ansys/aedt/core/visualization/post/post_icepak.py index 942971f816b..b08e911ab2c 100644 --- a/src/ansys/aedt/core/visualization/post/post_icepak.py +++ b/src/ansys/aedt/core/visualization/post/post_icepak.py @@ -74,8 +74,7 @@ def create_field_summary(self): @pyaedt_function_handler(timestep="time_step", design_variation="variation") def get_fans_operating_point(self, export_file=None, setup_name=None, time_step=None, variation=None): - """ - Get the operating point of the fans in the design. + """Get the operating point of the fans in the design. Parameters ---------- @@ -104,7 +103,6 @@ def get_fans_operating_point(self, export_file=None, setup_name=None, time_step= References ---------- - >>> oModule.ExportFanOperatingPoint Examples @@ -114,7 +112,6 @@ def get_fans_operating_point(self, export_file=None, setup_name=None, time_step= >>> ipk.create_fan() >>> filename, vol_flow_name, p_rise_name, op_dict= ipk.get_fans_operating_point() """ - if export_file is None: path = self._app.temp_directory base_name = f"{self._app.project_name}_{self._app.design_name}_FanOpPoint" diff --git a/src/ansys/aedt/core/visualization/post/rcs_exporter.py b/src/ansys/aedt/core/visualization/post/rcs_exporter.py index 4592a769ea6..7767c1060a1 100644 --- a/src/ansys/aedt/core/visualization/post/rcs_exporter.py +++ b/src/ansys/aedt/core/visualization/post/rcs_exporter.py @@ -143,7 +143,6 @@ def column_name(self, value): @pyaedt_function_handler() def get_monostatic_rcs(self): """Get RCS solution data.""" - variations = self.variations variations["IWaveTheta"] = ["All"] variations["IWavePhi"] = ["All"] @@ -163,7 +162,6 @@ def get_monostatic_rcs(self): @pyaedt_function_handler() def export_rcs(self, name="rcs_data", metadata_name="pyaedt_rcs_metadata"): """Export RCS solution data.""" - # Output directory solution_setup_name = self.setup_name.replace(":", "_").replace(" ", "") full_setup = f"{solution_setup_name}" diff --git a/src/ansys/aedt/core/visualization/post/solution_data.py b/src/ansys/aedt/core/visualization/post/solution_data.py index aedec767547..5d4ddbf1f81 100644 --- a/src/ansys/aedt/core/visualization/post/solution_data.py +++ b/src/ansys/aedt/core/visualization/post/solution_data.py @@ -91,8 +91,9 @@ def __init__(self, aedtdata): @property def enable_pandas_output(self): - """ - Set/Get a flag to use Pandas to export dict and lists. This applies to Solution data output. + """Set/Get a flag to use Pandas to export dict and lists. + + This applies to Solution data output. If ``True`` the property or method will return a pandas object in CPython environment. Default is ``False``. @@ -220,7 +221,6 @@ def update_sweeps(self): dict Updated sweeps. """ - names = list(self.nominal_variation.GetSweepNames()) for data in self._original_data: for v in data.GetDesignVariableNames(): @@ -281,7 +281,7 @@ def _init_solution_data_mag(self): @pyaedt_function_handler() def _init_solution_data_real(self): - """ """ + """Initialize the real part of the solution data.""" sols_data = {} for expression in self.expressions: diff --git a/src/ansys/aedt/core/visualization/post/spisim.py b/src/ansys/aedt/core/visualization/post/spisim.py index 03ac4735ea1..92263064bcf 100644 --- a/src/ansys/aedt/core/visualization/post/spisim.py +++ b/src/ansys/aedt/core/visualization/post/spisim.py @@ -212,7 +212,6 @@ def compute_erl( bool or float Effective return loss from the spisimExe command, ``False`` when failed. """ - cfg_dict = { "INPARRY": "", "MIXMODE": "", @@ -329,9 +328,7 @@ def compute_com( Returns ------- - """ - com_param = COMParametersVer3p4() if standard == 0: if os.path.splitext(config_file)[-1] == ".cfg": @@ -392,6 +389,7 @@ def export_com_configure_file(self, file_path, standard=1): Full path to configuration file to create. standard : int Index of the standard. + Returns ------- bool @@ -424,9 +422,8 @@ def detect_encoding(file_path, expected_pattern="", re_flags=0): class DataSet(object): - """ - This is the base class for storing all traces of a RAW file. Returned by the get_trace() or by the get_axis() - methods. + """Base class for storing all traces of a RAW file. Returned by the get_trace() or by the get_axis() methods. + Normally the user doesn't have to be aware of this class. It is only used internally to encapsulate the different implementations of the wave population. Data can be retrieved directly by using the [] operator. @@ -442,7 +439,9 @@ def __init__( datalen, ): """Base Class for both Axis and Trace Classes. - Defines the common operations between both.""" + + Defines the common operations between both. + """ self.name = name self.whattype = whattype self.data = zeros(datalen, dtype=float64) @@ -470,6 +469,7 @@ def wave(self): class Trace(DataSet): """This class is used to represent a trace. + This class is constructed by the get_trace() command. If numpy is available the get_wave() method will return a numpy array. """ @@ -617,8 +617,7 @@ def get_raw_property(self, property_name=None): @property def trace_names(self): - """ - Returns a list of exiting trace names of the RAW file. + """Returns a list of exiting trace names of the RAW file. Returns ------- @@ -628,7 +627,7 @@ def trace_names(self): return [trace.name for trace in self._traces] def get_trace(self, trace_ref): - """Retrieves the trace with the requested name (trace_ref). + """Retrieve the trace with the requested name (trace_ref). Parameters ---------- @@ -648,7 +647,7 @@ def get_trace(self, trace_ref): return self._traces[trace_ref] def get_wave(self, trace_ref): - """Retrieves the trace data with the requested name (trace_ref). + """Retrieve the wave data with the requested name (trace_ref). Parameters ---------- @@ -663,7 +662,7 @@ def get_wave(self, trace_ref): return self.get_trace(trace_ref).wave def get_axis(self): - """This function is equivalent to get_trace(0).wave instruction. + """Function equivalent to get_trace(0).wave instruction. Returns ------- diff --git a/src/ansys/aedt/core/visualization/report/common.py b/src/ansys/aedt/core/visualization/report/common.py index 2d4aa353ed6..5386b6d3f1e 100644 --- a/src/ansys/aedt/core/visualization/report/common.py +++ b/src/ansys/aedt/core/visualization/report/common.py @@ -32,10 +32,11 @@ from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.general_methods import write_configuration_file +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode from ansys.aedt.core.modeler.geometry_operators import GeometryOperators -class LimitLine(object): +class LimitLine(BinaryTreeNode): """Line Limit Management Class.""" def __init__(self, report_setup, trace_name, oo=None): @@ -43,24 +44,11 @@ def __init__(self, report_setup, trace_name, oo=None): self._oreport_setup = report_setup self.line_name = trace_name self.LINESTYLE = LineStyle() + self._initialize_tree_node() - @property - def properties(self): - """Line properties. - - Returns - ------- - :class:`ansys.aedt.core.modeler.cad.elements_3d.BinaryTree` when successful, - ``False`` when failed. - - """ - from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode - - try: - parent = BinaryTreeNode(self.line_name, self._oo, False) - return parent.props - except Exception: - return [] + @pyaedt_function_handler() + def _initialize_tree_node(self): + BinaryTreeNode.__init__(self, self.line_name, self._oo, False) @pyaedt_function_handler() def _change_property(self, props_value): @@ -113,31 +101,14 @@ def set_line_properties( return self._change_property(props) -class Note(object): +class Note(BinaryTreeNode): """Note Management Class.""" def __init__(self, report_setup, plot_note_name, oo=None): self._oo = oo self._oreport_setup = report_setup self.plot_note_name = plot_note_name - - @property - def properties(self): - """Note properties. - - Returns - ------- - :class:`ansys.aedt.core.modeler.cad.elements_3d.BinaryTree` when successful, - ``False`` when failed. - - """ - from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode - - try: - parent = BinaryTreeNode(self.plot_note_name, self._oo, False) - return parent.props - except Exception: - return [] + BinaryTreeNode.__init__(self, self.plot_note_name, self._oo, False) @pyaedt_function_handler() def _change_property(self, props_value): @@ -257,7 +228,7 @@ def set_note_properties( return self._change_property(props) -class Trace(object): +class Trace(BinaryTreeNode): """Provides trace management.""" def __init__( @@ -283,31 +254,11 @@ def __init__( self._symbol_color = None self._show_symbol = False self._available_props = [] + self._initialize_tree_node() - @property - def __all_props(self): - from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode - - try: - parent = BinaryTreeNode(self.aedt_name, self._oo, False) - return parent - except Exception: - return [] - - @property - def properties(self): - """All available properties. - - Returns - ------- - :class:`ansys.aedt.core.modeler.cad.elements_3d.BinaryTree` when successful, - ``False`` when failed. - - """ - try: - return self.__all_props.props - except Exception: - return {} + @pyaedt_function_handler() + def _initialize_tree_node(self): + BinaryTreeNode.__init__(self, self.aedt_name, self._oo, False) @property def curve_properties(self): @@ -319,8 +270,8 @@ def curve_properties(self): ``False`` when failed. """ - if self.aedt_name.split(":")[-1] in self.__all_props.children: - return self.__all_props.children[self.aedt_name.split(":")[-1]].props + if self.aedt_name.split(":")[-1] in self.children: + return self.children[self.aedt_name.split(":")[-1]].properties return {} @property @@ -435,64 +386,54 @@ def set_symbol_properties(self, show=True, style=None, show_arrows=None, fill=No return self._change_property(props) -class CommonReport(object): +class CommonReport(BinaryTreeNode): """Provides common reports.""" def __init__(self, app, report_category, setup_name, expressions=None): self._post = app - self._props = {} - self._props["report_category"] = report_category + self._legacy_props = {} + self._legacy_props["report_category"] = report_category self.setup = setup_name - self._props["report_type"] = "Rectangular Plot" - self._props["context"] = {} - self._props["context"]["domain"] = "Sweep" - self._props["context"]["primary_sweep"] = "Freq" - self._props["context"]["primary_sweep_range"] = ["All"] - self._props["context"]["secondary_sweep_range"] = ["All"] - self._props["context"]["variations"] = {"Freq": ["All"]} + self._legacy_props["report_type"] = "Rectangular Plot" + self._legacy_props["context"] = {} + self._legacy_props["context"]["domain"] = "Sweep" + self._legacy_props["context"]["primary_sweep"] = "Freq" + self._legacy_props["context"]["primary_sweep_range"] = ["All"] + self._legacy_props["context"]["secondary_sweep_range"] = ["All"] + self._legacy_props["context"]["variations"] = {"Freq": ["All"]} if hasattr(self._post._app, "available_variations") and self._post._app.available_variations: for el, k in self._post._app.available_variations.nominal_w_values_dict.items(): - self._props["context"]["variations"][el] = k - self._props["expressions"] = None - self._props["plot_name"] = None + self._legacy_props["context"]["variations"][el] = k + self._legacy_props["expressions"] = None + self._legacy_props["plot_name"] = None if expressions: self.expressions = expressions self._is_created = False self.siwave_dc_category = 0 self._traces = [] - self._child_object = None + self._initialize_tree_node() + + @pyaedt_function_handler() + def _initialize_tree_node(self): + if self._is_created: + oo = self._post.oreportsetup.GetChildObject(self._legacy_props["plot_name"]) + if oo: + BinaryTreeNode.__init__(self, self.plot_name, oo, False) @property - def _all_props(self): - if self._child_object: - return self._child_object + def __all_props(self): from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode try: - oo = self._post.oreportsetup.GetChildObject(self._props["plot_name"]) - self._child_object = BinaryTreeNode(self.plot_name, oo, False) - for var in [i.split(" ,")[-1] for i in list(self._child_object.props.values())[4:]]: - if var in self._child_object.children: - del self._child_object.children[var] - els = [i for i in self._child_object.children.keys() if i.startswith("LimitLine") or i.startswith("Note")] + oo = self._post.oreportsetup.GetChildObject(self._legacy_props["plot_name"]) + _child_object = BinaryTreeNode(self.plot_name, oo, False) + for var in [i.split(" ,")[-1] for i in list(_child_object.properties.values())[4:]]: + if var in _child_object.children: + del _child_object.children[var] + els = [i for i in _child_object.children.keys() if i.startswith("LimitLine") or i.startswith("Note")] for var in els: - del self._child_object.children[var] - return self._child_object - except Exception: - return {} - - @property - def properties(self): - """Report properties. - - Returns - ------- - :class:`ansys.aedt.core.modeler.cad.elements_3d.BinaryTree` when successful, - ``False`` when failed. - - """ - try: - return self._all_props + del _child_object.children[var] + return _child_object except Exception: return {} @@ -515,11 +456,11 @@ def differential_pairs(self): bool ``True`` when differential pairs is enabled, ``False`` otherwise. """ - return self._props["context"].get("differential_pairs", False) + return self._legacy_props["context"].get("differential_pairs", False) @differential_pairs.setter def differential_pairs(self, value): - self._props["context"]["differential_pairs"] = value + self._legacy_props["context"]["differential_pairs"] = value @property def matrix(self): @@ -539,16 +480,16 @@ def matrix(self): ): try: if "Parameter" in self.traces[0].properties: - self._props["context"]["matrix"] = self.traces[0].properties["Parameter"] + self._legacy_props["context"]["matrix"] = self.traces[0].properties["Parameter"] elif "Matrix" in self.traces[0].properties: - self._props["context"]["matrix"] = self.traces[0].properties["Matrix"] + self._legacy_props["context"]["matrix"] = self.traces[0].properties["Matrix"] except Exception: self._post._app.logger.warning("Property `matrix` not found.") - return self._props["context"].get("matrix", None) + return self._legacy_props["context"].get("matrix", None) @matrix.setter def matrix(self, value): - self._props["context"]["matrix"] = value + self._legacy_props["context"]["matrix"] = value @property def reduced_matrix(self): @@ -559,11 +500,11 @@ def reduced_matrix(self): str Reduced matrix name. """ - return self._props["context"].get("reduced_matrix", None) + return self._legacy_props["context"].get("reduced_matrix", None) @reduced_matrix.setter def reduced_matrix(self, value): - self._props["context"]["reduced_matrix"] = value + self._legacy_props["context"]["reduced_matrix"] = value @property def polyline(self): @@ -576,14 +517,14 @@ def polyline(self): """ if self._is_created and self.report_category != "Far Fields" and self.report_category.endswith("Fields"): try: - self._props["context"]["polyline"] = self.traces[0].properties["Geometry"] + self._legacy_props["context"]["polyline"] = self.traces[0].properties["Geometry"] except Exception: self._post._app.logger.debug("Something went wrong while processing polyline.") - return self._props["context"].get("polyline", None) + return self._legacy_props["context"].get("polyline", None) @polyline.setter def polyline(self, value): - self._props["context"]["polyline"] = value + self._legacy_props["context"]["polyline"] = value @property def expressions(self): @@ -594,28 +535,29 @@ def expressions(self): list Expressions. """ + self._initialize_tree_node() if self._is_created: - return [i.split(" ,")[-1] for i in list(self.properties.props.values())[4:]] - if self._props.get("expressions", None) is None: + return [i.split(" ,")[-1] for i in list(self.properties.values())[4:]] + if self._legacy_props.get("expressions", None) is None: return [] - return [k.get("name", None) for k in self._props["expressions"] if k.get("name", None) is not None] + return [k.get("name", None) for k in self._legacy_props["expressions"] if k.get("name", None) is not None] @expressions.setter def expressions(self, value): if isinstance(value, dict): - self._props["expressions"].append(value) + self._legacy_props["expressions"].append(value) elif isinstance(value, list): - self._props["expressions"] = [] + self._legacy_props["expressions"] = [] for el in value: if isinstance(el, dict): - self._props["expressions"].append(el) + self._legacy_props["expressions"].append(el) else: - self._props["expressions"].append({"name": el}) + self._legacy_props["expressions"].append({"name": el}) elif isinstance(value, str): - if isinstance(self._props["expressions"], list): - self._props["expressions"].append({"name": value}) + if isinstance(self._legacy_props["expressions"], list): + self._legacy_props["expressions"].append({"name": value}) else: - self._props["expressions"] = [{"name": value}] + self._legacy_props["expressions"] = [{"name": value}] @property def report_category(self): @@ -628,15 +570,15 @@ def report_category(self): """ if self._is_created: try: - return self.properties.props["Report Type"] + return self.properties["Report Type"] except Exception: - return self._props["report_category"] - return self._props["report_category"] + return self._legacy_props["report_category"] + return self._legacy_props["report_category"] @report_category.setter def report_category(self, value): if not self._is_created: - self._props["report_category"] = value + self._legacy_props["report_category"] = value @property def report_type(self): @@ -651,20 +593,20 @@ def report_type(self): """ if self._is_created: try: - return self.properties.props["Display Type"] + return self.properties["Display Type"] except Exception: - return self._props["report_type"] - return self._props["report_type"] + return self._legacy_props["report_type"] + return self._legacy_props["report_type"] @report_type.setter def report_type(self, report): if not self._is_created: - self._props["report_type"] = report + self._legacy_props["report_type"] = report if not self.primary_sweep: - if self._props["report_type"] in ["3D Polar Plot", "3D Spherical Plot"]: + if self._legacy_props["report_type"] in ["3D Polar Plot", "3D Spherical Plot"]: self.primary_sweep = "Phi" self.secondary_sweep = "Theta" - elif self._props["report_type"] == "Radiation Pattern": + elif self._legacy_props["report_type"] == "Radiation Pattern": self.primary_sweep = "Phi" elif self.domain == "Sweep": self.primary_sweep = "Freq" @@ -711,7 +653,7 @@ def traces(self): def _update_traces(self): for trace in self.traces[::]: trace_name = trace.name - for trace_val in self._props["expressions"]: + for trace_val in self._legacy_props["expressions"]: if trace_val["name"] == trace_name: trace_style = self.__props_with_default(trace_val, "trace_style") trace_width = self.__props_with_default(trace_val, "width") @@ -724,7 +666,7 @@ def _update_traces(self): ) for trace in self.traces[::]: trace_name = trace.name - for trace_val in self._props["expressions"]: + for trace_val in self._legacy_props["expressions"]: if trace_val["name"] == trace_name: if self.report_category in ["Eye Diagram", "Spectrum"]: continue @@ -743,7 +685,7 @@ def _update_traces(self): ) for trace in self.traces[::]: trace_name = trace.name - for trace_val in self._props["expressions"]: + for trace_val in self._legacy_props["expressions"]: if trace_val["name"] == trace_name: y_axis = self.__props_with_default(trace_val, "y_axis", "Y1") if y_axis != "Y1": @@ -761,61 +703,61 @@ def _update_traces(self): ) if ( - "eye_mask" in self._props + "eye_mask" in self._legacy_props and self.report_category in ["Eye Diagram", "Statistical Eye"] - or ("quantity_type" in self._props and self.report_type == "Rectangular Contour Plot") + or ("quantity_type" in self._legacy_props and self.report_type == "Rectangular Contour Plot") ): - eye_xunits = self.__props_with_default(self._props["eye_mask"], "xunits", "ns") - eye_yunits = self.__props_with_default(self._props["eye_mask"], "yunits", "mV") - eye_points = self.__props_with_default(self._props["eye_mask"], "points") - eye_enable = self.__props_with_default(self._props["eye_mask"], "enable_limits", False) - eye_upper = self.__props_with_default(self._props["eye_mask"], "upper_limit", 500) - eye_lower = self.__props_with_default(self._props["eye_mask"], "lower_limit", 0.3) - eye_transparency = self.__props_with_default(self._props["eye_mask"], "transparency", 0.3) - eye_color = self.__props_with_default(self._props["eye_mask"], "color", (0, 128, 0)) - eye_xoffset = self.__props_with_default(self._props["eye_mask"], "X Offset", "0ns") - eye_yoffset = self.__props_with_default(self._props["eye_mask"], "Y Offset", "0V") - if "quantity_type" in self._props and self.report_type == "Rectangular Contour Plot": - if "contours_number" in self._props.get("general", {}): + eye_xunits = self.__props_with_default(self._legacy_props["eye_mask"], "xunits", "ns") + eye_yunits = self.__props_with_default(self._legacy_props["eye_mask"], "yunits", "mV") + eye_points = self.__props_with_default(self._legacy_props["eye_mask"], "points") + eye_enable = self.__props_with_default(self._legacy_props["eye_mask"], "enable_limits", False) + eye_upper = self.__props_with_default(self._legacy_props["eye_mask"], "upper_limit", 500) + eye_lower = self.__props_with_default(self._legacy_props["eye_mask"], "lower_limit", 0.3) + eye_transparency = self.__props_with_default(self._legacy_props["eye_mask"], "transparency", 0.3) + eye_color = self.__props_with_default(self._legacy_props["eye_mask"], "color", (0, 128, 0)) + eye_xoffset = self.__props_with_default(self._legacy_props["eye_mask"], "X Offset", "0ns") + eye_yoffset = self.__props_with_default(self._legacy_props["eye_mask"], "Y Offset", "0V") + if "quantity_type" in self._legacy_props and self.report_type == "Rectangular Contour Plot": + if "contours_number" in self._legacy_props.get("general", {}): self._change_property( "Contour", f" Plot {self.traces[0].name}", [ "NAME:ChangedProps", - ["NAME:Num. Contours", "Value:=", str(self._props["general"]["contours_number"])], + ["NAME:Num. Contours", "Value:=", str(self._legacy_props["general"]["contours_number"])], ], ) - if "contours_scale" in self._props.get("general", {}): + if "contours_scale" in self._legacy_props.get("general", {}): self._change_property( "Contour", f" Plot {self.traces[0].name}", [ "NAME:ChangedProps", - ["NAME:Axis Scale", "Value:=", str(self._props["general"]["contours_scale"])], + ["NAME:Axis Scale", "Value:=", str(self._legacy_props["general"]["contours_scale"])], ], ) - if "enable_contours_auto_limit" in self._props.get("general", {}): + if "enable_contours_auto_limit" in self._legacy_props.get("general", {}): self._change_property( "Contour", f" Plot {self.traces[0].name}", ["NAME:ChangedProps", ["NAME:Scale Type", "Value:=", "Auto Limits"]], ) - elif "contours_min_limit" in self._props.get("general", {}): + elif "contours_min_limit" in self._legacy_props.get("general", {}): self._change_property( "Contour", f" Plot {self.traces[0].name}", [ "NAME:ChangedProps", - ["NAME:Min", "Value:=", str(self._props["general"]["contours_min_limit"])], + ["NAME:Min", "Value:=", str(self._legacy_props["general"]["contours_min_limit"])], ], ) - elif "contours_max_limit" in self._props.get("general", {}): + elif "contours_max_limit" in self._legacy_props.get("general", {}): self._change_property( "Contour", f" Plot {self.traces[0].name}", [ "NAME:ChangedProps", - ["NAME:Max", "Value:=", str(self._props["general"]["contours_max_limit"])], + ["NAME:Max", "Value:=", str(self._legacy_props["general"]["contours_max_limit"])], ], ) self.eye_mask( @@ -830,8 +772,8 @@ def _update_traces(self): x_offset=eye_xoffset, y_offset=eye_yoffset, ) - if "limitLines" in self._props and self.report_category not in ["Eye Diagram", "Statistical Eye"]: - for line in self._props["limitLines"].values(): + if "limitLines" in self._legacy_props and self.report_category not in ["Eye Diagram", "Statistical Eye"]: + for line in self._legacy_props["limitLines"].values(): if "equation" in line: line_start = self.__props_with_default(line, "start") line_stop = self.__props_with_default(line, "stop") @@ -867,8 +809,8 @@ def _update_traces(self): hatch_pixels=line_hatchpix, color=line_color, ) - if "notes" in self._props: - for note in self._props["notes"].values(): + if "notes" in self._legacy_props: + for note in self._legacy_props["notes"].values(): note_text = self.__props_with_default(note, "text") note_position = self.__props_with_default(note, "position", [0, 0]) self.add_note(note_text, note_position[0], note_position[1]) @@ -895,12 +837,14 @@ def _update_traces(self): bold=note_bold, color=note_color, ) - if "general" in self._props: - if "show_rectangular_plot" in self._props["general"] and self.report_category in ["Eye Diagram"]: - eye_rectangular = self.__props_with_default(self._props["general"], "show_rectangular_plot", True) + if "general" in self._legacy_props: + if "show_rectangular_plot" in self._legacy_props["general"] and self.report_category in ["Eye Diagram"]: + eye_rectangular = self.__props_with_default( + self._legacy_props["general"], "show_rectangular_plot", True + ) self.rectangular_plot(eye_rectangular) - if "legend" in self._props["general"] and self.report_type != "Rectangular Contour Plot": - legend = self._props["general"]["legend"] + if "legend" in self._legacy_props["general"] and self.report_type != "Rectangular Contour Plot": + legend = self._legacy_props["general"]["legend"] legend_sol_name = self.__props_with_default(legend, "show_solution_name", True) legend_var_keys = self.__props_with_default(legend, "show_variation_key", True) leend_trace_names = self.__props_with_default(legend, "show_trace_name", True) @@ -913,8 +857,8 @@ def _update_traces(self): back_color=legend_color, font_color=legend_font_color, ) - if "grid" in self._props["general"]: - grid = self._props["general"]["grid"] + if "grid" in self._legacy_props["general"]: + grid = self._legacy_props["general"]["grid"] grid_major_color = self.__props_with_default(grid, "major_color", (200, 200, 200)) grid_minor_color = self.__props_with_default(grid, "minor_color", (230, 230, 230)) grid_enable_major_x = self.__props_with_default(grid, "major_x", True) @@ -933,12 +877,12 @@ def _update_traces(self): style_minor=grid_style_minor, style_major=grid_style_major, ) - if "appearance" in self._props["general"]: - general = self._props["general"]["appearance"] + if "appearance" in self._legacy_props["general"]: + general = self._legacy_props["general"]["appearance"] general_back_color = self.__props_with_default(general, "background_color", (255, 255, 255)) general_plot_color = self.__props_with_default(general, "plot_color", (255, 255, 255)) enable_y_stripes = self.__props_with_default(general, "enable_y_stripes", True) - if self._props["report_type"] == "Radiation Pattern": + if self._legacy_props["report_type"] == "Radiation Pattern": enable_y_stripes = None general_field_width = self.__props_with_default(general, "field_width", 4) general_precision = self.__props_with_default(general, "precision", 4) @@ -951,8 +895,8 @@ def _update_traces(self): precision=general_precision, use_scientific_notation=general_use_scientific_notation, ) - if "header" in self._props["general"]: - header = self._props["general"]["header"] + if "header" in self._legacy_props["general"]: + header = self._legacy_props["general"]["header"] company_name = self.__props_with_default(header, "company_name", "") show_design_name = self.__props_with_default(header, "show_design_name", True) header_font = self.__props_with_default(header, "font", "Arial") @@ -972,9 +916,9 @@ def _update_traces(self): color=header_color, ) - for i in list(self._props["general"].keys()): + for i in list(self._legacy_props["general"].keys()): if "axis" in i: - axis = self._props["general"][i] + axis = self._legacy_props["general"][i] axis_font = self.__props_with_default(axis, "font", "Arial") axis_size = self.__props_with_default(axis, "font_size", 12) axis_italic = self.__props_with_default(axis, "italic", False) @@ -1095,14 +1039,14 @@ def plot_name(self): str Plot name. """ - return self._props["plot_name"] + return self._legacy_props["plot_name"] @plot_name.setter def plot_name(self, name): if self._is_created: if name not in self._post.oreportsetup.GetAllReportNames(): - self._post.oreportsetup.RenameReport(self._props["plot_name"], name) - self._props["plot_name"] = name + self._post.oreportsetup.RenameReport(self._legacy_props["plot_name"], name) + self._legacy_props["plot_name"] = name @property def variations(self): @@ -1131,15 +1075,15 @@ def variations(self): variations[tr.properties["Primary Sweep"]] = ["All"] if tr.properties.get("Secondary Sweep", None): variations[tr.properties["Secondary Sweep"]] = ["All"] - self._props["context"]["variations"] = variations + self._legacy_props["context"]["variations"] = variations except Exception: self._post._app.logger.debug("Something went wrong while processing variations.") - return self._props["context"]["variations"] + return self._legacy_props["context"]["variations"] @variations.setter def variations(self, value): - self._props["context"]["variations"] = value + self._legacy_props["context"]["variations"] = value @property def primary_sweep(self): @@ -1151,14 +1095,14 @@ def primary_sweep(self): Primary sweep. """ if self._is_created: - return list(self.properties.props.values())[4].split(" ,")[0] - return self._props["context"]["primary_sweep"] + return list(self.properties.values())[4].split(" ,")[0] + return self._legacy_props["context"]["primary_sweep"] @primary_sweep.setter def primary_sweep(self, value): - if value == self._props["context"].get("secondary_sweep", None): - self._props["context"]["secondary_sweep"] = self._props["context"]["primary_sweep"] - self._props["context"]["primary_sweep"] = value + if value == self._legacy_props["context"].get("secondary_sweep", None): + self._legacy_props["context"]["secondary_sweep"] = self._legacy_props["context"]["primary_sweep"] + self._legacy_props["context"]["primary_sweep"] = value if value == "Time": self.variations.pop("Freq", None) self.variations["Time"] = ["All"] @@ -1176,16 +1120,16 @@ def secondary_sweep(self): Secondary sweep. """ if self._is_created: - els = list(self.properties.props.values())[4].split(" ,") + els = list(self.properties.values())[4].split(" ,") return els[1] if len(els) == 3 else None - return self._props["context"].get("secondary_sweep", None) + return self._legacy_props["context"].get("secondary_sweep", None) @secondary_sweep.setter def secondary_sweep(self, value): - if value == self._props["context"]["primary_sweep"]: - self._props["context"]["primary_sweep"] = self._props["context"]["secondary_sweep"] - self._props["context"]["secondary_sweep"] = value + if value == self._legacy_props["context"]["primary_sweep"]: + self._legacy_props["context"]["primary_sweep"] = self._legacy_props["context"]["secondary_sweep"] + self._legacy_props["context"]["secondary_sweep"] = value if value == "Time": self.variations.pop("Freq", None) self.variations["Time"] = ["All"] @@ -1202,11 +1146,11 @@ def primary_sweep_range(self): str Primary sweep range. """ - return self._props["context"]["primary_sweep_range"] + return self._legacy_props["context"]["primary_sweep_range"] @primary_sweep_range.setter def primary_sweep_range(self, value): - self._props["context"]["primary_sweep_range"] = value + self._legacy_props["context"]["primary_sweep_range"] = value @property def secondary_sweep_range(self): @@ -1217,11 +1161,11 @@ def secondary_sweep_range(self): str Secondary sweep range. """ - return self._props["context"]["secondary_sweep_range"] + return self._legacy_props["context"]["secondary_sweep_range"] @secondary_sweep_range.setter def secondary_sweep_range(self, value): - self._props["context"]["secondary_sweep_range"] = value + self._legacy_props["context"]["secondary_sweep_range"] = value @property def _context(self): @@ -1293,11 +1237,11 @@ def domain(self): return self.traces[0].properties["Domain"] except Exception: self._post._app.logger.debug("Something went wrong while accessing trace's Domain property.") - return self._props["context"]["domain"] + return self._legacy_props["context"]["domain"] @domain.setter def domain(self, domain): - self._props["context"]["domain"] = domain + self._legacy_props["context"]["domain"] = domain if self._post._app.design_type in ["Maxwell 3D", "Maxwell 2D"]: return if self.primary_sweep == "Freq" and domain == "Time": @@ -1318,11 +1262,11 @@ def use_pulse_in_tdr(self): bool ``True`` when option is enabled, ``False`` otherwise. """ - return self._props["context"].get("use_pulse_in_tdr", False) + return self._legacy_props["context"].get("use_pulse_in_tdr", False) @use_pulse_in_tdr.setter def use_pulse_in_tdr(self, val): - self._props["context"]["use_pulse_in_tdr"] = val + self._legacy_props["context"]["use_pulse_in_tdr"] = val @pyaedt_function_handler() def _convert_dict_to_report_sel(self, sweeps): @@ -1388,6 +1332,7 @@ def create(self, name=None): ) self._post.plots.append(self) self._is_created = True + self._initialize_tree_node() return True @pyaedt_function_handler() @@ -1458,7 +1403,7 @@ def _export_context(self, output_dict): def _export_expressions(self, output_dict): output_dict["expressions"] = {} for expr in self.traces: - name = self.properties.props[expr.name].split(" ,")[-1] + name = self.properties[expr.name].split(" ,")[-1] pr = expr.curve_properties output_dict["expressions"][name] = {} if "Trace Type" in pr: @@ -1485,8 +1430,8 @@ def _export_graphical_objects(self, output_dict): from ansys.aedt.core.visualization.report.eye import AMIEyeDiagram from ansys.aedt.core.visualization.report.eye import EyeDiagram - if isinstance(self, (AMIEyeDiagram, EyeDiagram)) and "EyeDisplayTypeProperty" in self.properties.children: - pr = self.properties.children["EyeDisplayTypeProperty"].props + if isinstance(self, (AMIEyeDiagram, EyeDiagram)) and "EyeDisplayTypeProperty" in self.children: + pr = self.children["EyeDisplayTypeProperty"].properties if pr.get("Mask/MaskPoints", None) and len(pr["Mask/MaskPoints"]) > 1: pts_x = pr["Mask/MaskPoints"][1::2] pts_y = pr["Mask/MaskPoints"][2::2] @@ -1553,8 +1498,8 @@ def _export_general_appearance(self, output_dict): from ansys.aedt.core.visualization.report.eye import EyeDiagram output_dict["general"] = {} - if "AxisX" in self.properties.children: - axis = self.properties.children["AxisX"].props + if "AxisX" in self.children: + axis = self.children["AxisX"].properties output_dict["general"]["axisx"] = { "label": axis["Name"], "font": axis["Text Font/FaceName"], @@ -1569,9 +1514,9 @@ def _export_general_appearance(self, output_dict): } if not isinstance(self, (AMIEyeDiagram, EyeDiagram)): output_dict["general"]["axisx"]["linear_scaling"] = True if axis["Axis Scaling"] == "Linear" else False - y_axis_available = [i for i in self.properties.children.keys() if i.startswith("AxisY")] + y_axis_available = [i for i in self.children.keys() if i.startswith("AxisY")] for yaxis in y_axis_available: - axis = self.properties.children[yaxis].props + axis = self.children[yaxis].properties output_dict["general"][yaxis.lower()] = { "label": axis["Name"], "font": axis["Text Font/FaceName"], @@ -1587,8 +1532,8 @@ def _export_general_appearance(self, output_dict): output_dict["general"][yaxis.lower()]["linear_scaling"] = ( True if axis["Axis Scaling"] == "Linear" else False ) - if "General" in self.properties.children: - props = self.properties.children["General"].props + if "General" in self.children: + props = self.children["General"].properties output_dict["general"]["appearance"] = { "background_color": [ props["Back Color/Red"], @@ -1607,8 +1552,8 @@ def _export_general_appearance(self, output_dict): "use_scientific_notation": props["Use Scientific Notation"], } - if "Grid" in self.properties.children: - props = self.properties.children["Grid"].props + if "Grid" in self.children: + props = self.children["Grid"].properties output_dict["general"]["grid"] = { "major_color": [ props["Major grid line color/Red"], @@ -1627,8 +1572,8 @@ def _export_general_appearance(self, output_dict): "style_major": props["Major grid line style"], "style_minor": props["Minor grid line style"], } - if "Legend" in self.properties.children: - props = self.properties.children["Legend"].props + if "Legend" in self.children: + props = self.children["Legend"].properties output_dict["general"]["legend"] = { "back_color": [ props["Back Color/Red"], @@ -1644,8 +1589,8 @@ def _export_general_appearance(self, output_dict): "show_variation_key": props["Show Variation Key"], "show_trace_name": props["Show Trace Name"], } - if "Header" in self.properties.children: - props = self.properties.children["Header"].props + if "Header" in self.children: + props = self.children["Header"].properties output_dict["general"]["header"] = { "font": props["Title Font/FaceName"], "title_size": props["Title Font/Height"], @@ -2548,6 +2493,7 @@ def delete_traces(self, plot_name, traces_list): props = [f"{plot_name}:=", traces_list] try: self._post.oreportsetup.DeleteTraces(props) + self._initialize_tree_node() return True except Exception: return False @@ -2585,6 +2531,7 @@ def add_trace_to_report(self, traces, setup_name=None, variations=None, context= self._convert_dict_to_report_sel(variations if variations else self.variations), self._trace_info, ) + self._initialize_tree_node() return True except Exception: return False diff --git a/src/ansys/aedt/core/visualization/report/emi.py b/src/ansys/aedt/core/visualization/report/emi.py index c4f9d643518..f43b14905fb 100644 --- a/src/ansys/aedt/core/visualization/report/emi.py +++ b/src/ansys/aedt/core/visualization/report/emi.py @@ -82,11 +82,11 @@ def band(self): str Band name. """ - return self._props["context"].get("band", None) + return self._legacy_props["context"].get("band", None) @band.setter def band(self, value): - self._props["context"]["band"] = value + self._legacy_props["context"]["band"] = value @property def emission(self): @@ -105,10 +105,10 @@ def emission(self): def emission(self, value): if value == "CE": self._emission = value - self._props["context"]["emission"] = "0" + self._legacy_props["context"]["emission"] = "0" elif value == "RE": self._emission = value - self._props["context"]["emission"] = "1" + self._legacy_props["context"]["emission"] = "1" else: self.logger.error(f"Emission must be 'CE' or 'RE', value '{value}' is not valid.") @@ -121,11 +121,11 @@ def time_start(self): str Time start. """ - return self._props["context"].get("time_start", None) + return self._legacy_props["context"].get("time_start", None) @time_start.setter def time_start(self, value): - self._props["context"]["time_start"] = value + self._legacy_props["context"]["time_start"] = value @property def time_stop(self): @@ -136,11 +136,11 @@ def time_stop(self): str Time stop. """ - return self._props["context"].get("time_stop", None) + return self._legacy_props["context"].get("time_stop", None) @time_stop.setter def time_stop(self, value): - self._props["context"]["time_stop"] = value + self._legacy_props["context"]["time_stop"] = value @property def _context(self): diff --git a/src/ansys/aedt/core/visualization/report/eye.py b/src/ansys/aedt/core/visualization/report/eye.py index da06cd5b26d..e9c7bc4c434 100644 --- a/src/ansys/aedt/core/visualization/report/eye.py +++ b/src/ansys/aedt/core/visualization/report/eye.py @@ -32,6 +32,7 @@ from ansys.aedt.core import generate_unique_name from ansys.aedt.core import pyaedt_function_handler +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode from ansys.aedt.core.visualization.report.common import CommonReport @@ -41,13 +42,13 @@ class AMIConturEyeDiagram(CommonReport): def __init__(self, app, report_category, setup_name, expressions=None): CommonReport.__init__(self, app, report_category, setup_name, expressions) self.domain = "Time" - self._props["report_type"] = "Rectangular Contour Plot" + self._legacy_props["report_type"] = "Rectangular Contour Plot" self.variations.pop("Time", None) - self._props["context"]["variations"]["__UnitInterval"] = ["All"] - self._props["context"]["variations"]["__Amplitude"] = ["All"] - self._props["context"]["variations"]["__EyeOpening"] = ["0"] - self._props["context"]["primary_sweep"] = "__UnitInterval" - self._props["context"]["secondary_sweep"] = "__Amplitude" + self._legacy_props["context"]["variations"]["__UnitInterval"] = ["All"] + self._legacy_props["context"]["variations"]["__Amplitude"] = ["All"] + self._legacy_props["context"]["variations"]["__EyeOpening"] = ["0"] + self._legacy_props["context"]["primary_sweep"] = "__UnitInterval" + self._legacy_props["context"]["secondary_sweep"] = "__Amplitude" self.quantity_type = 0 self.min_latch_overlay = "0" self.noise_floor = "1e-16" @@ -69,12 +70,12 @@ def expressions(self): Expressions. """ if self._is_created: - return [i.split(" ,")[-1] for i in list(self.properties.props.values())[4:]] - if self._props.get("expressions", None) is None: + return [i.split(" ,")[-1] for i in list(self.properties.values())[4:]] + if self._legacy_props.get("expressions", None) is None: return [] expr_head = "Eye" new_exprs = [] - for expr_dict in self._props["expressions"]: + for expr_dict in self._legacy_props["expressions"]: expr = expr_dict["name"] if not ".int_ami" in expr: qtype = int(self.quantity_type) @@ -93,19 +94,19 @@ def expressions(self): @expressions.setter def expressions(self, value): if isinstance(value, dict): - self._props["expressions"].append = value + self._legacy_props["expressions"].append = value elif isinstance(value, list): - self._props["expressions"] = [] + self._legacy_props["expressions"] = [] for el in value: if isinstance(el, dict): - self._props["expressions"].append(el) + self._legacy_props["expressions"].append(el) else: - self._props["expressions"].append({"name": el}) + self._legacy_props["expressions"].append({"name": el}) elif isinstance(value, str): - if isinstance(self._props["expressions"], list): - self._props["expressions"].append({"name": value}) + if isinstance(self._legacy_props["expressions"], list): + self._legacy_props["expressions"].append({"name": value}) else: - self._props["expressions"] = [{"name": value}] + self._legacy_props["expressions"] = [{"name": value}] @property def quantity_type(self): @@ -116,11 +117,11 @@ def quantity_type(self): int Quantity type. """ - return self._props.get("quantity_type", 0) + return self._legacy_props.get("quantity_type", 0) @quantity_type.setter def quantity_type(self, value): - self._props["quantity_type"] = value + self._legacy_props["quantity_type"] = value @property def _context(self): @@ -307,7 +308,9 @@ def create(self, name=None): ) self._post.plots.append(self) self._is_created = True - + oo = self._post.oreportsetup.GetChildObject(self._legacy_props["plot_name"]) + if oo: + BinaryTreeNode.__init__(self, self.plot_name, oo, False) return True @pyaedt_function_handler(xunits="x_units", yunits="y_units", xoffset="x_offset", yoffset="y_offset") @@ -499,7 +502,7 @@ def __init__(self, app, report_category, setup_name, expressions=None): CommonReport.__init__(self, app, report_category, setup_name, expressions) self.domain = "Time" if report_category == "Statistical Eye": - self._props["report_type"] = "Statistical Eye Plot" + self._legacy_props["report_type"] = "Statistical Eye Plot" self.variations.pop("Time", None) self.variations["__UnitInterval"] = "All" self.variations["__Amplitude"] = "All" @@ -523,14 +526,14 @@ def expressions(self): Expressions. """ if self._is_created: - return [i.split(" ,")[-1] for i in list(self.properties.props.values())[4:]] - if self._props.get("expressions", None) is None: + return [i.split(" ,")[-1] for i in list(self.properties.values())[4:]] + if self._legacy_props.get("expressions", None) is None: return [] expr_head = "Wave" if self.report_category == "Statistical Eye": expr_head = "Eye" new_exprs = [] - for expr_dict in self._props["expressions"]: + for expr_dict in self._legacy_props["expressions"]: expr = expr_dict["name"] if not ".int_ami" in expr: qtype = int(self.quantity_type) @@ -557,11 +560,11 @@ def quantity_type(self): int Quantity type. """ - return self._props.get("quantity_type", 0) + return self._legacy_props.get("quantity_type", 0) @quantity_type.setter def quantity_type(self, value): - self._props["quantity_type"] = value + self._legacy_props["quantity_type"] = value @property def report_category(self): @@ -576,19 +579,19 @@ def report_category(self): try: return self.properties.props["Report Type"] except Exception: - return self._props["report_category"] - return self._props["report_category"] + return self._legacy_props["report_category"] + return self._legacy_props["report_category"] @report_category.setter def report_category(self, value): - self._props["report_category"] = value - if self._props["report_category"] == "Statistical Eye" and self.report_type == "Rectangular Plot": - self._props["report_type"] = "Statistical Eye Plot" + self._legacy_props["report_category"] = value + if self._legacy_props["report_category"] == "Statistical Eye" and self.report_type == "Rectangular Plot": + self._legacy_props["report_type"] = "Statistical Eye Plot" self.variations.pop("Time", None) self.variations["__UnitInterval"] = "All" self.variations["__Amplitude"] = "All" - elif self._props["report_category"] == "Eye Diagram" and self.report_type == "Statistical Eye Plot": - self._props["report_type"] = "Rectangular Plot" + elif self._legacy_props["report_category"] == "Eye Diagram" and self.report_type == "Statistical Eye Plot": + self._legacy_props["report_type"] = "Rectangular Plot" self.variations.pop("__UnitInterval", None) self.variations.pop("__Amplitude", None) self.variations["Time"] = "All" @@ -602,11 +605,11 @@ def unit_interval(self): str Unit interval. """ - return self._props["context"].get("unit_interval", None) + return self._legacy_props["context"].get("unit_interval", None) @unit_interval.setter def unit_interval(self, value): - self._props["context"]["unit_interval"] = value + self._legacy_props["context"]["unit_interval"] = value @property def offset(self): @@ -617,11 +620,11 @@ def offset(self): str Offset value. """ - return self._props["context"].get("offset", None) + return self._legacy_props["context"].get("offset", None) @offset.setter def offset(self, value): - self._props["context"]["offset"] = value + self._legacy_props["context"]["offset"] = value @property def auto_delay(self): @@ -632,11 +635,11 @@ def auto_delay(self): bool ``True`` if auto-delay is enabled, ``False`` otherwise. """ - return self._props["context"].get("auto_delay", None) + return self._legacy_props["context"].get("auto_delay", None) @auto_delay.setter def auto_delay(self, value): - self._props["context"]["auto_delay"] = value + self._legacy_props["context"]["auto_delay"] = value @property def manual_delay(self): @@ -647,11 +650,11 @@ def manual_delay(self): str ``True`` if manual-delay is enabled, ``False`` otherwise. """ - return self._props["context"].get("manual_delay", None) + return self._legacy_props["context"].get("manual_delay", None) @manual_delay.setter def manual_delay(self, value): - self._props["context"]["manual_delay"] = value + self._legacy_props["context"]["manual_delay"] = value @property def auto_cross_amplitude(self): @@ -662,11 +665,11 @@ def auto_cross_amplitude(self): bool ``True`` if auto-cross amplitude is enabled, ``False`` otherwise. """ - return self._props["context"].get("auto_cross_amplitude", None) + return self._legacy_props["context"].get("auto_cross_amplitude", None) @auto_cross_amplitude.setter def auto_cross_amplitude(self, value): - self._props["context"]["auto_cross_amplitude"] = value + self._legacy_props["context"]["auto_cross_amplitude"] = value @property def cross_amplitude(self): @@ -677,11 +680,11 @@ def cross_amplitude(self): str Cross-amplitude. """ - return self._props["context"].get("cross_amplitude", None) + return self._legacy_props["context"].get("cross_amplitude", None) @cross_amplitude.setter def cross_amplitude(self, value): - self._props["context"]["cross_amplitude"] = value + self._legacy_props["context"]["cross_amplitude"] = value @property def auto_compute_eye_meas(self): @@ -692,11 +695,11 @@ def auto_compute_eye_meas(self): bool ``True`` to compute eye measurements, ``False`` otherwise. """ - return self._props["context"].get("auto_compute_eye_meas", None) + return self._legacy_props["context"].get("auto_compute_eye_meas", None) @auto_compute_eye_meas.setter def auto_compute_eye_meas(self, value): - self._props["context"]["auto_compute_eye_meas"] = value + self._legacy_props["context"]["auto_compute_eye_meas"] = value @property def eye_measurement_point(self): @@ -707,11 +710,11 @@ def eye_measurement_point(self): str Eye measurement point. """ - return self._props["context"].get("eye_measurement_point", None) + return self._legacy_props["context"].get("eye_measurement_point", None) @eye_measurement_point.setter def eye_measurement_point(self, value): - self._props["context"]["eye_measurement_point"] = value + self._legacy_props["context"]["eye_measurement_point"] = value @property def _context(self): @@ -900,7 +903,9 @@ def create(self, name=None): ) self._post.plots.append(self) self._is_created = True - + oo = self._post.oreportsetup.GetChildObject(self._legacy_props["plot_name"]) + if oo: + BinaryTreeNode.__init__(self, self.plot_name, oo, False) return True @pyaedt_function_handler(xunits="x_units", yunits="y_units", xoffset="x_offset", yoffset="y_offset") @@ -1098,27 +1103,27 @@ def expressions(self): Expressions. """ if self._is_created: - return [i.split(" ,")[-1] for i in list(self.properties.props.values())[4:]] - if self._props.get("expressions", None) is None: + return [i.split(" ,")[-1] for i in list(self.properties.values())[4:]] + if self._legacy_props.get("expressions", None) is None: return [] - return [k.get("name", None) for k in self._props["expressions"] if k.get("name", None) is not None] + return [k.get("name", None) for k in self._legacy_props["expressions"] if k.get("name", None) is not None] @expressions.setter def expressions(self, value): if isinstance(value, dict): - self._props["expressions"].append = value + self._legacy_props["expressions"].append = value elif isinstance(value, list): - self._props["expressions"] = [] + self._legacy_props["expressions"] = [] for el in value: if isinstance(el, dict): - self._props["expressions"].append(el) + self._legacy_props["expressions"].append(el) else: - self._props["expressions"].append({"name": el}) + self._legacy_props["expressions"].append({"name": el}) elif isinstance(value, str): - if isinstance(self._props["expressions"], list): - self._props["expressions"].append({"name": value}) + if isinstance(self._legacy_props["expressions"], list): + self._legacy_props["expressions"].append({"name": value}) else: - self._props["expressions"] = [{"name": value}] + self._legacy_props["expressions"] = [{"name": value}] @property def time_start(self): @@ -1129,11 +1134,11 @@ def time_start(self): str Time start. """ - return self._props["context"].get("time_start", None) + return self._legacy_props["context"].get("time_start", None) @time_start.setter def time_start(self, value): - self._props["context"]["time_start"] = value + self._legacy_props["context"]["time_start"] = value @property def time_stop(self): @@ -1144,11 +1149,11 @@ def time_stop(self): str Time stop. """ - return self._props["context"].get("time_stop", None) + return self._legacy_props["context"].get("time_stop", None) @time_stop.setter def time_stop(self, value): - self._props["context"]["time_stop"] = value + self._legacy_props["context"]["time_stop"] = value @property def thinning(self): @@ -1159,11 +1164,11 @@ def thinning(self): bool ``True`` if thinning is enabled, ``False`` otherwise. """ - return self._props["context"].get("thinning", None) + return self._legacy_props["context"].get("thinning", None) @thinning.setter def thinning(self, value): - self._props["context"]["thinning"] = value + self._legacy_props["context"]["thinning"] = value @property def dy_dx_tolerance(self): @@ -1174,11 +1179,11 @@ def dy_dx_tolerance(self): float DY DX tolerance. """ - return self._props["context"].get("dy_dx_tolerance", None) + return self._legacy_props["context"].get("dy_dx_tolerance", None) @dy_dx_tolerance.setter def dy_dx_tolerance(self, value): - self._props["context"]["dy_dx_tolerance"] = value + self._legacy_props["context"]["dy_dx_tolerance"] = value @property def thinning_points(self): @@ -1189,11 +1194,11 @@ def thinning_points(self): int Number of thinning points. """ - return self._props["context"].get("thinning_points", None) + return self._legacy_props["context"].get("thinning_points", None) @thinning_points.setter def thinning_points(self, value): - self._props["context"]["thinning_points"] = value + self._legacy_props["context"]["thinning_points"] = value @property def _context(self): diff --git a/src/ansys/aedt/core/visualization/report/field.py b/src/ansys/aedt/core/visualization/report/field.py index a85b7571603..ee1cc945b10 100644 --- a/src/ansys/aedt/core/visualization/report/field.py +++ b/src/ansys/aedt/core/visualization/report/field.py @@ -51,15 +51,15 @@ def far_field_sphere(self): """ if self._is_created: try: - self._props["context"]["far_field_sphere"] = self.traces[0].properties["Geometry"] + self._legacy_props["context"]["far_field_sphere"] = self.traces[0].properties["Geometry"] except Exception: self._post._app.logger.warning("Property `far_field_sphere` not found.") - return self._props["context"].get("far_field_sphere", None) + return self._legacy_props["context"].get("far_field_sphere", None) @far_field_sphere.setter def far_field_sphere(self, value): - self._props["context"]["far_field_sphere"] = value + self._legacy_props["context"]["far_field_sphere"] = value @property def _context(self): @@ -87,11 +87,11 @@ def point_number(self): str Point number. """ - return self._props["context"].get("point_number", 1001) + return self._legacy_props["context"].get("point_number", 1001) @point_number.setter def point_number(self, value): - self._props["context"]["point_number"] = value + self._legacy_props["context"]["point_number"] = value @property def _context(self): @@ -121,11 +121,11 @@ def near_field(self): str Field name. """ - return self._props["context"].get("near_field", None) + return self._legacy_props["context"].get("near_field", None) @near_field.setter def near_field(self, value): - self._props["context"]["near_field"] = value + self._legacy_props["context"]["near_field"] = value class FarField(CommonReport): @@ -156,14 +156,14 @@ def far_field_sphere(self): """ if self._is_created: try: - self._props["context"]["far_field_sphere"] = self.traces[0].properties["Geometry"] + self._legacy_props["context"]["far_field_sphere"] = self.traces[0].properties["Geometry"] except Exception: self._post._app.logger.warning("Property `far_field_sphere` not found.") - return self._props["context"].get("far_field_sphere", None) + return self._legacy_props["context"].get("far_field_sphere", None) @far_field_sphere.setter def far_field_sphere(self, value): - self._props["context"]["far_field_sphere"] = value + self._legacy_props["context"]["far_field_sphere"] = value @property def _context(self): diff --git a/src/ansys/aedt/core/visualization/report/standard.py b/src/ansys/aedt/core/visualization/report/standard.py index 43425b20011..515545aee65 100644 --- a/src/ansys/aedt/core/visualization/report/standard.py +++ b/src/ansys/aedt/core/visualization/report/standard.py @@ -32,6 +32,7 @@ from ansys.aedt.core import generate_unique_name from ansys.aedt.core import pyaedt_function_handler +from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode from ansys.aedt.core.visualization.report.common import CommonReport @@ -50,11 +51,11 @@ def sub_design_id(self): int Number of the sub design ID. """ - return self._props["context"].get("Sub Design ID", None) + return self._legacy_props["context"].get("Sub Design ID", None) @sub_design_id.setter def sub_design_id(self, value): - self._props["context"]["Sub Design ID"] = value + self._legacy_props["context"]["Sub Design ID"] = value @property def time_start(self): @@ -65,11 +66,11 @@ def time_start(self): str Time start value. """ - return self._props["context"].get("time_start", None) + return self._legacy_props["context"].get("time_start", None) @time_start.setter def time_start(self, value): - self._props["context"]["time_start"] = value + self._legacy_props["context"]["time_start"] = value @property def time_stop(self): @@ -80,11 +81,11 @@ def time_stop(self): str Time stop value. """ - return self._props["context"].get("time_stop", None) + return self._legacy_props["context"].get("time_stop", None) @time_stop.setter def time_stop(self, value): - self._props["context"]["time_stop"] = value + self._legacy_props["context"]["time_stop"] = value @property def _did(self): @@ -110,15 +111,17 @@ def pulse_rise_time(self): float Pulse rise time. """ - return self._props["context"].get("pulse_rise_time", 0) if self.domain == "Time" else 0 + return self._legacy_props["context"].get("pulse_rise_time", 0) if self.domain == "Time" else 0 @pulse_rise_time.setter def pulse_rise_time(self, val): - self._props["context"]["pulse_rise_time"] = val + self._legacy_props["context"]["pulse_rise_time"] = val @property def time_windowing(self): - """Returns the TDR time windowing. Options are: + """Returns the TDR time windowing. + + Options are: * ``0`` : Rectangular * ``1`` : Bartlett * ``2`` : Blackman @@ -134,7 +137,7 @@ def time_windowing(self): int Time windowing. """ - _time_windowing = self._props["context"].get("time_windowing", 0) + _time_windowing = self._legacy_props["context"].get("time_windowing", 0) return _time_windowing if self.domain == "Time" and self.pulse_rise_time != 0 else 0 @time_windowing.setter @@ -151,9 +154,9 @@ def time_windowing(self, val): "lanzcos": 8, } if isinstance(val, int): - self._props["context"]["time_windowing"] = val + self._legacy_props["context"]["time_windowing"] = val elif isinstance(val, str) and val.lower in available_values: - self._props["context"]["time_windowing"] = available_values[val.lower()] + self._legacy_props["context"]["time_windowing"] = available_values[val.lower()] @property def _context(self): @@ -442,11 +445,11 @@ def time_start(self): str Time start. """ - return self._props["context"].get("time_start", "0s") + return self._legacy_props["context"].get("time_start", "0s") @time_start.setter def time_start(self, value): - self._props["context"]["time_start"] = value + self._legacy_props["context"]["time_start"] = value @property def time_stop(self): @@ -457,11 +460,11 @@ def time_stop(self): str Time stop. """ - return self._props["context"].get("time_stop", "100ns") + return self._legacy_props["context"].get("time_stop", "100ns") @time_stop.setter def time_stop(self, value): - self._props["context"]["time_stop"] = value + self._legacy_props["context"]["time_stop"] = value @property def window(self): @@ -472,11 +475,11 @@ def window(self): str Window. """ - return self._props["context"].get("window", "Rectangular") + return self._legacy_props["context"].get("window", "Rectangular") @window.setter def window(self, value): - self._props["context"]["window"] = value + self._legacy_props["context"]["window"] = value @property def kaiser_coeff(self): @@ -487,11 +490,11 @@ def kaiser_coeff(self): str Kaiser coefficient. """ - return self._props["context"].get("kaiser_coeff", 0) + return self._legacy_props["context"].get("kaiser_coeff", 0) @kaiser_coeff.setter def kaiser_coeff(self, value): - self._props["context"]["kaiser_coeff"] = value + self._legacy_props["context"]["kaiser_coeff"] = value @property def adjust_coherent_gain(self): @@ -502,11 +505,11 @@ def adjust_coherent_gain(self): bool ``True`` if coherent gain is enabled, ``False`` otherwise. """ - return self._props["context"].get("adjust_coherent_gain", False) + return self._legacy_props["context"].get("adjust_coherent_gain", False) @adjust_coherent_gain.setter def adjust_coherent_gain(self, value): - self._props["context"]["adjust_coherent_gain"] = value + self._legacy_props["context"]["adjust_coherent_gain"] = value @property def plot_continous_spectrum(self): @@ -517,11 +520,11 @@ def plot_continous_spectrum(self): bool ``True`` if continuous spectrum is enabled, ``False`` otherwise. """ - return self._props["context"].get("plot_continous_spectrum", False) + return self._legacy_props["context"].get("plot_continous_spectrum", False) @plot_continous_spectrum.setter def plot_continous_spectrum(self, value): - self._props["context"]["plot_continous_spectrum"] = value + self._legacy_props["context"]["plot_continous_spectrum"] = value @property def max_frequency(self): @@ -532,11 +535,11 @@ def max_frequency(self): str Maximum spectrum frequency. """ - return self._props["context"].get("max_frequency", "10GHz") + return self._legacy_props["context"].get("max_frequency", "10GHz") @max_frequency.setter def max_frequency(self, value): - self._props["context"]["max_frequency"] = value + self._legacy_props["context"]["max_frequency"] = value @property def _context(self): @@ -652,4 +655,7 @@ def create(self, name=None): ) self._post.plots.append(self) self._is_created = True + oo = self._post.oreportsetup.GetChildObject(self._legacy_props["plot_name"]) + if oo: + BinaryTreeNode.__init__(self, self.plot_name, oo, False) return True diff --git a/src/ansys/aedt/core/workflows/customize_automation_tab.py b/src/ansys/aedt/core/workflows/customize_automation_tab.py index 6673f862d6f..ed3c305b043 100644 --- a/src/ansys/aedt/core/workflows/customize_automation_tab.py +++ b/src/ansys/aedt/core/workflows/customize_automation_tab.py @@ -77,9 +77,7 @@ def add_automation_tab( ------- str Automation tab path. - """ - product = __tab_map(product) toolkit_name = name diff --git a/src/ansys/aedt/core/workflows/icepak/power_map_from_csv.py b/src/ansys/aedt/core/workflows/icepak/power_map_from_csv.py index c646b7563ae..6638dea5612 100644 --- a/src/ansys/aedt/core/workflows/icepak/power_map_from_csv.py +++ b/src/ansys/aedt/core/workflows/icepak/power_map_from_csv.py @@ -165,40 +165,32 @@ def main(extension_args): def create_powermaps_from_csv(ipk, csv_file): - """ - Creates powermap from an Icepak classic CSV file. + """Create powermap from an Icepak classic CSV file. Parameters ---------- - csv_file : str The file path to the CSV file to be processed. - """ - geometric_info, source_value_info, source_unit_info = extract_info(csv_file) create_powermaps_from_info(ipk, geometric_info, source_value_info, source_unit_info) def create_powermaps_from_info(ipk, geometric_info, source_value_info, source_unit_info): - """ - Creates power maps from geometric and source information. - - Parameters - ---------- + """Create power maps from geometric and source information. + Parameters + ---------- ipk: - geometric_info : list - A list of dictionaries, each containing: - - "name": The name of the geometric object. - - "vertices": A list of vertex coordinates. + geometric_info : list + A list of dictionaries, each containing: + - "name": The name of the geometric object. + - "vertices": A list of vertex coordinates. source_value_info: dict A dictionary mapping geometric object to its power value. source_unit_info: dict A dictionary mapping geometric object to its power unit. - """ - for info in geometric_info: name = info["name"] points = [] @@ -222,12 +214,10 @@ def create_powermaps_from_info(ipk, geometric_info, source_value_info, source_un def extract_info(csv_file): - """ - Extracts source and geometric information from an Icepak classic CSV file. + """Extracts source and geometric information from an Icepak classic CSV file. Parameters ---------- - csv_file (str): The file path to the CSV file to be processed. Returns diff --git a/src/ansys/aedt/core/workflows/installer/console_setup.py b/src/ansys/aedt/core/workflows/installer/console_setup.py index 27b12922b2c..4906d56aa62 100644 --- a/src/ansys/aedt/core/workflows/installer/console_setup.py +++ b/src/ansys/aedt/core/workflows/installer/console_setup.py @@ -23,9 +23,9 @@ # SOFTWARE. """ -Launches an interactive shell with an instance of HFSS +Launches an interactive shell with an instance of HFSS. -omitting the .py in the name of this file hides it from the Electronics Desktop Toolkit menu. +Omitting the .py in the name of this file hides it from the Electronics Desktop Toolkit menu. It should be hidden from this menu because the scripts in that menu are meant to be executed using IronPython cpython_console.py should be run instead of this script. diff --git a/src/ansys/aedt/core/workflows/installer/pyaedt_installer.py b/src/ansys/aedt/core/workflows/installer/pyaedt_installer.py index d93f4cbfc79..fe74af616ad 100644 --- a/src/ansys/aedt/core/workflows/installer/pyaedt_installer.py +++ b/src/ansys/aedt/core/workflows/installer/pyaedt_installer.py @@ -45,7 +45,6 @@ def add_pyaedt_to_aedt( personal_lib : str AEDT personal library folder. """ - logger = logging.getLogger("Global") if not personal_lib or not aedt_version: from ansys.aedt.core.generic.desktop_sessions import _desktop_sessions @@ -70,7 +69,6 @@ def add_pyaedt_to_aedt( def __add_pyaedt_tabs(personal_lib, aedt_version): """Add PyAEDT tabs in AEDT.""" - pyaedt_tabs = ["Console", "Jupyter", "Run_Script", "ExtensionManager"] extensions_catalog = read_toml(os.path.join(os.path.dirname(__file__), "extensions_catalog.toml")) diff --git a/src/ansys/aedt/core/workflows/misc.py b/src/ansys/aedt/core/workflows/misc.py index 943b41e0eba..20e7f5246f9 100644 --- a/src/ansys/aedt/core/workflows/misc.py +++ b/src/ansys/aedt/core/workflows/misc.py @@ -64,7 +64,6 @@ def is_student(): def get_arguments(args=None, description=""): # pragma: no cover """Get extension arguments.""" - output_args = {"is_batch": False, "is_test": False} if len(sys.argv) != 1: # pragma: no cover diff --git a/src/ansys/aedt/core/workflows/templates/pyaedt_utils.py b/src/ansys/aedt/core/workflows/templates/pyaedt_utils.py index 3e432da6c16..11c0923cd3f 100644 --- a/src/ansys/aedt/core/workflows/templates/pyaedt_utils.py +++ b/src/ansys/aedt/core/workflows/templates/pyaedt_utils.py @@ -40,12 +40,14 @@ def set_ansys_em_environment(oDesktop): + """Set the ANSYS_EM_ROOT environment variable.""" variable = "ANSYSEM_ROOT{}".format(oDesktop.GetVersion()[2:6].replace(".", "")) if variable not in os.environ: os.environ[variable] = oDesktop.GetExeDir() def sanitize_interpreter_path(interpreter_path, version): + """Sanitize the interpreter path.""" python_version = "3_10" if version > "231" else "3_7" if version > "231" and python_version not in interpreter_path: interpreter_path = interpreter_path.replace("3_7", "3_10") @@ -55,6 +57,7 @@ def sanitize_interpreter_path(interpreter_path, version): def check_file(file_path, oDesktop): + """Check if a file exists.""" if not os.path.isfile(file_path): show_error( '"{}" does not exist. Install PyAEDT using the Python script installer from the PyAEDT ' @@ -66,6 +69,7 @@ def check_file(file_path, oDesktop): def get_linux_terminal(): + """Get a Linux terminal.""" for terminal in ["x-terminal-emulator", "xterm", "gnome-terminal", "lxterminal", "mlterm"]: term = which(terminal) if term: @@ -92,12 +96,14 @@ def is_exe(fpath): def show_error(msg, oDesktop): + """Display an error message in the AEDT console and a dialog box.""" oDesktop.AddMessage("", "", 2, str(msg)) MessageBox.Show(str(msg), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error) sys.exit() def environment_variables(oDesktop): + """Set environment variables for the AEDT process.""" os.environ["PYAEDT_SCRIPT_PROCESS_ID"] = str(oDesktop.GetProcessID()) version = str(oDesktop.GetVersion()[:6]) os.environ["PYAEDT_SCRIPT_VERSION"] = version diff --git a/src/pyaedt/modules/Boundary.py b/src/pyaedt/modules/Boundary.py index ed3420aeced..8b137891791 100644 --- a/src/pyaedt/modules/Boundary.py +++ b/src/pyaedt/modules/Boundary.py @@ -1 +1 @@ -from ansys.aedt.core.modules.boundary import * + diff --git a/template.toml b/template.toml deleted file mode 100644 index 8a559d36a56..00000000000 --- a/template.toml +++ /dev/null @@ -1,21 +0,0 @@ -[aedb] -# Path to the aedb directory to convert -aedbpathin = "D:\\Data\\siverse\\uvia" -# Path to where the new aedb directory will be created -aedbpathout = "D:\\Data\\siverse\\uvia\\3DVia" -# Name of the aedn directory to convert -aedbfilein = "PCIE_RX_HFSS_5Ghz_20GHZ.aedb" -# Name of the new created aedb directory -aedbfileout = "PCIE_RX_HFSS_5Ghz_20GHZ_uvia_45deg.aedb" - -[aedt] -# Version of AEDT to be used "2023.2" for 2023R2, "2024.1" for 2024R1, ... -version = "2024.2" - -[setup] -# List of padstack for which instance via will be converted to micro-via -pdsk_list = ["v40h20-2", "v40h15-3", "v40h15", "v40h15-4"] -# Hole wall angle from start pad to end pad -laser_angle = "75" -# True convert only signal via, False convert signal and GND via -only_sig_via = "False" \ No newline at end of file diff --git a/tests/system/general/conftest.py b/tests/system/general/conftest.py index e874c99a1dd..f36c07515f2 100644 --- a/tests/system/general/conftest.py +++ b/tests/system/general/conftest.py @@ -168,8 +168,13 @@ def desktop(): d.odesktop.SetDesktopConfiguration("All") d.odesktop.SetSchematicEnvironment(0) yield d + pid = d.aedt_process_id d.release_desktop(True, True) time.sleep(1) + try: + os.kill(pid, 9) + except OSError: + pass @pytest.fixture(scope="module") diff --git a/tests/system/general/example_models/cad/DXF/dxf_r12.dxf b/tests/system/general/example_models/cad/DXF/dxf_r12.dxf new file mode 100644 index 00000000000..8baee76a07a --- /dev/null +++ b/tests/system/general/example_models/cad/DXF/dxf_r12.dxf @@ -0,0 +1,3374 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1009 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$EXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$LIMMIN + 10 +0.0 + 20 +0.0 + 9 +$LIMMAX + 10 +420.0 + 20 +297.0 + 9 +$ORTHOMODE + 70 +0 + 9 +$REGENMODE + 70 +1 + 9 +$FILLMODE + 70 +1 + 9 +$DRAGMODE + 70 +2 + 9 +$QTEXTMODE + 70 +0 + 9 +$MIRRTEXT + 70 +1 + 9 +$OSMODE + 70 +20583 + 9 +$LTSCALE + 40 +1.0 + 9 +$ATTMODE + 70 +1 + 9 +$TEXTSIZE + 40 +2.5 + 9 +$TRACEWID + 40 +1.0 + 9 +$TEXTSTYLE + 7 +OpenSans + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +ByLayer + 9 +$CECOLOR + 62 +256 + 9 +$DIMSCALE + 40 +1.0 + 9 +$DIMASZ + 40 +0.175 + 9 +$DIMEXO + 40 +0.125 + 9 +$DIMDLI + 40 +3.75 + 9 +$DIMRND + 40 +0.0 + 9 +$DIMDLE + 40 +0.0 + 9 +$DIMEXE + 40 +0.375 + 9 +$DIMTP + 40 +0.0 + 9 +$DIMTM + 40 +0.0 + 9 +$DIMTXT + 40 +0.25 + 9 +$DIMCEN + 40 +2.5 + 9 +$DIMTSZ + 40 +0.0 + 9 +$DIMTOL + 70 +0 + 9 +$DIMLIM + 70 +0 + 9 +$DIMTIH + 70 +0 + 9 +$DIMTOH + 70 +0 + 9 +$DIMSE1 + 70 +0 + 9 +$DIMSE2 + 70 +0 + 9 +$DIMTAD + 70 +1 + 9 +$DIMZIN + 70 +12 + 9 +$DIMBLK + 1 +ARCHTICK + 9 +$DIMASO + 70 +1 + 9 +$DIMSHO + 70 +1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 +0 + 9 +$DIMALTD + 70 +3 + 9 +$DIMALTF + 40 +0.03937007874 + 9 +$DIMLFAC + 40 +100.0 + 9 +$DIMTOFL + 70 +1 + 9 +$DIMTVP + 40 +0.0 + 9 +$DIMTIX + 70 +0 + 9 +$DIMSOXD + 70 +0 + 9 +$DIMSAH + 70 +0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +EZDXF + 9 +$DIMCLRD + 70 +0 + 9 +$DIMCLRE + 70 +0 + 9 +$DIMCLRT + 70 +0 + 9 +$DIMTFAC + 40 +0.5 + 9 +$DIMGAP + 40 +0.1 + 9 +$COORDS + 70 +1 + 9 +$ATTDIA + 70 +0 + 9 +$ATTREQ + 70 +1 + 9 +$HANDLING + 70 +1 + 9 +$LUNITS + 70 +2 + 9 +$LUPREC + 70 +4 + 9 +$SKETCHINC + 40 +1.0 + 9 +$FILLETRAD + 40 +10.0 + 9 +$AUNITS + 70 +0 + 9 +$AUPREC + 70 +2 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0.0 + 9 +$PELEVATION + 40 +0.0 + 9 +$THICKNESS + 40 +0.0 + 9 +$LIMCHECK + 70 +0 + 9 +$CHAMFERA + 40 +0.0 + 9 +$CHAMFERB + 40 +0.0 + 9 +$SKPOLY + 70 +0 + 9 +$TDCREATE + 40 +2460648.4490277776 + 9 +$TDUPDATE + 40 +2460648.4490277776 + 9 +$TDINDWG + 40 +0.0 + 9 +$TDUSRTIMER + 40 +0.0 + 9 +$USRTIMER + 70 +1 + 9 +$ANGBASE + 50 +0.0 + 9 +$ANGDIR + 70 +0 + 9 +$PDMODE + 70 +0 + 9 +$PDSIZE + 40 +0.0 + 9 +$PLINEWID + 40 +0.0 + 9 +$SPLFRAME + 70 +0 + 9 +$SPLINETYPE + 70 +6 + 9 +$SPLINESEGS + 70 +8 + 9 +$HANDSEED + 5 +9D + 9 +$SURFTAB1 + 70 +6 + 9 +$SURFTAB2 + 70 +6 + 9 +$SURFTYPE + 70 +6 + 9 +$SURFU + 70 +6 + 9 +$SURFV + 70 +6 + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$USERI1 + 70 +0 + 9 +$USERI2 + 70 +0 + 9 +$USERI3 + 70 +0 + 9 +$USERI4 + 70 +0 + 9 +$USERI5 + 70 +0 + 9 +$USERR1 + 40 +0.0 + 9 +$USERR2 + 40 +0.0 + 9 +$USERR3 + 40 +0.0 + 9 +$USERR4 + 40 +0.0 + 9 +$USERR5 + 40 +0.0 + 9 +$WORLDVIEW + 70 +1 + 9 +$SHADEDGE + 70 +3 + 9 +$SHADEDIF + 70 +70 + 9 +$TILEMODE + 70 +1 + 9 +$MAXACTVP + 70 +64 + 9 +$PLIMCHECK + 70 +0 + 9 +$PEXTMIN + 10 +1e+20 + 20 +1e+20 + 30 +1e+20 + 9 +$PEXTMAX + 10 +-1e+20 + 20 +-1e+20 + 30 +-1e+20 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$PLIMMAX + 10 +420.0 + 20 +297.0 + 9 +$UNITMODE + 70 +0 + 9 +$VISRETAIN + 70 +1 + 9 +$PLINEGEN + 70 +0 + 9 +$PSLTSCALE + 70 +1 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 70 +1 + 0 +VPORT + 5 +23 + 2 +*Active + 70 +0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +0.0 + 22 +0.0 + 13 +0.0 + 23 +0.0 + 14 +0.5 + 24 +0.5 + 15 +0.5 + 25 +0.5 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +0.0 + 27 +0.0 + 37 +0.0 + 40 +1000.0 + 41 +1.34 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 +0 + 72 +1000 + 73 +1 + 74 +3 + 75 +0 + 76 +0 + 77 +0 + 78 +0 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 70 +21 + 0 +LTYPE + 5 +24 + 2 +ByBlock + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +25 + 2 +ByLayer + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +26 + 2 +Continuous + 70 +0 + 3 + + 72 +65 + 73 +0 + 40 +0.0 + 0 +LTYPE + 5 +2D + 2 +CENTER + 70 +0 + 3 +Center ____ _ ____ _ ____ _ ____ _ ____ _ ____ + 72 +65 + 73 +4 + 40 +5.08 + 49 +3.175 + 49 +-0.635 + 49 +0.635 + 49 +-0.635 + 0 +LTYPE + 5 +2E + 2 +CENTERX2 + 70 +0 + 3 +Center (2x) ________ __ ________ __ ________ + 72 +65 + 73 +4 + 40 +8.89 + 49 +6.35 + 49 +-0.635 + 49 +1.27 + 49 +-0.635 + 0 +LTYPE + 5 +2F + 2 +CENTER2 + 70 +0 + 3 +Center (.5x) ____ _ ____ _ ____ _ ____ _ ____ + 72 +65 + 73 +4 + 40 +2.54 + 49 +1.587 + 49 +-0.318 + 49 +0.318 + 49 +-0.318 + 0 +LTYPE + 5 +30 + 2 +DASHED + 70 +0 + 3 +Dashed __ __ __ __ __ __ __ __ __ __ __ __ __ _ + 72 +65 + 73 +2 + 40 +1.524 + 49 +1.27 + 49 +-0.254 + 0 +LTYPE + 5 +31 + 2 +DASHEDX2 + 70 +0 + 3 +Dashed (2x) ____ ____ ____ ____ ____ ____ + 72 +65 + 73 +2 + 40 +3.048 + 49 +2.54 + 49 +-0.508 + 0 +LTYPE + 5 +32 + 2 +DASHED2 + 70 +0 + 3 +Dashed (.5x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ + 72 +65 + 73 +2 + 40 +0.762 + 49 +0.635 + 49 +-0.127 + 0 +LTYPE + 5 +33 + 2 +PHANTOM + 70 +0 + 3 +Phantom ______ __ __ ______ __ __ ______ + 72 +65 + 73 +6 + 40 +6.35 + 49 +3.175 + 49 +-0.635 + 49 +0.635 + 49 +-0.635 + 49 +0.635 + 49 +-0.635 + 0 +LTYPE + 5 +34 + 2 +PHANTOMX2 + 70 +0 + 3 +Phantom (2x)____________ ____ ____ ____________ + 72 +65 + 73 +6 + 40 +10.795 + 49 +6.35 + 49 +-0.635 + 49 +1.27 + 49 +-0.635 + 49 +1.27 + 49 +-0.635 + 0 +LTYPE + 5 +35 + 2 +PHANTOM2 + 70 +0 + 3 +Phantom (.5x) ___ _ _ ___ _ _ ___ _ _ ___ _ _ ___ + 72 +65 + 73 +6 + 40 +3.175 + 49 +1.587 + 49 +-0.318 + 49 +0.318 + 49 +-0.318 + 49 +0.318 + 49 +-0.318 + 0 +LTYPE + 5 +36 + 2 +DASHDOT + 70 +0 + 3 +Dash dot __ . __ . __ . __ . __ . __ . __ . __ + 72 +65 + 73 +4 + 40 +3.556 + 49 +2.54 + 49 +-0.508 + 49 +0.0 + 49 +-0.508 + 0 +LTYPE + 5 +37 + 2 +DASHDOTX2 + 70 +0 + 3 +Dash dot (2x) ____ . ____ . ____ . ____ + 72 +65 + 73 +4 + 40 +6.096 + 49 +5.08 + 49 +-0.508 + 49 +0.0 + 49 +-0.508 + 0 +LTYPE + 5 +38 + 2 +DASHDOT2 + 70 +0 + 3 +Dash dot (.5x) _ . _ . _ . _ . _ . _ . _ . _ + 72 +65 + 73 +4 + 40 +1.778 + 49 +1.27 + 49 +-0.254 + 49 +0.0 + 49 +-0.254 + 0 +LTYPE + 5 +39 + 2 +DOT + 70 +0 + 3 +Dot . . . . . . . . . . . . . . . . + 72 +65 + 73 +2 + 40 +0.508 + 49 +0.0 + 49 +-0.508 + 0 +LTYPE + 5 +3A + 2 +DOTX2 + 70 +0 + 3 +Dot (2x) . . . . . . . . + 72 +65 + 73 +2 + 40 +1.016 + 49 +0.0 + 49 +-1.016 + 0 +LTYPE + 5 +3B + 2 +DOT2 + 70 +0 + 3 +Dot (.5) . . . . . . . . . . . . . . . . . . . + 72 +65 + 73 +2 + 40 +0.254 + 49 +0.0 + 49 +-0.254 + 0 +LTYPE + 5 +3C + 2 +DIVIDE + 70 +0 + 3 +Divide __ . . __ . . __ . . __ . . __ . . __ + 72 +65 + 73 +6 + 40 +4.064 + 49 +2.54 + 49 +-0.508 + 49 +0.0 + 49 +-0.508 + 49 +0.0 + 49 +-0.508 + 0 +LTYPE + 5 +3D + 2 +DIVIDEX2 + 70 +0 + 3 +Divide (2x) ____ . . ____ . . ____ . . ____ + 72 +65 + 73 +6 + 40 +6.604 + 49 +5.08 + 49 +-0.508 + 49 +0.0 + 49 +-0.508 + 49 +0.0 + 49 +-0.508 + 0 +LTYPE + 5 +3E + 2 +DIVIDE2 + 70 +0 + 3 +Divide(.5x) _ . _ . _ . _ . _ . _ . _ . _ + 72 +65 + 73 +6 + 40 +2.032 + 49 +1.27 + 49 +-0.254 + 49 +0.0 + 49 +-0.254 + 49 +0.0 + 49 +-0.254 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 70 +3 + 0 +LAYER + 5 +27 + 2 +0 + 70 +0 + 62 +7 + 6 +Continuous + 0 +LAYER + 5 +8F + 2 +L1 + 70 +0 + 62 +7 + 6 +Continuous + 0 +LAYER + 5 +90 + 2 +L2 + 70 +0 + 62 +7 + 6 +Continuous + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 70 +26 + 0 +STYLE + 5 +29 + 2 +Standard + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +txt + 4 + + 0 +STYLE + 5 +3F + 2 +OpenSans-Light + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSans-Light.ttf + 4 + + 0 +STYLE + 5 +40 + 2 +OpenSans-Light-Italic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSans-LightItalic.ttf + 4 + + 0 +STYLE + 5 +41 + 2 +OpenSans + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSans-Regular.ttf + 4 + + 0 +STYLE + 5 +42 + 2 +OpenSans-Italic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSans-Italic.ttf + 4 + + 0 +STYLE + 5 +43 + 2 +OpenSans-SemiBold + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSans-SemiBold.ttf + 4 + + 0 +STYLE + 5 +44 + 2 +OpenSans-SemiBoldItalic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSans-SemiBoldItalic.ttf + 4 + + 0 +STYLE + 5 +45 + 2 +OpenSans-Bold + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSans-Bold.ttf + 4 + + 0 +STYLE + 5 +46 + 2 +OpenSans-BoldItalic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSans-BoldItalic.ttf + 4 + + 0 +STYLE + 5 +47 + 2 +OpenSans-ExtraBold + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSans-ExtraBold.ttf + 4 + + 0 +STYLE + 5 +48 + 2 +OpenSans-ExtraBoldItalic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSans-ExtraBoldItalic.ttf + 4 + + 0 +STYLE + 5 +49 + 2 +OpenSansCondensed-Bold + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSansCondensed-Bold.ttf + 4 + + 0 +STYLE + 5 +4A + 2 +OpenSansCondensed-Light + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSansCondensed-Light.ttf + 4 + + 0 +STYLE + 5 +4B + 2 +OpenSansCondensed-Italic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +OpenSansCondensed-LightItalic.ttf + 4 + + 0 +STYLE + 5 +4C + 2 +LiberationSans + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationSans-Regular.ttf + 4 + + 0 +STYLE + 5 +4D + 2 +LiberationSans-Bold + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationSans-Bold.ttf + 4 + + 0 +STYLE + 5 +4E + 2 +LiberationSans-BoldItalic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationSans-BoldItalic.ttf + 4 + + 0 +STYLE + 5 +4F + 2 +LiberationSans-Italic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationSans-Italic.ttf + 4 + + 0 +STYLE + 5 +50 + 2 +LiberationSerif + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationSerif-Regular.ttf + 4 + + 0 +STYLE + 5 +51 + 2 +LiberationSerif-Bold + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationSerif-Bold.ttf + 4 + + 0 +STYLE + 5 +52 + 2 +LiberationSerif-BoldItalic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationSerif-BoldItalic.ttf + 4 + + 0 +STYLE + 5 +53 + 2 +LiberationSerif-Italic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationSerif-Italic.ttf + 4 + + 0 +STYLE + 5 +54 + 2 +LiberationMono + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationMono-Regular.ttf + 4 + + 0 +STYLE + 5 +55 + 2 +LiberationMono-Bold + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationMono-Bold.ttf + 4 + + 0 +STYLE + 5 +56 + 2 +LiberationMono-BoldItalic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationMono-BoldItalic.ttf + 4 + + 0 +STYLE + 5 +57 + 2 +LiberationMono-Italic + 70 +0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 +0 + 42 +2.5 + 3 +LiberationMono-Italic.ttf + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 70 +3 + 0 +APPID + 5 +2A + 2 +ACAD + 70 +0 + 0 +APPID + 5 +9B + 2 +HATCHBACKGROUNDCOLOR + 70 +0 + 0 +APPID + 5 +9C + 2 +EZDXF + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 70 +12 + 0 +DIMSTYLE +105 +2B + 2 +Standard + 70 +0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +2.5 + 42 +0.625 + 43 +3.75 + 44 +1.25 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +2.5 +141 +2.5 +142 +0.0 +143 +0.03937007874 +144 +1.0 +145 +0.0 +146 +1.0 +147 +0.625 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +8 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +71 + 2 +EZDXF + 70 +0 + 3 + + 4 + + 5 +ARCHTICK + 6 + + 7 + + 40 +1.0 + 41 +0.175 + 42 +0.125 + 43 +3.75 + 44 +0.375 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.25 +141 +2.5 +142 +0.0 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.1 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +12 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +7D + 2 +EZ_M_100_H25_CM + 70 +0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +0.25 + 42 +0.125 + 43 +3.75 + 44 +0.375 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.25 +141 +2.5 +142 +0.125 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.1 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +12 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +7E + 2 +EZ_M_50_H25_CM + 70 +0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +0.125 + 42 +0.0625 + 43 +3.75 + 44 +0.1875 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.125 +141 +2.5 +142 +0.0625 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.05 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +12 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +7F + 2 +EZ_M_25_H25_CM + 70 +0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +0.0625 + 42 +0.03125 + 43 +3.75 + 44 +0.09375 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.0625 +141 +2.5 +142 +0.03125 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.025 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +12 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +80 + 2 +EZ_M_20_H25_CM + 70 +0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +0.05 + 42 +0.025 + 43 +3.75 + 44 +0.07500000000000001 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.05 +141 +2.5 +142 +0.025 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.020000000000000004 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +12 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +81 + 2 +EZ_M_10_H25_CM + 70 +0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +0.025 + 42 +0.0125 + 43 +3.75 + 44 +0.037500000000000006 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.025 +141 +2.5 +142 +0.0125 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.010000000000000002 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +12 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +82 + 2 +EZ_M_5_H25_CM + 70 +0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +0.0125 + 42 +0.00625 + 43 +3.75 + 44 +0.018750000000000003 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.0125 +141 +2.5 +142 +0.00625 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.005000000000000001 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +12 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +83 + 2 +EZ_M_1_H25_CM + 70 +0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +0.0025 + 42 +0.00125 + 43 +3.75 + 44 +0.00375 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.0025 +141 +2.5 +142 +0.00125 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.001 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +12 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +84 + 2 +EZ_RADIUS + 70 +0 + 3 + + 4 + + 5 +CLOSEDBLANK + 6 + + 7 + + 40 +1.0 + 41 +0.25 + 42 +0.125 + 43 +3.75 + 44 +0.375 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.25 +141 +0.0 +142 +0.0 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.1 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +12 +170 +0 +171 +3 +172 +0 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +8D + 2 +EZ_RADIUS_INSIDE + 70 +0 + 3 + + 4 + + 5 +CLOSEDBLANK + 6 + + 7 + + 40 +1.0 + 41 +0.25 + 42 +0.125 + 43 +3.75 + 44 +0.375 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.25 +141 +0.0 +142 +0.0 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.1 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +0 + 78 +12 +170 +0 +171 +3 +172 +0 +173 +0 +174 +1 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +DIMSTYLE +105 +8E + 2 +EZ_CURVED + 70 +0 + 3 + + 4 + + 5 + + 6 + + 7 + + 40 +1.0 + 41 +0.25 + 42 +0.125 + 43 +3.75 + 44 +0.375 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 +140 +0.25 +141 +2.5 +142 +0.0 +143 +0.03937007874 +144 +100.0 +145 +0.0 +146 +0.5 +147 +0.1 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +1 + 78 +12 +170 +0 +171 +3 +172 +1 +173 +0 +174 +0 +175 +0 +176 +0 +177 +0 +178 +0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +18 + 8 +0 + 2 +$Model_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +$Model_Space + 1 + +1001 +EZDXF +1000 +CREATED_BY_EZDXF +1000 +1.3.4 @ 2024-12-03T15:46:36.838546+00:00 +1000 +WRITTEN_BY_EZDXF +1000 +1.3.4 @ 2024-12-03T15:46:36.841710+00:00 + 0 +ENDBLK + 5 +19 + 8 +0 + 0 +BLOCK + 5 +1C + 8 +0 + 2 +$Paper_Space + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +$Paper_Space + 1 + + 0 +ENDBLK + 5 +1D + 8 +0 + 0 +BLOCK + 5 +73 + 8 +0 + 2 +_ARCHTICK + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +_ARCHTICK + 1 + + 0 +POLYLINE + 5 +75 + 8 +0 + 6 +BYBLOCK + 62 +0 + 66 +1 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 70 +0 + 40 +0.15 + 41 +0.15 + 0 +VERTEX + 5 +77 + 8 +0 + 6 +BYBLOCK + 10 +-0.5 + 20 +-0.5 + 30 +0.0 + 70 +0 + 0 +VERTEX + 5 +78 + 8 +0 + 6 +BYBLOCK + 10 +0.5 + 20 +0.5 + 30 +0.0 + 70 +0 + 0 +SEQEND + 5 +76 + 8 +0 + 0 +ENDBLK + 5 +74 + 8 +0 + 0 +BLOCK + 5 +7A + 8 +0 + 2 +_CLOSEDFILLED + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +_CLOSEDFILLED + 1 + + 0 +SOLID + 5 +7C + 8 +0 + 6 +BYBLOCK + 62 +0 + 10 +-1.0 + 20 +0.16439898372402537 + 30 +0.0 + 11 +0.0 + 21 +0.0 + 31 +0.0 + 12 +-1.0 + 22 +-0.16439898372402537 + 32 +0.0 + 13 +-1.0 + 23 +-0.16439898372402537 + 33 +0.0 + 0 +ENDBLK + 5 +7B + 8 +0 + 0 +BLOCK + 5 +86 + 8 +0 + 2 +_CLOSEDBLANK + 70 +0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +_CLOSEDBLANK + 1 + + 0 +POLYLINE + 5 +88 + 8 +0 + 6 +BYBLOCK + 62 +0 + 66 +1 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 70 +1 + 0 +VERTEX + 5 +8A + 8 +0 + 6 +BYBLOCK + 10 +-1.0 + 20 +0.16439898372402537 + 30 +0.0 + 70 +0 + 0 +VERTEX + 5 +8B + 8 +0 + 6 +BYBLOCK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 70 +0 + 0 +VERTEX + 5 +8C + 8 +0 + 6 +BYBLOCK + 10 +-1.0 + 20 +-0.16439898372402537 + 30 +0.0 + 70 +0 + 0 +SEQEND + 5 +89 + 8 +0 + 0 +ENDBLK + 5 +87 + 8 +0 + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +CIRCLE + 5 +91 + 8 +L1 + 10 +1.0 + 20 +1.0 + 30 +0.0 + 40 +1.0 + 0 +LINE + 5 +92 + 8 +L1 + 10 +-1.0 + 20 +-1.0 + 30 +0.0 + 11 +1.0 + 21 +-1.0 + 31 +0.0 + 0 +LINE + 5 +93 + 8 +L1 + 10 +1.0 + 20 +-1.0 + 30 +0.0 + 11 +1.0 + 21 +1.0 + 31 +0.0 + 0 +LINE + 5 +94 + 8 +L1 + 10 +1.0 + 20 +1.0 + 30 +0.0 + 11 +-1.0 + 21 +1.0 + 31 +0.0 + 0 +LINE + 5 +95 + 8 +L1 + 10 +-1.0 + 20 +1.0 + 30 +0.0 + 11 +-1.0 + 21 +-1.0 + 31 +0.0 + 0 +CIRCLE + 5 +96 + 8 +L2 + 10 +1.0 + 20 +1.0 + 30 +0.0 + 40 +1.0 + 0 +LINE + 5 +97 + 8 +L2 + 10 +-1.0 + 20 +-1.0 + 30 +0.0 + 11 +1.0 + 21 +-1.0 + 31 +0.0 + 0 +LINE + 5 +98 + 8 +L2 + 10 +1.0 + 20 +-1.0 + 30 +0.0 + 11 +1.0 + 21 +1.0 + 31 +0.0 + 0 +LINE + 5 +99 + 8 +L2 + 10 +1.0 + 20 +1.0 + 30 +0.0 + 11 +-1.0 + 21 +1.0 + 31 +0.0 + 0 +LINE + 5 +9A + 8 +L2 + 10 +-1.0 + 20 +1.0 + 30 +0.0 + 11 +-1.0 + 21 +-1.0 + 31 +0.0 + 0 +ENDSEC + 0 +EOF diff --git a/tests/system/general/test_01_3dlayout_edb.py b/tests/system/general/test_01_3dlayout_edb.py index a3bcb6113c3..4d9f0b98e61 100644 --- a/tests/system/general/test_01_3dlayout_edb.py +++ b/tests/system/general/test_01_3dlayout_edb.py @@ -385,10 +385,11 @@ def test_19_dcir(self): context="RL", ) assert isinstance(self.dcir_example_project.get_dcir_element_data_current_source("SIwaveDCIR1"), pd.DataFrame) - p_layers = self.dcir_example_project.post.compute_power_by_layer() - p_nets = self.dcir_example_project.post.compute_power_by_nets(nets=["5V", "GND"]) - assert p_nets - assert p_layers + assert self.dcir_example_project.post.compute_power_by_layer() + assert self.dcir_example_project.post.compute_power_by_layer(layers=["1_Top"]) + assert self.dcir_example_project.post.compute_power_by_net() + assert self.dcir_example_project.post.compute_power_by_net(nets=["5V", "GND"]) + assert self.dcir_example_project.post.compute_power_by_layer(solution="SIwaveDCIR1") def test_20_change_options(self): assert self.aedtapp.change_options() diff --git a/tests/system/general/test_04_SBR.py b/tests/system/general/test_04_SBR.py index 0a361c2a651..34b61ce98bb 100644 --- a/tests/system/general/test_04_SBR.py +++ b/tests/system/general/test_04_SBR.py @@ -161,7 +161,7 @@ def test_02_add_antennas(self): array.native_properties["Array Length In Wavelength"] = "10" assert array.update() - assert array.object_properties.props["Name"] == array.name + assert array.properties["Name"] == array.name native_components = len(self.aedtapp.native_component_names) array.name = "new_name" diff --git a/tests/system/general/test_07_Object3D.py b/tests/system/general/test_07_Object3D.py index a113e9f5c6c..3cd578af1f7 100644 --- a/tests/system/general/test_07_Object3D.py +++ b/tests/system/general/test_07_Object3D.py @@ -632,7 +632,7 @@ def test_26_unclassified_object(self): vArg2 = ["NAME:IntersectParameters", "KeepOriginals:=", False] self.aedtapp.modeler.oeditor.Intersect(vArg1, vArg2) - assert box1 in self.aedtapp.modeler.unclassified_objects + assert box1.name in self.aedtapp.modeler.unclassified_names def test_26a_delete_unclassified_object(self): unclassified = self.aedtapp.modeler.unclassified_objects @@ -662,17 +662,17 @@ def test_27_get_object_history_properties(self): box_clone_history = box_clone.history() assert box_history.node == "box_history" assert box_history.command == "CreateBox" - assert box_history.props["Command"] == "CreateBox" + assert box_history.properties["Command"] == "CreateBox" assert box_history.children == {} assert box_clone_history.node == "box_history1" assert box_clone_history.command == box_history.command - assert box_clone_history.props["Command"] == box_history.props["Command"] - assert box_clone_history.props["Position/X"] == box_history.props["Position/X"] - assert box_clone_history.props["Position/Y"] == box_history.props["Position/Y"] - assert box_clone_history.props["Position/Z"] == box_history.props["Position/Z"] - assert box_clone_history.props["XSize"] == box_history.props["XSize"] - assert box_clone_history.props["YSize"] == box_history.props["YSize"] - assert box_clone_history.props["ZSize"] == box_history.props["ZSize"] + assert box_clone_history.properties["Command"] == box_history.properties["Command"] + assert box_clone_history.properties["Position/X"] == box_history.properties["Position/X"] + assert box_clone_history.properties["Position/Y"] == box_history.properties["Position/Y"] + assert box_clone_history.properties["Position/Z"] == box_history.properties["Position/Z"] + assert box_clone_history.properties["XSize"] == box_history.properties["XSize"] + assert box_clone_history.properties["YSize"] == box_history.properties["YSize"] + assert box_clone_history.properties["ZSize"] == box_history.properties["ZSize"] assert len(box_clone_history.children) == 3 assert "Subtract:1" in box_clone_history.children.keys() assert "Rotate:1" in box_clone_history.children.keys() @@ -686,10 +686,10 @@ def test_27_get_object_history_properties(self): subtract = self.aedtapp.modeler["box_history1"].history().children["Subtract:1"].children assert len(subtract) == 1 for key in subtract.keys(): - assert subtract[key].command == subtract[key].props["Command"] + assert subtract[key].command == subtract[key].properties["Command"] subtract_child = subtract[key].children for child in subtract_child.keys(): - assert subtract_child[child].command == subtract_child[child].props["Command"] + assert subtract_child[child].command == subtract_child[child].properties["Command"] assert len(subtract_child[child].children) == 0 def test_27b_object_suppress(self): @@ -703,29 +703,29 @@ def test_27c_object_jsonalize(self): def test_28_set_object_history_properties(self): history = self.aedtapp.modeler["box_history1"].history() - assert history.props["Position/X"] == "10meter" - history.props["Position/X"] = "15meter" - assert history.props["Position/X"] == "15meter" - assert history.props["ZSize"] == "15meter" - history.props["ZSize"] = "10meter" - assert history.props["ZSize"] == "10meter" + assert history.properties["Position/X"] == "10meter" + history.properties["Position/X"] = "15meter" + assert history.properties["Position/X"] == "15meter" + assert history.properties["ZSize"] == "15meter" + history.properties["ZSize"] = "10meter" + assert history.properties["ZSize"] == "10meter" subtract = history.children["Subtract:1"].children for key in subtract.keys(): subtract_child = subtract[key].children for child in subtract_child.keys(): if "CreateCylinder" in child: - assert subtract_child[child].props["Center Position/X"] == "10meter" - subtract_child[child].props["Center Position/X"] = "15meter" - assert subtract_child[child].props["Center Position/X"] == "15meter" - assert subtract_child[child].props["Axis"] == "Y" - subtract_child[child].props["Axis"] = "Z" - assert subtract_child[child].props["Axis"] == "Z" - assert subtract_child[child].props["Radius"] == "5meter" - subtract_child[child].props["Radius"] = "8meter" - assert subtract_child[child].props["Radius"] == "8meter" - assert subtract_child[child].props["Height"] == "20meter" - subtract_child[child].props["Height"] = "24meter" - assert subtract_child[child].props["Height"] == "24meter" + assert subtract_child[child].properties["Center Position/X"] == "10meter" + subtract_child[child].properties["Center Position/X"] = "15meter" + assert subtract_child[child].properties["Center Position/X"] == "15meter" + assert subtract_child[child].properties["Axis"] == "Y" + subtract_child[child].properties["Axis"] = "Z" + assert subtract_child[child].properties["Axis"] == "Z" + assert subtract_child[child].properties["Radius"] == "5meter" + subtract_child[child].properties["Radius"] = "8meter" + assert subtract_child[child].properties["Radius"] == "8meter" + assert subtract_child[child].properties["Height"] == "20meter" + subtract_child[child].properties["Height"] = "24meter" + assert subtract_child[child].properties["Height"] == "24meter" def test_29_test_nets(self): self.aedtapp.insert_design("nets") @@ -768,3 +768,11 @@ def test_20_simplify_objects(self): ) assert not self.aedtapp.modeler.simplify_objects(assignment=None) assert not self.aedtapp.modeler.simplify_objects(assignment=1) + + def test_30_rescale(self): + self.aedtapp.modeler.model_units = "meter" + box1 = self.aedtapp.modeler.create_box([0, 0, 0], [5, 10, 2], material="Copper") + assert box1.mass == 893300.0 + self.aedtapp.modeler.rescale_model = True + self.aedtapp.modeler.model_units = "mm" + assert round(box1.mass, 5) == 0.00089 diff --git a/tests/system/general/test_12_1_PostProcessing.py b/tests/system/general/test_12_1_PostProcessing.py index 642f5302bfb..7a65c67fbbc 100644 --- a/tests/system/general/test_12_1_PostProcessing.py +++ b/tests/system/general/test_12_1_PostProcessing.py @@ -418,7 +418,7 @@ def test_09e_add_traces_to_report(self): assert not new_report.add_trace_to_report(traces, setup, variations) def test_09f_update_trace_name(self): - report = [plot for plot in self.aedtapp.post.plots if plot.plot_name == "add_traces_test"][0] + report = self.aedtapp.create_scattering("add_traces_test_2") old_trace_name = report.traces[0].name assert old_trace_name in report.traces[0].aedt_name new_name = "update_trace_name_test" diff --git a/tests/system/general/test_20_HFSS.py b/tests/system/general/test_20_HFSS.py index d5996eb1c00..9df72ef1b5c 100644 --- a/tests/system/general/test_20_HFSS.py +++ b/tests/system/general/test_20_HFSS.py @@ -139,7 +139,7 @@ def test_04_assign_coating(self): coat = self.aedtapp.assign_coating([id, "inner_1", 41], **args) coat.name = "Coating1inner" assert coat.update() - assert coat.object_properties + assert coat.properties material = coat.props.get("Material", "") assert material == "aluminum" assert not self.aedtapp.assign_coating(["insulator2", 45]) @@ -164,7 +164,7 @@ def test_05_create_wave_port_from_sheets(self): terminals_rename=False, ) - assert port.object_properties + assert port.properties assert port.name == "sheet1_Port" assert port.name in [i.name for i in self.aedtapp.boundaries] assert port.props["RenormalizeAllTerminals"] is False @@ -1532,13 +1532,24 @@ def test_63_set_phase_center_per_port(self): assert not self.aedtapp.set_phase_center_per_port(["Global"]) assert not self.aedtapp.set_phase_center_per_port("Global") - @pytest.mark.skipif(config["NonGraphical"], reason="Test fails on build machine") - def test_64_import_dxf(self): - self.aedtapp.insert_design("dxf") - dxf_file = os.path.join(TESTS_GENERAL_PATH, "example_models", "cad", "DXF", "dxf2.dxf") + @pytest.mark.skipif( + config["NonGraphical"] and config["desktopVersion"] < "2024.2", + reason="Not working in non graphical before version 2024.2", + ) + @pytest.mark.parametrize( + ("dxf_file", "object_count", "self_stitch_tolerance"), + ( + (os.path.join(TESTS_GENERAL_PATH, "example_models", "cad", "DXF", "dxf2.dxf"), 1, 0.0), + (os.path.join(TESTS_GENERAL_PATH, "example_models", "cad", "DXF", "dxf_r12.dxf"), 4, -1), + ), + ) + def test_64_import_dxf(self, dxf_file: str, object_count: int, self_stitch_tolerance: float): + design_name = self.aedtapp.insert_design("test_64_import_dxf") + self.aedtapp.set_active_design(design_name) dxf_layers = self.aedtapp.get_dxf_layers(dxf_file) assert isinstance(dxf_layers, list) - assert self.aedtapp.import_dxf(dxf_file, dxf_layers) + assert self.aedtapp.import_dxf(dxf_file, dxf_layers, self_stitch_tolerance=self_stitch_tolerance) + assert len(self.aedtapp.modeler.objects) == object_count def test_65_component_array(self, add_app): hfss_array = add_app(project_name=component_array, subfolder=test_subfolder) diff --git a/tests/system/general/test_28_Maxwell3D.py b/tests/system/general/test_28_Maxwell3D.py index 0ba29f6b845..9a61868f337 100644 --- a/tests/system/general/test_28_Maxwell3D.py +++ b/tests/system/general/test_28_Maxwell3D.py @@ -1131,17 +1131,19 @@ def test_59_assign_floating(self): def test_60_resistive_sheet(self): self.aedtapp.insert_design("ResistiveSheet") self.aedtapp.solution_type = SOLUTIONS.Maxwell3d.EddyCurrent - my_box = self.aedtapp.modeler.create_box( - origin=[0, 0, 0], sizes=[0.4, -1, 0.8], name="my_box", material="copper" + self.aedtapp.modeler.create_box(origin=[0, 0, 0], sizes=[0.4, -1, 0.8], name="my_box", material="copper") + my_rectangle = self.aedtapp.modeler.create_rectangle( + orientation=1, origin=[0, 0, 0.8], sizes=[-1, 0.4], name="my_rect" ) - resistive_face = my_box.faces[0] - bound = self.aedtapp.assign_resistive_sheet(assignment=resistive_face, resistance="3ohm") + + # From 2025.1, this boundary can only be assigned to Sheets that touch conductor Solids. + bound = self.aedtapp.assign_resistive_sheet(assignment=my_rectangle.faces[0], resistance="3ohm") assert bound - assert bound.props["Faces"][0] == resistive_face.id + assert bound.props["Faces"][0] == my_rectangle.faces[0].id assert bound.props["Resistance"] == "3ohm" self.aedtapp.solution_type = SOLUTIONS.Maxwell3d.Magnetostatic - bound = self.aedtapp.assign_resistive_sheet(assignment=resistive_face, non_linear=True) + bound = self.aedtapp.assign_resistive_sheet(assignment=my_rectangle.name, non_linear=True) assert bound.props["Nonlinear"] - assert bound.props["Faces"][0] == resistive_face.id + assert bound.props["Objects"][0] == my_rectangle.name self.aedtapp.solution_type = SOLUTIONS.Maxwell3d.ACConduction - assert not self.aedtapp.assign_resistive_sheet(assignment=resistive_face, resistance="3ohm") + assert not self.aedtapp.assign_resistive_sheet(assignment=my_rectangle, resistance="3ohm") diff --git a/tests/system/general/test_30_Q2D.py b/tests/system/general/test_30_Q2D.py index 38c57f29c4a..75f959bb85c 100644 --- a/tests/system/general/test_30_Q2D.py +++ b/tests/system/general/test_30_Q2D.py @@ -103,7 +103,7 @@ def test_09_auto_assign(self): o = self.aedtapp.create_rectangle([6, 6], [5, 3], name="Rectangle1", material="Copper") o = self.aedtapp.create_rectangle([0, 0], [5, 3], name="Rectangle2", material="Copper") assert self.aedtapp.auto_assign_conductors() - assert self.aedtapp.boundaries[0].object_properties + assert self.aedtapp.boundaries[0].properties assert len(self.aedtapp.boundaries) == 2 def test_10_toggle_conductor(self): @@ -221,7 +221,7 @@ def test_14_export_matrix_data(self, add_app): def test_15_export_equivalent_circuit(self, add_app): q2d = add_app(application=Q2d, project_name=self.test_matrix, just_open=True) q2d.insert_reduced_matrix(q2d.MATRIXOPERATIONS.Float, "Circle2", "Test4") - assert q2d.matrices[4].name == "Test4" + assert q2d.matrices[-1].name == "Test4" assert len(q2d.setups[0].sweeps[0].frequencies) > 0 assert q2d.setups[0].sweeps[0].basis_frequencies == [] assert q2d.export_equivalent_circuit(os.path.join(self.local_scratch.path, "test_export_circuit.cir")) diff --git a/tests/system/general/test_41_3dlayout_modeler.py b/tests/system/general/test_41_3dlayout_modeler.py index 8dbf26bd4c6..be902599c7d 100644 --- a/tests/system/general/test_41_3dlayout_modeler.py +++ b/tests/system/general/test_41_3dlayout_modeler.py @@ -336,7 +336,7 @@ def test_13a_create_edge_port(self): assert self.aedtapp.create_edge_port("line1", 3, False) assert len(self.aedtapp.excitations) > 0 time_domain = os.path.join(TESTS_GENERAL_PATH, "example_models", test_subfolder, "Sinusoidal.csv") - assert self.aedtapp.boundaries[0].object_properties.props["Magnitude"] == "1V" + assert self.aedtapp.boundaries[0].properties["Magnitude"] == "1V" assert self.aedtapp.edit_source_from_file( source=port_wave.name, input_file=time_domain, @@ -345,8 +345,9 @@ def test_13a_create_edge_port(self): y_scale=1e-3, data_format="Voltage", ) - assert self.aedtapp.boundaries[0].object_properties.props["Magnitude"] != "1V" - self.aedtapp.boundaries[0].object_properties.props["Boundary Type"] = "PEC" + assert self.aedtapp.boundaries[0].properties["Magnitude"] != "1V" + self.aedtapp.boundaries[0].properties["Boundary Type"] = "PEC" + assert self.aedtapp.boundaries[0].properties["Boundary Type"] == "PEC" assert list(self.aedtapp.oboundary.GetAllBoundariesList())[0] == self.aedtapp.boundaries[0].name def test_14a_create_coaxial_port(self): @@ -607,9 +608,9 @@ def test_27_create_pin_port(self): assert port.name == "PinPort1" port.props["Magnitude"] = "2V" assert port.props["Magnitude"] == "2V" - assert port.object_properties.props["Magnitude"] == "2V" - port.object_properties.props["Magnitude"] = "5V" - assert port.object_properties.props["Magnitude"] == "5V" + assert port.properties["Magnitude"] == "2V" + port.properties["Magnitude"] = "5V" + assert port.properties["Magnitude"] == "5V" def test_28_create_scattering(self): assert self.aedtapp.create_scattering() diff --git a/tests/system/general/test_98_Icepak.py b/tests/system/general/test_98_Icepak.py index e1184c056e1..8cb40a365ce 100644 --- a/tests/system/general/test_98_Icepak.py +++ b/tests/system/general/test_98_Icepak.py @@ -26,10 +26,10 @@ from ansys.aedt.core import Icepak from ansys.aedt.core.generic.settings import settings -from ansys.aedt.core.modules.boundary import NativeComponentObject -from ansys.aedt.core.modules.boundary import NetworkObject -from ansys.aedt.core.modules.boundary import PCBSettingsDeviceParts -from ansys.aedt.core.modules.boundary import PCBSettingsPackageParts +from ansys.aedt.core.modules.boundary.circuit_boundary import NetworkObject +from ansys.aedt.core.modules.boundary.layout_boundary import NativeComponentObject +from ansys.aedt.core.modules.boundary.layout_boundary import PCBSettingsDeviceParts +from ansys.aedt.core.modules.boundary.layout_boundary import PCBSettingsPackageParts from ansys.aedt.core.modules.mesh_icepak import MeshRegion from ansys.aedt.core.modules.setup_templates import SetupKeys from ansys.aedt.core.visualization.post.field_data import FolderPlotSettings @@ -1228,10 +1228,12 @@ def test_55_native_components_history(self): self.aedtapp.modeler.user_defined_components[fan.name].duplicate_along_line([4, 5, 6]) fan_1_history = self.aedtapp.modeler.user_defined_components[fan.name].history() assert fan_1_history.command == "Move" - assert all(fan_1_history.props["Move Vector/" + i] == j + "mm" for i, j in [("X", "1"), ("Y", "2"), ("Z", "3")]) + assert all( + fan_1_history.properties["Move Vector/" + i] == j + "mm" for i, j in [("X", "1"), ("Y", "2"), ("Z", "3")] + ) assert fan_1_history.children["DuplicateAlongLine:1"].command == "DuplicateAlongLine" assert all( - fan_1_history.children["DuplicateAlongLine:1"].props["Vector/" + i] == j + "mm" + fan_1_history.children["DuplicateAlongLine:1"].properties["Vector/" + i] == j + "mm" for i, j in [("X", "4"), ("Y", "5"), ("Z", "6")] ) diff --git a/tests/system/general/test_utils.py b/tests/system/general/test_utils.py new file mode 100644 index 00000000000..46d74604fa9 --- /dev/null +++ b/tests/system/general/test_utils.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Test utility functions of PyAEDT. +""" + +import os +from pathlib import Path + +from ansys.aedt.core.generic.settings import Settings + + +def test_settings_load_default_yaml(): + """Test loading the default YAML file in docs/source/Resources.""" + default_settings = Settings() + + local_settings = Settings() + project_root = Path(__file__).resolve().parents[3] + pyaedt_settings_path = project_root / "doc" / "source" / "Resources" / "pyaedt_settings.yaml" + local_settings.load_yaml_configuration(str(pyaedt_settings_path)) + + # Compare except for keys where it does not make sense, e.g. log filename, time_tick + default_settings_attributes = default_settings.__dict__ + del default_settings_attributes["_Settings__global_log_file_name"] + del default_settings_attributes["_Settings__time_tick"] + local_settings_attributes = local_settings.__dict__ + del local_settings_attributes["_Settings__global_log_file_name"] + del local_settings_attributes["_Settings__time_tick"] + if os.name != "posix": + del local_settings_attributes["_Settings__aedt_environment_variables"]["ANS_NODEPCHECK"] + + assert default_settings_attributes == local_settings_attributes + + +def test_settings_writte_default_yaml(tmp_path): + default_settings = Settings() + path = str("pyaedt_settings.yaml") + default_settings.writte_yaml_configuration(path) + + assert os.path.exists(path) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 46c3d7ff6ab..49bc5638e03 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -26,11 +26,16 @@ """ import logging +import os from unittest.mock import MagicMock from unittest.mock import PropertyMock from unittest.mock import patch from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.generic.settings import ALLOWED_AEDT_ENV_VAR_SETTINGS +from ansys.aedt.core.generic.settings import ALLOWED_GENERAL_SETTINGS +from ansys.aedt.core.generic.settings import ALLOWED_LOG_SETTINGS +from ansys.aedt.core.generic.settings import ALLOWED_LSF_SETTINGS from ansys.aedt.core.generic.settings import Settings from ansys.aedt.core.generic.settings import settings import pytest @@ -38,6 +43,7 @@ SETTINGS_RELEASE_ON_EXCEPTION = settings.release_on_exception SETTINGS_ENABLE_ERROR_HANDLER = settings.enable_error_handler ERROR_MESSAGE = "Dummy message." +TOML_DATA = {"key_0": "dummy", "key_1": 12, "key_2": [1, 2], "key_3": {"key_4": 42}} @pytest.fixture(scope="module", autouse=True) @@ -144,7 +150,7 @@ def test_settings_load_yaml(tmp_path): assert default_settings.desktop_launch_timeout == 12 -def test_settings_load_yaml_with_non_allowed_key(tmp_path): +def test_settings_load_yaml_with_non_allowed_attribute_key(tmp_path): """Test loading a configuration file with invalid key.""" default_settings = Settings() @@ -152,6 +158,10 @@ def test_settings_load_yaml_with_non_allowed_key(tmp_path): yaml_path = tmp_path / "pyaedt_settings.yaml" yaml_path.write_text( """ + # Valid key + log: + enable_debug_edb_logger: false + # Invalid key general: dummy: 12.0 """ @@ -163,3 +173,96 @@ def test_settings_load_yaml_with_non_allowed_key(tmp_path): with pytest.raises(KeyError) as excinfo: default_settings.load_yaml_configuration(str(yaml_path), raise_on_wrong_key=True) assert str(excinfo) in "Key 'dummy' is not part of the allowed keys" + + +def test_settings_load_yaml_with_non_allowed_env_variable_key(tmp_path): + """Test loading a configuration file with invalid key.""" + default_settings = Settings() + + # Create temporary YAML configuration file + yaml_path = tmp_path / "pyaedt_settings.yaml" + yaml_path.write_text( + """ + # Valid key + log: + enable_debug_edb_logger: false + # Invalid key + aedt_env_var: + AEDT_DUMMY: 12.0 + """ + ) + + default_settings.load_yaml_configuration(str(yaml_path), raise_on_wrong_key=False) + assert "AEDT_DUMMY" in default_settings.aedt_environment_variables + + with pytest.raises(KeyError) as excinfo: + default_settings.load_yaml_configuration(str(yaml_path), raise_on_wrong_key=True) + assert str(excinfo) in "An environment variable key is not part of the allowed keys." + + +def test_settings_attributes(): + """Test accessing settings attributes.""" + default_settings = Settings() + + for attr in ALLOWED_LOG_SETTINGS + ALLOWED_GENERAL_SETTINGS + ALLOWED_LSF_SETTINGS: + _ = getattr(default_settings, attr) + for attr in ALLOWED_AEDT_ENV_VAR_SETTINGS: + if os.name != "posix" and attr == "ANS_NODEPCHECK": + continue + _ = getattr(default_settings, "aedt_environment_variables")[attr] + + +def test_settings_check_allowed_attributes(): + """Test that every non python setting is an allowed settings.""" + default_settings = Settings() + # All allowed attributes + allowed_attrs_expected = ( + ALLOWED_LOG_SETTINGS + ALLOWED_GENERAL_SETTINGS + ALLOWED_LSF_SETTINGS + ["aedt_environment_variables"] + ) + # Check attributes that are not related to Python objects (otherwise they are not 'allowed') + attrs_ignored = ["formatter", "logger", "remote_rpc_session", "time_tick"] + settings_attrs = ( + key.split("_Settings__")[-1] for key in default_settings.__dict__.keys() if key.startswith("_Settings__") + ) + settings_attrs = filter(lambda attr: attr not in attrs_ignored, settings_attrs) + + assert sorted(allowed_attrs_expected) == sorted(settings_attrs) + + +def test_settings_check_allowed_env_variables(): + """Test that known environment variables are allowed.""" + default_settings = Settings() + env_variables = default_settings.aedt_environment_variables.keys() + allowed_env_var_expected = ALLOWED_AEDT_ENV_VAR_SETTINGS + if os.name != "posix": + allowed_env_var_expected.remove("ANS_NODEPCHECK") + + assert sorted(allowed_env_var_expected) == sorted(env_variables) + + +def test_read_toml(tmp_path): + """Test loading a TOML file.""" + from ansys.aedt.core.generic.general_methods import read_toml + + file_path = tmp_path / "dummy.toml" + content = """ + key_0 = 'dummy' + key_1 = 12 + key_2 = [1,2] + [key_3] + key_4 = 42 + """ + file_path.write_text(content, encoding="utf-8") + + res = read_toml(file_path) + assert TOML_DATA == res + + +def test_write_toml(tmp_path): + """Test writing a TOML file.""" + from ansys.aedt.core.generic.general_methods import _create_toml_file + + file_path = tmp_path / "dummy.toml" + _create_toml_file(TOML_DATA, file_path) + + assert file_path.exists()