diff --git a/MANIFEST.in b/MANIFEST.in index 7451fb54375..c4086f8b731 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,4 @@ global-include ansys.aedt.core *.txt *.md *.toml *.json *.png *.xml *.areg *.joblib *.acf *.m *.ipynb *.py_build +include src/ansys/aedt/core/syslib/**/*.so +include src/ansys/aedt/core/syslib/**/*.pyd +include src/ansys/aedt/core/syslib/**/*.license \ No newline at end of file diff --git a/doc/changelog.d/6236.miscellaneous.md b/doc/changelog.d/6236.miscellaneous.md new file mode 100644 index 00000000000..09b6a2a7bee --- /dev/null +++ b/doc/changelog.d/6236.miscellaneous.md @@ -0,0 +1 @@ +Nastran import refactoring \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 1646c9aa799..3f9256b616d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,6 +131,12 @@ examples = [ "scikit-rf>=0.30.0,<1.8", ] +[tool.setuptools] +include-package-data = true + +[tool.setuptools.package-data] +"ansys.aedt.core.syslib" = ["**/*.so", "**/*.pyd", "**/*.license"] + [tool.setuptools.dynamic] version = {attr = "ansys.aedt.core.__version__"} diff --git a/src/ansys/aedt/core/extensions/project/import_nastran.py b/src/ansys/aedt/core/extensions/project/import_nastran.py index fc61ea24614..768ac0b6911 100644 --- a/src/ansys/aedt/core/extensions/project/import_nastran.py +++ b/src/ansys/aedt/core/extensions/project/import_nastran.py @@ -36,7 +36,7 @@ from ansys.aedt.core.extensions.misc import get_port from ansys.aedt.core.extensions.misc import get_process_id from ansys.aedt.core.extensions.misc import is_student -from ansys.aedt.core.visualization.advanced.misc import nastran_to_stl +from ansys.aedt.core.syslib.nastran_import import nastran_to_stl @dataclass @@ -45,6 +45,7 @@ class ExtensionData: lightweight: bool = False planar: bool = True file_path: str = "" + remove_multiple_connections: bool = False PORT = get_port() @@ -52,7 +53,13 @@ class ExtensionData: AEDT_PROCESS_ID = get_process_id() IS_STUDENT = is_student() EXTENSION_TITLE = "Import Nastran or STL file" -EXTENSION_DEFAULT_ARGUMENTS = {"decimate": 0.0, "lightweight": False, "planar": True, "file_path": ""} +EXTENSION_DEFAULT_ARGUMENTS = { + "decimate": 0.0, + "lightweight": False, + "planar": True, + "file_path": "", + "remove_multiple_connections": False, +} result = None @@ -78,6 +85,7 @@ def browseFiles(): title="Select a Nastran or stl File", filetypes=(("Nastran", "*.nas"), ("STL", "*.stl"), ("all files", "*.*")), ) + text.delete("1.0", tkinter.END) text.insert(tkinter.END, filename) b1 = ttk.Button(root, text="...", width=10, command=browseFiles, style="PyAEDT.TButton", name="browse_button") @@ -103,6 +111,17 @@ def browseFiles(): check3 = ttk.Checkbutton(root, variable=planar, style="PyAEDT.TCheckbutton", name="check_planar_merge") check3.grid(row=3, column=1, pady=10, padx=5) + label = ttk.Label(root, text="Remove multiple connections:", style="PyAEDT.TLabel") + label.grid(row=4, column=0, pady=10) + remove_multiple_connections = tkinter.IntVar(root, value=0) + check4 = ttk.Checkbutton( + root, + variable=remove_multiple_connections, + style="PyAEDT.TCheckbutton", + name="check_remove_multiple_connections", + ) + check4.grid(row=4, column=1, pady=10, padx=5) + def toggle_theme(): if root.theme == "light": set_dark_theme() @@ -145,6 +164,7 @@ def callback(): lightweight=True if light.get() == 1 else False, planar=True if planar.get() == 1 else False, file_path=text.get("1.0", tkinter.END).strip(), + remove_multiple_connections=True if remove_multiple_connections.get() == 1 else False, ) root.destroy() @@ -160,9 +180,9 @@ def preview(): if file_path_ui.endswith(".nas"): nastran_to_stl(file_path_ui, decimation=decimate_ui, preview=True) else: - from ansys.aedt.core.visualization.advanced.misc import simplify_stl + from ansys.aedt.core.visualization.advanced.misc import simplify_and_preview_stl - simplify_stl(file_path_ui, decimation=decimate_ui, preview=True) + simplify_and_preview_stl(file_path_ui, decimation=decimate_ui, preview=True) b2 = ttk.Button(root, text="Preview", width=40, command=preview, style="PyAEDT.TButton", name="preview_button") b2.grid(row=5, column=0, pady=10, padx=10) @@ -178,6 +198,7 @@ def main(extension_args): lightweight = extension_args["lightweight"] decimate = extension_args["decimate"] planar = extension_args["planar"] + remove_multiple_connections = extension_args["remove_multiple_connections"] if file_path.is_file(): app = ansys.aedt.core.Desktop( @@ -198,12 +219,16 @@ def main(extension_args): if file_path.suffix == ".nas": aedtapp.modeler.import_nastran( - str(file_path), import_as_light_weight=lightweight, decimation=decimate, enable_planar_merge=str(planar) + str(file_path), + import_as_light_weight=lightweight, + decimation=decimate, + enable_planar_merge=str(planar), + remove_multiple_connections=remove_multiple_connections, ) else: - from ansys.aedt.core.visualization.advanced.misc import simplify_stl + from ansys.aedt.core.visualization.advanced.misc import simplify_and_preview_stl - outfile = simplify_stl(str(file_path), decimation=decimate) + outfile = simplify_and_preview_stl(str(file_path), decimation=decimate) aedtapp.modeler.import_3d_cad( outfile, healing=False, create_lightweigth_part=lightweight, merge_planar_faces=planar ) diff --git a/src/ansys/aedt/core/generic/general_methods.py b/src/ansys/aedt/core/generic/general_methods.py index dbe8eb36d6c..4a19c9649aa 100644 --- a/src/ansys/aedt/core/generic/general_methods.py +++ b/src/ansys/aedt/core/generic/general_methods.py @@ -31,6 +31,7 @@ import itertools import logging import os +import platform import re import sys import time @@ -47,8 +48,11 @@ from ansys.aedt.core.internal.errors import GrpcApiError from ansys.aedt.core.internal.errors import MethodNotSupportedError -is_linux = os.name == "posix" -is_windows = not is_linux +system = platform.system() +is_linux = system == "Linux" +is_windows = system == "Windows" +is_macos = system == "Darwin" + inside_desktop = True if "4.0.30319.42000" in sys.version else False inclusion_list = [ diff --git a/src/ansys/aedt/core/generic/settings.py b/src/ansys/aedt/core/generic/settings.py index 597ca3f3d20..a7b37f63943 100644 --- a/src/ansys/aedt/core/generic/settings.py +++ b/src/ansys/aedt/core/generic/settings.py @@ -38,6 +38,7 @@ import logging import os +from pathlib import Path import time from typing import Any from typing import List @@ -45,6 +46,8 @@ from typing import Union import uuid +from ansys.aedt.core import pyaedt_path + is_linux = os.name == "posix" # Settings allowed to be updated using a YAML configuration file. @@ -102,6 +105,8 @@ "remote_rpc_session_temp_folder", "block_figure_plot", "skip_license_check", + "pyd_libraries_path", + "pyd_libraries_user_path", ] ALLOWED_AEDT_ENV_VAR_SETTINGS = [ "ANSYSEM_FEATURE_F335896_MECHANICAL_STRUCTURAL_SOLN_TYPE_ENABLE", @@ -172,7 +177,7 @@ def __init__(self): self.__lsf_queue: Optional[str] = None self.__custom_lsf_command: Optional[str] = None # Settings related to environment variables that are set before launching a new AEDT session - # This includes those that enable the beta features ! + # This includes those that enable the beta features! self.__aedt_environment_variables: dict[str, str] = { "ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE": "1", "ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE": "1", @@ -214,6 +219,8 @@ def __init__(self): self.__time_tick = time.time() self.__pyaedt_server_path = "" self.__block_figure_plot = False + self.__pyd_libraries_path: Path = Path(pyaedt_path) / "syslib" + self.__pyd_libraries_user_path: Optional[str] = None # Load local settings if YAML configuration file exists. pyaedt_settings_path = os.environ.get("PYAEDT_LOCAL_SETTINGS_PATH", "") @@ -785,6 +792,36 @@ def skip_license_check(self): def skip_license_check(self, value): self.__skip_license_check = value + @property + def pyd_libraries_path(self): + if self.__pyd_libraries_user_path is not None: + # If the user path is set, return it + return Path(self.__pyd_libraries_user_path) + return Path(self.__pyd_libraries_path) + + @property + def pyd_libraries_user_path(self): + # Get the user path for PyAEDT libraries. + if self.__pyd_libraries_user_path is not None: + return Path(self.__pyd_libraries_user_path) + return None + + @pyd_libraries_user_path.setter + def pyd_libraries_user_path(self, val): + if val is None: + # If the user path is None, set it to None + self.__pyd_libraries_user_path = None + else: + lib_path = Path(str(val)) + if not lib_path.exists(): + # If the user path does not exist, return None + raise ValueError("The user path for PyAEDT libraries does not exist. Please set a valid path.") + else: + # If the user path exists, set it as a Path object + self.__pyd_libraries_user_path = lib_path + + # yaml setting file IO methods + def load_yaml_configuration(self, path: str, raise_on_wrong_key: bool = False): """Update default settings from a YAML configuration file.""" import yaml @@ -825,15 +862,22 @@ def filter_settings_with_raise(settings: dict, allowed_keys: List[str]): 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): + def write_yaml_configuration(self, path: str): """Write the current settings into a YAML configuration file.""" import yaml 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["log"] = { + key: str(value) if isinstance(value := getattr(self, key), Path) else value for key in ALLOWED_LOG_SETTINGS + } + data["lsf"] = { + key: str(value) if isinstance(value := getattr(self, key), Path) else value 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} + data["general"] = { + key: str(value) if isinstance(value := getattr(self, key), Path) else value + for key in ALLOWED_GENERAL_SETTINGS + } with open(path, "w") as file: yaml.safe_dump(data, file, sort_keys=False) diff --git a/src/ansys/aedt/core/modeler/modeler_3d.py b/src/ansys/aedt/core/modeler/modeler_3d.py index f68627cbd57..5e75260b6e0 100644 --- a/src/ansys/aedt/core/modeler/modeler_3d.py +++ b/src/ansys/aedt/core/modeler/modeler_3d.py @@ -36,7 +36,7 @@ from ansys.aedt.core.internal.errors import GrpcApiError from ansys.aedt.core.modeler.cad.primitives_3d import Primitives3D from ansys.aedt.core.modeler.geometry_operators import GeometryOperators -from ansys.aedt.core.visualization.advanced.misc import nastran_to_stl +from ansys.aedt.core.syslib.nastran_import import nastran_to_stl class Modeler3D(Primitives3D): @@ -1029,6 +1029,7 @@ def import_nastran( save_only_stl=False, preview=False, merge_angle=1e-3, + remove_multiple_connections=False, ): """Import Nastran file into 3D Modeler by converting the faces to stl and reading it. @@ -1060,6 +1061,8 @@ def import_nastran( Whether to preview the model in pyvista or skip it. merge_angle : float, optional Angle in radians for which faces will be considered planar. Default is ``1e-3``. + remove_multiple_connections : bool, optional + Whether to remove multiple connections in the mesh. Default is ``False``. Returns ------- @@ -1078,6 +1081,7 @@ def import_nastran( output_folder=self._app.working_directory, enable_planar_merge=enable_planar_merge, preview=preview, + remove_multiple_connections=remove_multiple_connections, ) if save_only_stl: return output_stls, nas_to_dict diff --git a/src/ansys/aedt/core/syslib/__init__.py b/src/ansys/aedt/core/syslib/__init__.py new file mode 100644 index 00000000000..df6761d7662 --- /dev/null +++ b/src/ansys/aedt/core/syslib/__init__.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2025 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 importlib.util +from pathlib import Path +import sys + +from ansys.aedt.core.generic.general_methods import is_windows + + +def load_native_module(module_name: str, base_dir: Path | str) -> object: + """ + Dynamically load a compiled native Python module (.pyd or .so) from a base directory. + Automatically choose the correct file extension based on the platform and Python version. + + The module name must end with '_lib' or '_dynload'. + + Example: + module_name='nastran_import_lib' on Python 3.11 → loads 'nastran_import_lib_311.so' on Linux + + Parameters + ---------- + module_name: str + The name of the module to load (e.g. 'nastran_import'). + + base_dir: Path, str + Path to the directory containing the compiled module. + + Returns + ------- + The loaded module object. + + Raises + ------ + FileNotFoundError: If the compiled module file is not found. + ImportError: If the module cannot be imported. + """ + base_path = Path(base_dir) + + # Validate the module name + if not module_name.endswith(("_lib", "_dynload")): + raise ValueError("Module name must end with '_lib' or '_dynload'.") + + # Get Python version as suffix (e.g., '311' for Python 3.11) + version_suffix = f"{sys.version_info.major}{sys.version_info.minor}" + + if version_suffix not in ["310", "311", "312", "313"]: + raise ValueError( + f"Unsupported Python version for {module_name}: {sys.version_info.major}.{sys.version_info.minor} \n" + f"Supported versions are 3.10, 3.11, 3.12, and 3.13." + ) + + # Determine the platform-specific extension + ext = ".pyd" if is_windows else ".so" + + # Construct the full module path + file_name = f"{module_name}_{version_suffix}{ext}" + module_path = base_path / file_name + + if not module_path.is_file(): + raise FileNotFoundError(f"Module file not found at: {module_path}") + + spec = importlib.util.spec_from_file_location(module_name, str(module_path)) + if spec is None or spec.loader is None: + raise ImportError(f"Failed to create import spec for {module_path}") + + mod = importlib.util.module_from_spec(spec) + sys.modules[module_name] = mod + spec.loader.exec_module(mod) + + return mod diff --git a/src/ansys/aedt/core/syslib/nastran_import/__init__.py b/src/ansys/aedt/core/syslib/nastran_import/__init__.py new file mode 100644 index 00000000000..8b760ced1bb --- /dev/null +++ b/src/ansys/aedt/core/syslib/nastran_import/__init__.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2025 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.file_utils 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.modeler.geometry_operators import GeometryOperators +from ansys.aedt.core.syslib import load_native_module +from ansys.aedt.core.visualization.advanced.misc import preview_pyvista + +__all__ = ["nastran_to_stl"] + +# Dynamically load the compiled nastran_import module from a directory +_nastran_import_lib = load_native_module("nastran_import_lib", settings.pyd_libraries_path / "nastran_import") + +# Initialize the helpers for the nastran_import_lib module +_nastran_import_lib.initialize_helpers(open_file, GeometryOperators, preview_pyvista) + + +@pyaedt_function_handler() +def nastran_to_stl(*args, **kwargs): + """Convert a Nastran file to STL format. + + Parameters + ---------- + input_file : str + Path to the input Nastran file. + output_folder : str, optional + Path to the output folder where the STL files will be saved. + If ``None``, the directory of the input file is used. + decimation : int, optional + The decimation factor for mesh simplification. + Default is ``0`` (no decimation). + enable_planar_merge : str, optional + Whether to enable or not planar merge. It can be ``"True"``, ``"False"`` or ``"Auto"``. + ``"Auto"`` will disable the planar merge if stl contains more than 50000 triangles. + preview : bool, optional + Whether to generate a preview of the STL files using PyVista. + Default is ``False``. + remove_multiple_connections : bool, optional + Whether to remove multiple connections in the mesh. + Default is ``False``. + + Returns + ------- + tuple + A tuple containing: + - A list of paths to the generated STL files. + - A dictionary representing the parsed Nastran data. + - A boolean indicating whether planar merging was enabled. + + """ + return _nastran_import_lib.nastran_to_stl(*args, **kwargs) diff --git a/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_310.pyd b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_310.pyd new file mode 100644 index 00000000000..8f8be6d6c08 Binary files /dev/null and b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_310.pyd differ diff --git a/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_310.pyd.license b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_310.pyd.license new file mode 100644 index 00000000000..e9617127c93 --- /dev/null +++ b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_310.pyd.license @@ -0,0 +1,23 @@ +-*- coding: utf-8 -*- + +Copyright (C) 2021 - 2025 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/syslib/nastran_import/nastran_import_lib_310.so b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_310.so new file mode 100644 index 00000000000..e63ad839970 Binary files /dev/null and b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_310.so differ diff --git a/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_311.pyd b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_311.pyd new file mode 100644 index 00000000000..2ea07656e66 Binary files /dev/null and b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_311.pyd differ diff --git a/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_311.pyd.license b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_311.pyd.license new file mode 100644 index 00000000000..e9617127c93 --- /dev/null +++ b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_311.pyd.license @@ -0,0 +1,23 @@ +-*- coding: utf-8 -*- + +Copyright (C) 2021 - 2025 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/syslib/nastran_import/nastran_import_lib_311.so b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_311.so new file mode 100644 index 00000000000..54526d9478b Binary files /dev/null and b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_311.so differ diff --git a/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_312.pyd b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_312.pyd new file mode 100644 index 00000000000..64e59798cb6 Binary files /dev/null and b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_312.pyd differ diff --git a/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_312.pyd.license b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_312.pyd.license new file mode 100644 index 00000000000..e9617127c93 --- /dev/null +++ b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_312.pyd.license @@ -0,0 +1,23 @@ +-*- coding: utf-8 -*- + +Copyright (C) 2021 - 2025 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/syslib/nastran_import/nastran_import_lib_312.so b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_312.so new file mode 100644 index 00000000000..c27657568c4 Binary files /dev/null and b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_312.so differ diff --git a/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_313.pyd b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_313.pyd new file mode 100644 index 00000000000..f567003b24c Binary files /dev/null and b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_313.pyd differ diff --git a/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_313.pyd.license b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_313.pyd.license new file mode 100644 index 00000000000..e9617127c93 --- /dev/null +++ b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_313.pyd.license @@ -0,0 +1,23 @@ +-*- coding: utf-8 -*- + +Copyright (C) 2021 - 2025 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/syslib/nastran_import/nastran_import_lib_313.so b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_313.so new file mode 100644 index 00000000000..4a87f359df7 Binary files /dev/null and b/src/ansys/aedt/core/syslib/nastran_import/nastran_import_lib_313.so differ diff --git a/src/ansys/aedt/core/visualization/advanced/misc.py b/src/ansys/aedt/core/visualization/advanced/misc.py index 9d535f8564a..702a33c11ff 100644 --- a/src/ansys/aedt/core/visualization/advanced/misc.py +++ b/src/ansys/aedt/core/visualization/advanced/misc.py @@ -279,451 +279,81 @@ def parse_rdat_file(file_path): return report_dict -@pyaedt_function_handler() -def _parse_nastran(file_path): - """Nastran file parser.""" - - if not Path(file_path).exists(): - raise FileNotFoundError(f"File ({file_path}) not found") - - logger = logging.getLogger("Global") - nas_to_dict = {"Points": [], "PointsId": {}, "Assemblies": {}} - includes = [] - - def parse_lines(input_lines, input_pid=0, in_assembly="Main"): - if in_assembly not in nas_to_dict["Assemblies"]: - nas_to_dict["Assemblies"][in_assembly] = {"Triangles": {}, "Solids": {}, "Lines": {}, "Shells": {}} - - def get_point(ll, start, length): - n = ll[start : start + length].strip() - if "-" in n[1:] and "e" not in n[1:].lower(): - n = n[0] + n[1:].replace("-", "e-") - return n - - line_header = "" - for lk in range(len(input_lines)): - line = input_lines[lk] - line_type = line[:8].strip() - obj_type = "Triangles" - if line.startswith("$"): - line_header = line[1:] - continue - elif line.startswith("*"): - continue - elif line_type in ["GRID", "GRID*"]: - num_points = 3 - obj_type = "Grid" - elif line_type in [ - "CTRIA3", - "CTRIA3*", - ]: - num_points = 3 - obj_type = "Triangles" - elif line_type in ["CROD", "CBEAM", "CBAR", "CROD*", "CBEAM*", "CBAR*"]: - num_points = 2 - obj_type = "Lines" - elif line_type in [ - "CQUAD4", - "CQUAD4*", - ]: - num_points = 4 - obj_type = "Triangles" - elif line_type in ["CTETRA", "CTETRA*"]: - num_points = 4 - obj_type = "Solids" - elif line_type in ["CPYRA", "CPYRAM", "CPYRA*", "CPYRAM*"]: - num_points = 5 - obj_type = "Solids" - elif line_type in ["CHEXA", "CHEXA*"]: - num_points = 8 - obj_type = "Solids" - elif line_type in ["PSHELL", "PSHELL*"]: - num_points = 1 - obj_type = "Shells" - elif line_type in ["MPC", "MPC*"]: - num_points = 16 - else: - continue - - points = [] - start_pointer = 8 - word_length = 8 - if line_type.endswith("*"): - word_length = 16 - grid_id = int(line[start_pointer : start_pointer + word_length]) - pp = 0 - start_pointer = start_pointer + word_length - object_id = line[start_pointer : start_pointer + word_length] - if obj_type == "Shells": - nas_to_dict["Assemblies"][in_assembly][obj_type][grid_id] = [] - elif obj_type != "Grid": - object_id = int(object_id) - if object_id not in nas_to_dict["Assemblies"][in_assembly][obj_type]: - nas_to_dict["Assemblies"][in_assembly][obj_type][object_id] = [] - while pp < num_points: - start_pointer = start_pointer + word_length - if start_pointer >= 72: - lk += 1 - line = input_lines[lk] - start_pointer = 8 - points.append(get_point(line, start_pointer, word_length)) - pp += 1 - - if line_type in ["GRID", "GRID*"]: - nas_to_dict["PointsId"][grid_id] = input_pid - nas_to_dict["Points"].append([float(i) for i in points]) - input_pid += 1 - elif line_type in [ - "CTRIA3", - "CTRIA3*", - ]: - tri = [nas_to_dict["PointsId"][int(i)] for i in points] - nas_to_dict["Assemblies"][in_assembly]["Triangles"][object_id].append(tri) - elif line_type in ["CROD", "CBEAM", "CBAR", "CROD*", "CBEAM*", "CBAR*"]: - tri = [nas_to_dict["PointsId"][int(i)] for i in points] - nas_to_dict["Assemblies"][in_assembly]["Lines"][object_id].append(tri) - elif line_type in ["CQUAD4", "CQUAD4*"]: - tri = [ - nas_to_dict["PointsId"][int(points[0])], - nas_to_dict["PointsId"][int(points[1])], - nas_to_dict["PointsId"][int(points[2])], - ] - nas_to_dict["Assemblies"][in_assembly]["Triangles"][object_id].append(tri) - tri = [ - nas_to_dict["PointsId"][int(points[0])], - nas_to_dict["PointsId"][int(points[2])], - nas_to_dict["PointsId"][int(points[3])], - ] - nas_to_dict["Assemblies"][in_assembly]["Triangles"][object_id].append(tri) - elif line_type in ["PSHELL", "PSHELL*"]: - nas_to_dict["Assemblies"][in_assembly]["Shells"][grid_id] = [str(object_id.strip()), points[0]] - elif line_type in ["MPC", "MPC*"]: - if ( - line_header - and f"Port_{line_header.replace(',', '_')}_{object_id}" - not in nas_to_dict["Assemblies"][in_assembly]["Triangles"] - ): - name = f"Port_{line_header.replace(',', '_')}_{object_id}" - else: - name = f"Port_{object_id}" - tri = [ - nas_to_dict["PointsId"][int(points[2])], - nas_to_dict["PointsId"][int(points[7])], - nas_to_dict["PointsId"][int(points[10])], - ] - nas_to_dict["Assemblies"][in_assembly]["Triangles"][name] = [tri] - tri = [ - nas_to_dict["PointsId"][int(points[2])], - nas_to_dict["PointsId"][int(points[7])], - nas_to_dict["PointsId"][int(points[15])], - ] - nas_to_dict["Assemblies"][in_assembly]["Triangles"][name].append(tri) - tri = [ - nas_to_dict["PointsId"][int(points[2])], - nas_to_dict["PointsId"][int(points[10])], - nas_to_dict["PointsId"][int(points[15])], - ] - nas_to_dict["Assemblies"][in_assembly]["Triangles"][name].append(tri) - elif line_type in ["CHEXA", "CHEXA*"]: - - def add_hexa_tria(p1, p2, p3, hexa_obj): - tri = [ - nas_to_dict["PointsId"][int(points[p1])], - nas_to_dict["PointsId"][int(points[p2])], - nas_to_dict["PointsId"][int(points[p3])], - ] - nas_to_dict["Assemblies"][in_assembly]["Solids"][hexa_obj].append(tri) - - add_hexa_tria(0, 1, 2, object_id) - add_hexa_tria(0, 2, 3, object_id) - add_hexa_tria(0, 1, 4, object_id) - add_hexa_tria(1, 4, 5, object_id) - add_hexa_tria(1, 2, 6, object_id) - add_hexa_tria(1, 6, 5, object_id) - add_hexa_tria(4, 7, 6, object_id) - add_hexa_tria(4, 6, 5, object_id) - add_hexa_tria(0, 3, 7, object_id) - add_hexa_tria(0, 7, 4, object_id) - add_hexa_tria(3, 2, 7, object_id) - add_hexa_tria(2, 7, 6, object_id) - else: - from itertools import combinations - - for k in list(combinations(points, 3)): - tri = [ - nas_to_dict["PointsId"][int(k[0])], - nas_to_dict["PointsId"][int(k[1])], - nas_to_dict["PointsId"][int(k[2])], - ] - tri.sort() - tri = tuple(tri) - nas_to_dict["Assemblies"][in_assembly]["Solids"][object_id].append(tri) - - return input_pid - - logger.info("Loading file") - with open_file(file_path, "r") as f: - lines = f.read().splitlines() - for line in lines: - if line.startswith("INCLUDE"): - includes.append(line.split(" ")[1].replace("'", "").strip()) - pid = parse_lines(lines) - for include in includes: - with open_file(os.path.join(os.path.dirname(file_path), include), "r") as f: - lines = f.read().splitlines() - name = include.split(".")[0] - pid = parse_lines(lines, pid, name) - logger.info("File loaded") - for assembly in list(nas_to_dict["Assemblies"].keys())[::]: - if ( - nas_to_dict["Assemblies"][assembly]["Triangles"] - == nas_to_dict["Assemblies"][assembly]["Solids"] - == nas_to_dict["Assemblies"][assembly]["Lines"] - == {} - ): - del nas_to_dict["Assemblies"][assembly] - for _, assembly_object in nas_to_dict["Assemblies"].items(): - - def domino(segments): - def check_new_connection(s, polylines, exclude_index=-1): - s = s[:] - polylines = [poly[:] for poly in polylines] - attached = False - p_index = None - for i, p in enumerate(polylines): - if i == exclude_index: - continue - if s[0] == p[-1]: - p.extend(s[1:]) # the new segment attaches to the end - attached = True - elif s[-1] == p[0]: - for item in reversed(s[:-1]): - p.insert(0, item) # the new segment attaches to the beginning - attached = True - elif s[0] == p[0]: - for item in s[1:]: - p.insert(0, item) # the new segment attaches to the beginning in reverse order - attached = True - elif s[-1] == p[-1]: - p.extend(s[-2::-1]) # the new segment attaches to the end in reverse order - attached = True - if attached: - p_index = i - break - if not attached: - polylines.append(s) - return polylines, attached, p_index - - polylines = [] - for segment in segments: - polylines, attached_flag, attached_p_index = check_new_connection(segment, polylines) - if attached_flag: - other_polylines = polylines[:attached_p_index] + polylines[attached_p_index + 1 :] - polylines, _, _ = check_new_connection( - polylines[attached_p_index], other_polylines, attached_p_index - ) - - return polylines - - def remove_self_intersections(polylines): - polylines = [poly[:] for poly in polylines] - new_polylines = [] - for p in polylines: - if p[0] in p[1:]: - new_polylines.append([p[0], p[1]]) - p.pop(0) - if p[-1] in p[:-1]: - new_polylines.append([p[-2], p[-1]]) - p.pop(-1) - new_polylines.append(p) - return new_polylines - - if assembly_object["Lines"]: - for lname, lines in assembly_object["Lines"].items(): - new_lines = lines[::] - new_lines = remove_self_intersections(domino(new_lines)) - assembly_object["Lines"][lname] = new_lines - - return nas_to_dict - - @pyaedt_function_handler() @graphics_required -def _write_stl(nas_to_dict, decimation, working_directory, enable_planar_merge=True): - """Write stl file.""" +def preview_pyvista(dict_in, decimation=0, output_stls=None): import pyvista as pv - logger = logging.getLogger("Global") - - def _write_solid_stl(triangle, pp): - try: - # points = [nas_to_dict["Points"][id] for id in triangle] - points = [pp[i] for i in triangle[::-1]] - except KeyError: # pragma: no cover - return - fc = points[0] - v1 = points[1] - v2 = points[2] - cv1 = GeometryOperators.v_points(fc, v2) - cv2 = GeometryOperators.v_points(fc, v1) - try: - n = GeometryOperators.v_cross(cv1, cv2) - - normal = GeometryOperators.normalize_vector(n) - f.write(f" facet normal {normal[0]} {normal[1]} {normal[2]}\n") - f.write(" outer loop\n") - f.write(f" vertex {points[0][0]} {points[0][1]} {points[0][2]}\n") - f.write(f" vertex {points[1][0]} {points[1][1]} {points[1][2]}\n") - f.write(f" vertex {points[2][0]} {points[2][1]} {points[2][2]}\n") - f.write(" endloop\n") - f.write(" endfacet\n") - except Exception: # pragma: no cover - logger.debug("Failed to normalize vector.") - - logger.info("Creating STL file with detected faces") - enable_stl_merge = False if enable_planar_merge == "False" or enable_planar_merge is False else True - - def decimate(points_in, faces_in): - fin = [[3] + list(i) for i in faces_in] - mesh = pv.PolyData(points_in, faces=fin) - new_mesh = mesh.decimate_pro(decimation, preserve_topology=True, boundary_vertex_deletion=False) - points_out = list(new_mesh.points) - faces_out = [i[1:] for i in new_mesh.faces.reshape(-1, 4) if i[0] == 3] - return points_out, faces_out - - output_stls = [] - for assembly_name, assembly in nas_to_dict["Assemblies"].items(): - output_stl = os.path.join(working_directory, assembly_name + ".stl") - f = open(output_stl, "w") - for tri_id, triangles in assembly["Triangles"].items(): + if decimation > 0: + pl = pv.Plotter(shape=(1, 2)) + else: # pragma: no cover + pl = pv.Plotter() + dargs = dict(show_edges=True) + colors = [] + color_by_assembly = True + if len(dict_in["Assemblies"]) == 1: + color_by_assembly = False + + css4_colors = list(CSS4_COLORS.values()) + k = 0 + p_out = dict_in["Points"][::] + for assembly in dict_in["Assemblies"].values(): + if color_by_assembly: + h = css4_colors[k].lstrip("#") + colors.append(tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))) + k += 1 + + for triangles in assembly["Triangles"].values(): + if not triangles: + continue tri_out = triangles - p_out = nas_to_dict["Points"][::] - if decimation > 0 and len(triangles) > 20: - p_out, tri_out = decimate(nas_to_dict["Points"], tri_out) - f.write(f"solid Sheet_{tri_id}\n") - if enable_planar_merge == "Auto" and len(tri_out) > 50000: - enable_stl_merge = False # pragma: no cover - for triangle in tri_out: - _write_solid_stl(triangle, p_out) - f.write("endsolid\n") - for solidid, solid_triangles in assembly["Solids"].items(): - f.write(f"solid Solid_{solidid}\n") + fin = [[3] + list(i) for i in tri_out] + if not color_by_assembly: + h = css4_colors[k].lstrip("#") + colors.append(tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))) + k = k + 1 if k < len(css4_colors) - 1 else 0 + pl.add_mesh(pv.PolyData(p_out, faces=fin), color=colors[-1], **dargs) + + for triangles in assembly["Solids"].values(): + if not triangles: + continue import pandas as pd - df = pd.Series(solid_triangles) + df = pd.Series(triangles) tri_out = df.drop_duplicates(keep=False).to_list() - p_out = nas_to_dict["Points"][::] - if decimation > 0 and len(solid_triangles) > 20: - p_out, tri_out = decimate(nas_to_dict["Points"], tri_out) - if enable_planar_merge == "Auto" and len(tri_out) > 50000: - enable_stl_merge = False # pragma: no cover - for triangle in tri_out: - _write_solid_stl(triangle, p_out) - f.write("endsolid\n") - f.close() - output_stls.append(output_stl) - logger.info("STL file created") - return output_stls, enable_stl_merge - + p_out = dict_in["Points"][::] + fin = [[3] + list(i) for i in tri_out] + if not color_by_assembly: + h = css4_colors[k].lstrip("#") + colors.append(tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))) + k = k + 1 if k < len(css4_colors) - 1 else 0 -@pyaedt_function_handler() -@graphics_required -def nastran_to_stl(input_file, output_folder=None, decimation=0, enable_planar_merge="True", preview=False): - """Convert Nastran file into stl.""" - import pyvista as pv + pl.add_mesh(pv.PolyData(p_out, faces=fin), color=colors[-1], **dargs) - if not Path(input_file).exists(): - raise FileNotFoundError(f"File ({input_file}) not found") + pl.add_text("Input mesh", font_size=24) + pl.reset_camera() - logger = logging.getLogger("Global") - nas_to_dict = _parse_nastran(input_file) - - empty = True - for assembly in nas_to_dict["Assemblies"].values(): - if assembly["Triangles"] or assembly["Solids"] or assembly["Lines"]: - empty = False - break - if empty: # pragma: no cover - logger.error("Failed to import file. Check the model and retry") - return False - if output_folder is None: - output_folder = os.path.dirname(input_file) - output_stls, enable_stl_merge = _write_stl(nas_to_dict, decimation, output_folder, enable_planar_merge) - if preview: - logger.info("Generating preview...") - if decimation > 0: - pl = pv.Plotter(shape=(1, 2)) - else: # pragma: no cover - pl = pv.Plotter() - dargs = dict(show_edges=True) - colors = [] - color_by_assembly = True - if len(nas_to_dict["Assemblies"]) == 1: - color_by_assembly = False - - def preview_pyvista(dict_in): - css4_colors = list(CSS4_COLORS.values()) - k = 0 - p_out = nas_to_dict["Points"][::] - for assembly in dict_in["Assemblies"].values(): - if color_by_assembly: - h = css4_colors[k].lstrip("#") - colors.append(tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))) - k += 1 - - for triangles in assembly["Triangles"].values(): - if not triangles: - continue - tri_out = triangles - fin = [[3] + list(i) for i in tri_out] - if not color_by_assembly: - h = css4_colors[k].lstrip("#") - colors.append(tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))) - k = k + 1 if k < len(css4_colors) - 1 else 0 - pl.add_mesh(pv.PolyData(p_out, faces=fin), color=colors[-1], **dargs) - - for triangles in assembly["Solids"].values(): - if not triangles: - continue - import pandas as pd - - df = pd.Series(triangles) - tri_out = df.drop_duplicates(keep=False).to_list() - p_out = nas_to_dict["Points"][::] - fin = [[3] + list(i) for i in tri_out] - if not color_by_assembly: - h = css4_colors[k].lstrip("#") - colors.append(tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))) - k = k + 1 if k < len(css4_colors) - 1 else 0 - - pl.add_mesh(pv.PolyData(p_out, faces=fin), color=colors[-1], **dargs) - - preview_pyvista(nas_to_dict) - pl.add_text("Input mesh", font_size=24) + if decimation > 0 and output_stls: + k = 0 pl.reset_camera() - if decimation > 0 and output_stls: - k = 0 - pl.reset_camera() - pl.subplot(0, 1) - css4_colors = list(CSS4_COLORS.values()) - for output_stl in output_stls: - mesh = pv.read(output_stl) - h = css4_colors[k].lstrip("#") - colors.append(tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))) - pl.add_mesh(mesh, color=colors[-1], **dargs) - k = k + 1 if k < len(css4_colors) - 1 else 0 - pl.add_text("Decimated mesh", font_size=24) - pl.reset_camera() - pl.link_views() - if "PYTEST_CURRENT_TEST" not in os.environ: - pl.show() # pragma: no cover - logger.info("STL files created") - return output_stls, nas_to_dict, enable_stl_merge + pl.subplot(0, 1) + css4_colors = list(CSS4_COLORS.values()) + for output_stl in output_stls: + mesh = pv.read(output_stl) + h = css4_colors[k].lstrip("#") + colors.append(tuple(int(h[i : i + 2], 16) for i in (0, 2, 4))) + pl.add_mesh(mesh, color=colors[-1], **dargs) + k = k + 1 if k < len(css4_colors) - 1 else 0 + pl.add_text("Decimated mesh", font_size=24) + pl.reset_camera() + pl.link_views() + if "PYTEST_CURRENT_TEST" not in os.environ: + pl.show() # pragma: no cover @pyaedt_function_handler() @graphics_required -def simplify_stl(input_file, output_file=None, decimation=0.5, preview=False): +def simplify_and_preview_stl(input_file, output_file=None, decimation=0.5, preview=False): """Import and simplify a stl file using pyvista and fast-simplification. Parameters diff --git a/tests/system/extensions/test_45_extensions.py b/tests/system/extensions/test_45_extensions.py index c04cab406ef..b88891b637d 100644 --- a/tests/system/extensions/test_45_extensions.py +++ b/tests/system/extensions/test_45_extensions.py @@ -135,7 +135,16 @@ def test_06_project_import_stl(self, add_app, local_scratch): os.path.join(local_scratch.path, "sphere.stl"), ) - assert main({"is_test": True, "file_path": file_path, "lightweight": True, "decimate": 0.0, "planar": True}) + assert main( + { + "is_test": True, + "file_path": file_path, + "lightweight": True, + "decimate": 0.0, + "planar": True, + "remove_multiple_connections": True, + } + ) assert len(aedtapp.modeler.object_list) == 1 aedtapp.close_project(aedtapp.project_name) diff --git a/tests/system/general/test_20_HFSS.py b/tests/system/general/test_20_HFSS.py index e2750259b95..9d0adf34421 100644 --- a/tests/system/general/test_20_HFSS.py +++ b/tests/system/general/test_20_HFSS.py @@ -1466,11 +1466,11 @@ def test_59_test_nastran(self): assert self.aedtapp.modeler.import_nastran(example_project2, decimation=0.1, preview=True, save_only_stl=True) assert self.aedtapp.modeler.import_nastran(example_project2, decimation=0.5) example_project = os.path.join(TESTS_GENERAL_PATH, "example_models", test_subfolder, "sphere.stl") - from ansys.aedt.core.visualization.advanced.misc import simplify_stl + from ansys.aedt.core.visualization.advanced.misc import simplify_and_preview_stl - out = simplify_stl(example_project, decimation=0.8) + out = simplify_and_preview_stl(example_project, decimation=0.8) assert os.path.exists(out) - out = simplify_stl(example_project, decimation=0.8, preview=True) + out = simplify_and_preview_stl(example_project, decimation=0.8, preview=True) assert out def test_60_set_variable(self): diff --git a/tests/system/general/test_utils.py b/tests/system/general/test_utils.py index 92ebdaf1d05..01ad0ab8132 100644 --- a/tests/system/general/test_utils.py +++ b/tests/system/general/test_utils.py @@ -52,9 +52,9 @@ def test_settings_load_default_yaml(): assert default_settings_attributes == local_settings_attributes -def test_settings_writte_default_yaml(tmp_path): +def test_settings_write_default_yaml(tmp_path): default_settings = Settings() path = str("pyaedt_settings.yaml") - default_settings.writte_yaml_configuration(path) + default_settings.write_yaml_configuration(path) assert os.path.exists(path) diff --git a/tests/unit/extensions/test_import_nastran.py b/tests/unit/extensions/test_import_nastran.py index 3948b44f3bd..3ade263de91 100644 --- a/tests/unit/extensions/test_import_nastran.py +++ b/tests/unit/extensions/test_import_nastran.py @@ -73,8 +73,8 @@ def test_import_nastran_default_values(): @patch("tkinter.filedialog.askopenfilename") def test_import_nastran_modified_values(mock_askopenfilename): - """Test that the modifief values of the UI are returned correctly.""" - EXPECTED_RESULT = ExtensionData(0.5, True, False, MOCK_NAS_PATH) + """Test that the modified values of the UI are returned correctly.""" + EXPECTED_RESULT = ExtensionData(0.5, True, False, MOCK_NAS_PATH, True) mock_askopenfilename.return_value = MOCK_NAS_PATH root = create_ui(withdraw=True) @@ -82,6 +82,7 @@ def test_import_nastran_modified_values(mock_askopenfilename): root.nametowidget("browse_button").invoke() root.nametowidget("check_lightweight").invoke() root.nametowidget("check_planar_merge").invoke() + root.nametowidget("check_remove_multiple_connections").invoke() root.nametowidget("decimation_text").delete("1.0", "end") root.nametowidget("decimation_text").insert("1.0", "0.5") root.nametowidget("ok_button").invoke() @@ -94,7 +95,7 @@ def test_import_nastran_modified_values(mock_askopenfilename): @pytest.mark.parametrize("mock_path", [MOCK_NAS_PATH, MOCK_STL_PATH]) @patch("tkinter.filedialog.askopenfilename") def test_import_nastran_preview_on_non_existing_file(mock_askopenfilename, mock_path): - """Test that the preview button raises an exception when non existing file is selected.""" + """Test that the preview button raises an exception when a non-existing file is selected.""" mock_askopenfilename.return_value = mock_path root = create_ui(withdraw=True) root.nametowidget("browse_button").invoke()