Skip to content

REFACTOR: Import nastran extension and tests #6227

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ jobs:
pytest-extra-args: ${{ env.PYTEST_ARGUMENTS }}
python-version: ${{ env.MAIN_PYTHON_VERSION }}
optional-dependencies-name: unit-tests
requires-xvfb: true

- name: Upload coverage to Codecov
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
Expand Down Expand Up @@ -684,6 +685,11 @@ jobs:
pip uninstall --yes vtk
pip install --extra-index-url https://wheels.vtk.org vtk-osmesa

- name: "Install X Virtual Frame Buffer"
run: |
sudo apt-get update
sudo apt-get install -y xvfb

- name: Run tests marked with 'extensions'
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
with:
Expand All @@ -693,7 +699,7 @@ jobs:
command: |
export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT251 }}/common/mono/Linux64/lib64:$LD_LIBRARY_PATH
source .venv/bin/activate
pytest ${{ env.PYTEST_ARGUMENTS }} --timeout=600 -m extensions
xvfb-run pytest ${{ env.PYTEST_ARGUMENTS }} --timeout=600 -m extensions

- uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
with:
Expand Down
1 change: 1 addition & 0 deletions doc/changelog.d/6227.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Import nastran extension and tests
52 changes: 52 additions & 0 deletions src/ansys/aedt/core/extensions/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import argparse
import os
from pathlib import Path
import sys

from ansys.aedt.core.internal.aedt_versions import aedt_versions
Expand Down Expand Up @@ -63,6 +64,57 @@
return student_version


def create_default_ui(title, withdraw=False):
import tkinter
from tkinter import ttk
from tkinter.messagebox import showerror

import PIL.Image
import PIL.ImageTk

import ansys.aedt.core.extensions
from ansys.aedt.core.extensions.misc import ExtensionTheme

def report_callback_exception(self, exc, val, tb):
showerror("Error", message=str(val))

Check warning on line 79 in src/ansys/aedt/core/extensions/misc.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/extensions/misc.py#L79

Added line #L79 was not covered by tests

def report_callback_exception_withdraw(self, exc, val, tb):
raise val

if withdraw:
tkinter.Tk.report_callback_exception = report_callback_exception_withdraw
else:
tkinter.Tk.report_callback_exception = report_callback_exception

root = tkinter.Tk()

if withdraw:
root.withdraw()
root.title(title)

if not withdraw:
# Load the logo for the main window
icon_path = Path(ansys.aedt.core.extensions.__path__[0]) / "images" / "large" / "logo.png"
im = PIL.Image.open(icon_path)
photo = PIL.ImageTk.PhotoImage(im)

# Set the icon for the main window
root.iconphoto(True, photo)

# Configure style for ttk buttons
style = ttk.Style()
theme = ExtensionTheme()

# Apply light theme initially
theme.apply_light_theme(style)
root.theme = "light"

# Set background color of the window (optional)
root.configure(bg=theme.light["widget_bg"])

return root, theme, style


def get_arguments(args=None, description=""): # pragma: no cover
"""Get extension arguments."""
output_args = {"is_batch": False, "is_test": False}
Expand Down
181 changes: 80 additions & 101 deletions src/ansys/aedt/core/extensions/project/import_nastran.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from dataclasses import asdict
from dataclasses import dataclass
import os
from pathlib import Path
import tkinter

import ansys.aedt.core
from ansys.aedt.core import get_pyaedt_app
Expand All @@ -34,55 +38,37 @@
from ansys.aedt.core.extensions.misc import is_student
from ansys.aedt.core.visualization.advanced.misc import nastran_to_stl

port = get_port()
version = get_aedt_version()
aedt_process_id = get_process_id()
is_student = is_student()

# Extension batch arguments
extension_arguments = {"decimate": 0.0, "lightweight": False, "planar": True, "file_path": ""}
extension_description = "Import Nastran or STL file"
@dataclass
class ExtensionData:
decimate: float = 0.0
lightweight: bool = False
planar: bool = True
file_path: str = ""


def frontend(): # pragma: no cover
import tkinter
from tkinter import filedialog
from tkinter import ttk

import PIL.Image
import PIL.ImageTk

from ansys.aedt.core.extensions.misc import ExtensionTheme

master = tkinter.Tk()
master.title("Import Nastran or STL file")
PORT = get_port()
VERSION = get_aedt_version()
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": ""}

# Detect if user closes the UI
master.flag = False
result = None

# Load the logo for the main window
icon_path = Path(ansys.aedt.core.extensions.__path__[0]) / "images" / "large" / "logo.png"
im = PIL.Image.open(icon_path)
photo = PIL.ImageTk.PhotoImage(im)

# Set the icon for the main window
master.iconphoto(True, photo)

# Configure style for ttk buttons
style = ttk.Style()
theme = ExtensionTheme()

# Apply light theme initially
theme.apply_light_theme(style)
master.theme = "light"
def create_ui(withdraw=False):
from tkinter import filedialog
from tkinter import ttk

# Set background color of the window (optional)
master.configure(bg=theme.light["widget_bg"])
from ansys.aedt.core.extensions.misc import create_default_ui

label2 = ttk.Label(master, text="Browse file:", style="PyAEDT.TLabel")
root, theme, style = create_default_ui(EXTENSION_TITLE, withdraw=withdraw)

label2 = ttk.Label(root, text="Browse file:", style="PyAEDT.TLabel")
label2.grid(row=0, column=0, pady=10)
text = tkinter.Text(master, width=40, height=1)

text = tkinter.Text(root, width=40, height=1, name="file_path_text")
text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)
text.grid(row=0, column=1, pady=10, padx=5)

Expand All @@ -94,105 +80,97 @@
)
text.insert(tkinter.END, filename)

b1 = ttk.Button(master, text="...", width=10, command=browseFiles, style="PyAEDT.TButton")
b1 = ttk.Button(root, text="...", width=10, command=browseFiles, style="PyAEDT.TButton", name="browse_button")
b1.grid(row=0, column=2, pady=10)

label = ttk.Label(master, text="Decimation factor (0-0.9). It may affect results:", style="PyAEDT.TLabel")
label = ttk.Label(root, text="Decimation factor (0-0.9). It may affect results:", style="PyAEDT.TLabel")
label.grid(row=1, column=0, pady=10)

check = tkinter.Text(master, width=20, height=1)
check = tkinter.Text(root, width=20, height=1, name="decimation_text")
check.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)
check.insert(tkinter.END, "0.0")
check.grid(row=1, column=1, pady=10, padx=5)

label = ttk.Label(master, text="Import as lightweight (only HFSS):", style="PyAEDT.TLabel")
label = ttk.Label(root, text="Import as lightweight (only HFSS):", style="PyAEDT.TLabel")
label.grid(row=2, column=0, pady=10)
light = tkinter.IntVar()
check2 = ttk.Checkbutton(master, variable=light, style="PyAEDT.TCheckbutton")
light = tkinter.IntVar(root, name="var_lightweight")
check2 = ttk.Checkbutton(root, variable=light, style="PyAEDT.TCheckbutton", name="check_lightweight")
check2.grid(row=2, column=1, pady=10, padx=5)

label = ttk.Label(master, text="Enable planar merge:", style="PyAEDT.TLabel")
label = ttk.Label(root, text="Enable planar merge:", style="PyAEDT.TLabel")
label.grid(row=3, column=0, pady=10)
planar = tkinter.IntVar(value=1)
check3 = ttk.Checkbutton(master, variable=planar, style="PyAEDT.TCheckbutton")
planar = tkinter.IntVar(root, value=1)
check3 = ttk.Checkbutton(root, variable=planar, style="PyAEDT.TCheckbutton", name="check_planar_merge")
check3.grid(row=3, column=1, pady=10, padx=5)

def toggle_theme():
if master.theme == "light":
if root.theme == "light":
set_dark_theme()
master.theme = "dark"
root.theme = "dark"
else:
set_light_theme()
master.theme = "light"
root.theme = "light"

def set_light_theme():
master.configure(bg=theme.light["widget_bg"])
root.configure(bg=theme.light["widget_bg"])
text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)
check.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)
theme.apply_light_theme(style)
change_theme_button.config(text="\u263d") # Sun icon for light theme

def set_dark_theme():
master.configure(bg=theme.dark["widget_bg"])
root.configure(bg=theme.dark["widget_bg"])
text.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font)
check.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font)
theme.apply_dark_theme(style)
change_theme_button.config(text="\u2600") # Moon icon for dark theme

# Create a frame for the toggle button to position it correctly
button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2)
button_frame = ttk.Frame(
root, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2, name="theme_button_frame"
)
button_frame.grid(row=5, column=2, pady=10, padx=10)

# Add the toggle theme button inside the frame
change_theme_button = ttk.Button(
button_frame, width=20, text="\u263d", command=toggle_theme, style="PyAEDT.TButton"
button_frame, width=20, text="\u263d", command=toggle_theme, style="PyAEDT.TButton", name="theme_toggle_button"
)

change_theme_button.grid(row=0, column=0, padx=0)

def callback():
master.flag = True
master.decimate_ui = float(check.get("1.0", tkinter.END).strip())
master.lightweight_ui = True if light.get() == 1 else False
master.planar_ui = True if planar.get() == 1 else False
master.file_path_ui = text.get("1.0", tkinter.END).strip()
master.destroy()
global result
result = ExtensionData(
decimate=float(check.get("1.0", tkinter.END).strip()),
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(),
)
root.destroy()

def preview():
master.decimate_ui = float(check.get("1.0", tkinter.END).strip())
master.lightweight_ui = True if light.get() == 1 else False
master.planar_ui = True if planar.get() == 1 else False
master.file_path_ui = text.get("1.0", tkinter.END).strip()
decimate_ui = float(check.get("1.0", tkinter.END).strip())
file_path_ui = text.get("1.0", tkinter.END).strip()
if not file_path_ui:
raise ValueError("Incorrect file path. Please select a valid file.")

Check warning on line 155 in src/ansys/aedt/core/extensions/project/import_nastran.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/extensions/project/import_nastran.py#L155

Added line #L155 was not covered by tests

if not Path(file_path_ui).is_file():
raise FileNotFoundError(f"File ({file_path_ui}) not found")

if master.file_path_ui.endswith(".nas"):
nastran_to_stl(input_file=master.file_path_ui, decimation=master.decimate_ui, preview=True)
if file_path_ui.endswith(".nas"):
nastran_to_stl(file_path_ui, decimation=decimate_ui, preview=True)

Check warning on line 161 in src/ansys/aedt/core/extensions/project/import_nastran.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/extensions/project/import_nastran.py#L160-L161

Added lines #L160 - L161 were not covered by tests
else:
from ansys.aedt.core.visualization.advanced.misc import simplify_stl

simplify_stl(master.file_path_ui, decimation=master.decimate_ui, preview=True)
simplify_stl(file_path_ui, decimation=decimate_ui, preview=True)

Check warning on line 165 in src/ansys/aedt/core/extensions/project/import_nastran.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/extensions/project/import_nastran.py#L165

Added line #L165 was not covered by tests

b2 = ttk.Button(master, text="Preview", width=40, command=preview, style="PyAEDT.TButton")
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)

b3 = ttk.Button(master, text="Ok", width=40, command=callback, style="PyAEDT.TButton")
b3 = ttk.Button(root, text="Ok", width=40, command=callback, style="PyAEDT.TButton", name="ok_button")
b3.grid(row=5, column=1, pady=10, padx=10)

tkinter.mainloop()

decimate_ui = getattr(master, "decimate_ui", extension_arguments["decimate"])
lightweight_ui = getattr(master, "lightweight_ui", extension_arguments["lightweight"])
planar_ui = getattr(master, "planar_ui", extension_arguments["planar"])
file_path_ui = getattr(master, "file_path_ui", extension_arguments["file_path"])

output_dict = {}
if master.flag:
output_dict = {
"decimate": decimate_ui,
"lightweight": lightweight_ui,
"planar": planar_ui,
"file_path": file_path_ui,
}
return output_dict
return root


def main(extension_args):
Expand All @@ -204,10 +182,10 @@
if file_path.is_file():
app = ansys.aedt.core.Desktop(
new_desktop=False,
version=version,
port=port,
aedt_process_id=aedt_process_id,
student_version=is_student,
version=VERSION,
port=PORT,
aedt_process_id=AEDT_PROCESS_ID,
student_version=IS_STUDENT,
)

active_project = app.active_project()
Expand All @@ -233,28 +211,29 @@
else:
app = ansys.aedt.core.Desktop(
new_desktop=False,
version=version,
port=port,
aedt_process_id=aedt_process_id,
student_version=is_student,
version=VERSION,
port=PORT,
aedt_process_id=AEDT_PROCESS_ID,
student_version=IS_STUDENT,
)
app.logger.debug("Wrong file selected. Select a .nas or .stl file")

if not extension_args["is_test"]: # pragma: no cover
if "PYTEST_CURRENT_TEST" not in os.environ:
app.release_desktop(False, False)
return True


if __name__ == "__main__": # pragma: no cover
args = get_arguments(extension_arguments, extension_description)
args = get_arguments(EXTENSION_DEFAULT_ARGUMENTS, EXTENSION_TITLE)

# Open UI
if not args["is_batch"]: # pragma: no cover
output = frontend()
if output:
for output_name, output_value in output.items():
if output_name in extension_arguments:
args[output_name] = output_value
root = create_ui()

tkinter.mainloop()

if result:
args.update(asdict(result))
main(args)
else:
main(args)
Loading