Skip to content

Commit 18f9830

Browse files
REFACTOR: Import nastran extension and tests (#6227)
Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent 57195c4 commit 18f9830

File tree

8 files changed

+330
-131
lines changed

8 files changed

+330
-131
lines changed

.github/workflows/ci_cd.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ jobs:
164164
pytest-extra-args: ${{ env.PYTEST_ARGUMENTS }}
165165
python-version: ${{ env.MAIN_PYTHON_VERSION }}
166166
optional-dependencies-name: unit-tests
167+
requires-xvfb: true
167168

168169
- name: Upload coverage to Codecov
169170
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
@@ -686,6 +687,11 @@ jobs:
686687
pip uninstall --yes vtk
687688
pip install --extra-index-url https://wheels.vtk.org vtk-osmesa
688689
690+
- name: "Install X Virtual Frame Buffer"
691+
run: |
692+
sudo apt-get update
693+
sudo apt-get install -y xvfb
694+
689695
- name: Run tests marked with 'extensions'
690696
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
691697
with:
@@ -695,7 +701,7 @@ jobs:
695701
command: |
696702
export LD_LIBRARY_PATH=${{ env.ANSYSEM_ROOT251 }}/common/mono/Linux64/lib64:$LD_LIBRARY_PATH
697703
source .venv/bin/activate
698-
pytest ${{ env.PYTEST_ARGUMENTS }} --timeout=600 -m extensions
704+
xvfb-run pytest ${{ env.PYTEST_ARGUMENTS }} --timeout=600 -m extensions
699705
700706
- uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
701707
with:

doc/changelog.d/6227.miscellaneous.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Import nastran extension and tests

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import argparse
2828
import os
29+
from pathlib import Path
2930
import sys
3031

3132
from ansys.aedt.core.internal.aedt_versions import aedt_versions
@@ -63,6 +64,57 @@ def is_student():
6364
return student_version
6465

6566

67+
def create_default_ui(title, withdraw=False):
68+
import tkinter
69+
from tkinter import ttk
70+
from tkinter.messagebox import showerror
71+
72+
import PIL.Image
73+
import PIL.ImageTk
74+
75+
import ansys.aedt.core.extensions
76+
from ansys.aedt.core.extensions.misc import ExtensionTheme
77+
78+
def report_callback_exception(self, exc, val, tb):
79+
showerror("Error", message=str(val))
80+
81+
def report_callback_exception_withdraw(self, exc, val, tb):
82+
raise val
83+
84+
if withdraw:
85+
tkinter.Tk.report_callback_exception = report_callback_exception_withdraw
86+
else:
87+
tkinter.Tk.report_callback_exception = report_callback_exception
88+
89+
root = tkinter.Tk()
90+
91+
if withdraw:
92+
root.withdraw()
93+
root.title(title)
94+
95+
if not withdraw:
96+
# Load the logo for the main window
97+
icon_path = Path(ansys.aedt.core.extensions.__path__[0]) / "images" / "large" / "logo.png"
98+
im = PIL.Image.open(icon_path)
99+
photo = PIL.ImageTk.PhotoImage(im)
100+
101+
# Set the icon for the main window
102+
root.iconphoto(True, photo)
103+
104+
# Configure style for ttk buttons
105+
style = ttk.Style()
106+
theme = ExtensionTheme()
107+
108+
# Apply light theme initially
109+
theme.apply_light_theme(style)
110+
root.theme = "light"
111+
112+
# Set background color of the window (optional)
113+
root.configure(bg=theme.light["widget_bg"])
114+
115+
return root, theme, style
116+
117+
66118
def get_arguments(args=None, description=""): # pragma: no cover
67119
"""Get extension arguments."""
68120
output_args = {"is_batch": False, "is_test": False}

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

Lines changed: 80 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2323
# SOFTWARE.
2424

25+
from dataclasses import asdict
26+
from dataclasses import dataclass
27+
import os
2528
from pathlib import Path
29+
import tkinter
2630

2731
import ansys.aedt.core
2832
from ansys.aedt.core import get_pyaedt_app
@@ -34,55 +38,37 @@
3438
from ansys.aedt.core.extensions.misc import is_student
3539
from ansys.aedt.core.visualization.advanced.misc import nastran_to_stl
3640

37-
port = get_port()
38-
version = get_aedt_version()
39-
aedt_process_id = get_process_id()
40-
is_student = is_student()
4141

42-
# Extension batch arguments
43-
extension_arguments = {"decimate": 0.0, "lightweight": False, "planar": True, "file_path": ""}
44-
extension_description = "Import Nastran or STL file"
42+
@dataclass
43+
class ExtensionData:
44+
decimate: float = 0.0
45+
lightweight: bool = False
46+
planar: bool = True
47+
file_path: str = ""
4548

4649

47-
def frontend(): # pragma: no cover
48-
import tkinter
49-
from tkinter import filedialog
50-
from tkinter import ttk
51-
52-
import PIL.Image
53-
import PIL.ImageTk
54-
55-
from ansys.aedt.core.extensions.misc import ExtensionTheme
56-
57-
master = tkinter.Tk()
58-
master.title("Import Nastran or STL file")
50+
PORT = get_port()
51+
VERSION = get_aedt_version()
52+
AEDT_PROCESS_ID = get_process_id()
53+
IS_STUDENT = is_student()
54+
EXTENSION_TITLE = "Import Nastran or STL file"
55+
EXTENSION_DEFAULT_ARGUMENTS = {"decimate": 0.0, "lightweight": False, "planar": True, "file_path": ""}
5956

60-
# Detect if user closes the UI
61-
master.flag = False
57+
result = None
6258

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

68-
# Set the icon for the main window
69-
master.iconphoto(True, photo)
70-
71-
# Configure style for ttk buttons
72-
style = ttk.Style()
73-
theme = ExtensionTheme()
74-
75-
# Apply light theme initially
76-
theme.apply_light_theme(style)
77-
master.theme = "light"
60+
def create_ui(withdraw=False):
61+
from tkinter import filedialog
62+
from tkinter import ttk
7863

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

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

68+
label2 = ttk.Label(root, text="Browse file:", style="PyAEDT.TLabel")
8469
label2.grid(row=0, column=0, pady=10)
85-
text = tkinter.Text(master, width=40, height=1)
70+
71+
text = tkinter.Text(root, width=40, height=1, name="file_path_text")
8672
text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)
8773
text.grid(row=0, column=1, pady=10, padx=5)
8874

@@ -94,105 +80,97 @@ def browseFiles():
9480
)
9581
text.insert(tkinter.END, filename)
9682

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

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

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

108-
label = ttk.Label(master, text="Import as lightweight (only HFSS):", style="PyAEDT.TLabel")
94+
label = ttk.Label(root, text="Import as lightweight (only HFSS):", style="PyAEDT.TLabel")
10995
label.grid(row=2, column=0, pady=10)
110-
light = tkinter.IntVar()
111-
check2 = ttk.Checkbutton(master, variable=light, style="PyAEDT.TCheckbutton")
96+
light = tkinter.IntVar(root, name="var_lightweight")
97+
check2 = ttk.Checkbutton(root, variable=light, style="PyAEDT.TCheckbutton", name="check_lightweight")
11298
check2.grid(row=2, column=1, pady=10, padx=5)
11399

114-
label = ttk.Label(master, text="Enable planar merge:", style="PyAEDT.TLabel")
100+
label = ttk.Label(root, text="Enable planar merge:", style="PyAEDT.TLabel")
115101
label.grid(row=3, column=0, pady=10)
116-
planar = tkinter.IntVar(value=1)
117-
check3 = ttk.Checkbutton(master, variable=planar, style="PyAEDT.TCheckbutton")
102+
planar = tkinter.IntVar(root, value=1)
103+
check3 = ttk.Checkbutton(root, variable=planar, style="PyAEDT.TCheckbutton", name="check_planar_merge")
118104
check3.grid(row=3, column=1, pady=10, padx=5)
119105

120106
def toggle_theme():
121-
if master.theme == "light":
107+
if root.theme == "light":
122108
set_dark_theme()
123-
master.theme = "dark"
109+
root.theme = "dark"
124110
else:
125111
set_light_theme()
126-
master.theme = "light"
112+
root.theme = "light"
127113

128114
def set_light_theme():
129-
master.configure(bg=theme.light["widget_bg"])
115+
root.configure(bg=theme.light["widget_bg"])
130116
text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)
131117
check.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)
132118
theme.apply_light_theme(style)
133119
change_theme_button.config(text="\u263d") # Sun icon for light theme
134120

135121
def set_dark_theme():
136-
master.configure(bg=theme.dark["widget_bg"])
122+
root.configure(bg=theme.dark["widget_bg"])
137123
text.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font)
138124
check.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font)
139125
theme.apply_dark_theme(style)
140126
change_theme_button.config(text="\u2600") # Moon icon for dark theme
141127

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

146134
# Add the toggle theme button inside the frame
147135
change_theme_button = ttk.Button(
148-
button_frame, width=20, text="\u263d", command=toggle_theme, style="PyAEDT.TButton"
136+
button_frame, width=20, text="\u263d", command=toggle_theme, style="PyAEDT.TButton", name="theme_toggle_button"
149137
)
150138

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

153141
def callback():
154-
master.flag = True
155-
master.decimate_ui = float(check.get("1.0", tkinter.END).strip())
156-
master.lightweight_ui = True if light.get() == 1 else False
157-
master.planar_ui = True if planar.get() == 1 else False
158-
master.file_path_ui = text.get("1.0", tkinter.END).strip()
159-
master.destroy()
142+
global result
143+
result = ExtensionData(
144+
decimate=float(check.get("1.0", tkinter.END).strip()),
145+
lightweight=True if light.get() == 1 else False,
146+
planar=True if planar.get() == 1 else False,
147+
file_path=text.get("1.0", tkinter.END).strip(),
148+
)
149+
root.destroy()
160150

161151
def preview():
162-
master.decimate_ui = float(check.get("1.0", tkinter.END).strip())
163-
master.lightweight_ui = True if light.get() == 1 else False
164-
master.planar_ui = True if planar.get() == 1 else False
165-
master.file_path_ui = text.get("1.0", tkinter.END).strip()
152+
decimate_ui = float(check.get("1.0", tkinter.END).strip())
153+
file_path_ui = text.get("1.0", tkinter.END).strip()
154+
if not file_path_ui:
155+
raise ValueError("Incorrect file path. Please select a valid file.")
156+
157+
if not Path(file_path_ui).is_file():
158+
raise FileNotFoundError(f"File ({file_path_ui}) not found")
166159

167-
if master.file_path_ui.endswith(".nas"):
168-
nastran_to_stl(input_file=master.file_path_ui, decimation=master.decimate_ui, preview=True)
160+
if file_path_ui.endswith(".nas"):
161+
nastran_to_stl(file_path_ui, decimation=decimate_ui, preview=True)
169162
else:
170163
from ansys.aedt.core.visualization.advanced.misc import simplify_stl
171164

172-
simplify_stl(master.file_path_ui, decimation=master.decimate_ui, preview=True)
165+
simplify_stl(file_path_ui, decimation=decimate_ui, preview=True)
173166

174-
b2 = ttk.Button(master, text="Preview", width=40, command=preview, style="PyAEDT.TButton")
167+
b2 = ttk.Button(root, text="Preview", width=40, command=preview, style="PyAEDT.TButton", name="preview_button")
175168
b2.grid(row=5, column=0, pady=10, padx=10)
176169

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

180-
tkinter.mainloop()
181-
182-
decimate_ui = getattr(master, "decimate_ui", extension_arguments["decimate"])
183-
lightweight_ui = getattr(master, "lightweight_ui", extension_arguments["lightweight"])
184-
planar_ui = getattr(master, "planar_ui", extension_arguments["planar"])
185-
file_path_ui = getattr(master, "file_path_ui", extension_arguments["file_path"])
186-
187-
output_dict = {}
188-
if master.flag:
189-
output_dict = {
190-
"decimate": decimate_ui,
191-
"lightweight": lightweight_ui,
192-
"planar": planar_ui,
193-
"file_path": file_path_ui,
194-
}
195-
return output_dict
173+
return root
196174

197175

198176
def main(extension_args):
@@ -204,10 +182,10 @@ def main(extension_args):
204182
if file_path.is_file():
205183
app = ansys.aedt.core.Desktop(
206184
new_desktop=False,
207-
version=version,
208-
port=port,
209-
aedt_process_id=aedt_process_id,
210-
student_version=is_student,
185+
version=VERSION,
186+
port=PORT,
187+
aedt_process_id=AEDT_PROCESS_ID,
188+
student_version=IS_STUDENT,
211189
)
212190

213191
active_project = app.active_project()
@@ -233,28 +211,29 @@ def main(extension_args):
233211
else:
234212
app = ansys.aedt.core.Desktop(
235213
new_desktop=False,
236-
version=version,
237-
port=port,
238-
aedt_process_id=aedt_process_id,
239-
student_version=is_student,
214+
version=VERSION,
215+
port=PORT,
216+
aedt_process_id=AEDT_PROCESS_ID,
217+
student_version=IS_STUDENT,
240218
)
241219
app.logger.debug("Wrong file selected. Select a .nas or .stl file")
242220

243-
if not extension_args["is_test"]: # pragma: no cover
221+
if "PYTEST_CURRENT_TEST" not in os.environ:
244222
app.release_desktop(False, False)
245223
return True
246224

247225

248226
if __name__ == "__main__": # pragma: no cover
249-
args = get_arguments(extension_arguments, extension_description)
227+
args = get_arguments(EXTENSION_DEFAULT_ARGUMENTS, EXTENSION_TITLE)
250228

251229
# Open UI
252230
if not args["is_batch"]: # pragma: no cover
253-
output = frontend()
254-
if output:
255-
for output_name, output_value in output.items():
256-
if output_name in extension_arguments:
257-
args[output_name] = output_value
231+
root = create_ui()
232+
233+
tkinter.mainloop()
234+
235+
if result:
236+
args.update(asdict(result))
258237
main(args)
259238
else:
260239
main(args)

0 commit comments

Comments
 (0)