Skip to content

Commit ac6d158

Browse files
eblanco-ansyspyansys-ci-botpre-commit-ci[bot]
authored
REFACTOR: 6380 migrate import schematic extension (#6389)
Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 8d1a4ae commit ac6d158

File tree

5 files changed

+360
-137
lines changed

5 files changed

+360
-137
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
6380 migrate import schematic extension

src/ansys/aedt/core/extensions/circuit/import_schematic.py

Lines changed: 121 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
44
# SPDX-License-Identifier: MIT
55
#
6-
#
76
# Permission is hereby granted, free of charge, to any person obtaining a copy
87
# of this software and associated documentation files (the "Software"), to deal
98
# in the Software without restriction, including without limitation the rights
@@ -22,165 +21,154 @@
2221
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2322
# SOFTWARE.
2423

24+
25+
from dataclasses import dataclass
26+
import os
2527
from pathlib import Path
28+
import tkinter
29+
from tkinter import filedialog
30+
from tkinter import ttk
2631

27-
import ansys.aedt.core
2832
from ansys.aedt.core import Circuit
29-
import ansys.aedt.core.extensions
33+
from ansys.aedt.core import Desktop
34+
from ansys.aedt.core.extensions.misc import ExtensionCircuitCommon
35+
from ansys.aedt.core.extensions.misc import ExtensionCommonData
3036
from ansys.aedt.core.extensions.misc import get_aedt_version
3137
from ansys.aedt.core.extensions.misc import get_arguments
3238
from ansys.aedt.core.extensions.misc import get_port
3339
from ansys.aedt.core.extensions.misc import get_process_id
3440
from ansys.aedt.core.extensions.misc import is_student
3541

36-
port = get_port()
37-
version = get_aedt_version()
38-
aedt_process_id = get_process_id()
39-
is_student = is_student()
40-
41-
# Extension batch arguments
42-
extension_arguments = {"asc_file": ""}
43-
extension_description = "Import schematic to Circuit"
44-
45-
46-
def frontend(): # pragma: no cover
47-
import tkinter
48-
from tkinter import filedialog
49-
from tkinter import ttk
50-
51-
import PIL.Image
52-
import PIL.ImageTk
53-
54-
from ansys.aedt.core.extensions.misc import ExtensionTheme
42+
# Retrieve environment info
43+
PORT = get_port()
44+
VERSION = get_aedt_version()
45+
AEDT_PROCESS_ID = get_process_id()
46+
IS_STUDENT = is_student()
5547

56-
master = tkinter.Tk()
57-
master.title(extension_description)
48+
# Extension batch arguments and title
49+
EXTENSION_DEFAULT_ARGUMENTS = {"file_extension": ""}
50+
EXTENSION_TITLE = "Import schematic to Circuit"
5851

59-
# Detect if user closes the UI
60-
master.flag = False
6152

62-
# Load the logo for the main window
63-
icon_path = Path(ansys.aedt.core.extensions.__path__[0]) / "images" / "large" / "logo.png"
64-
im = PIL.Image.open(icon_path)
65-
photo = PIL.ImageTk.PhotoImage(im)
53+
@dataclass
54+
class ImportSchematicData(ExtensionCommonData):
55+
"""Data class for import schematic extension."""
6656

67-
# Set the icon for the main window
68-
master.iconphoto(True, photo)
57+
file_extension: str = EXTENSION_DEFAULT_ARGUMENTS["file_extension"]
6958

70-
# Configure style for ttk buttons
71-
style = ttk.Style()
72-
theme = ExtensionTheme()
7359

74-
# Apply light theme initially
75-
theme.apply_light_theme(style)
76-
master.theme = "light"
60+
class ImportSchematicExtension(ExtensionCircuitCommon):
61+
"""Extension for importing schematic into Circuit."""
7762

78-
# Set background color of the window (optional)
79-
master.configure(bg=theme.light["widget_bg"])
80-
81-
label2 = ttk.Label(master, text="Browse file:", style="PyAEDT.TLabel")
82-
label2.grid(row=0, column=0, pady=10, padx=10)
83-
84-
text = tkinter.Text(master, width=40, height=1)
85-
text.grid(row=0, column=1, pady=10, padx=5)
86-
text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)
87-
88-
def browse_asc_folder():
89-
inital_dir = text.get("1.0", tkinter.END).strip()
90-
filename = filedialog.askopenfilename(
91-
initialdir=Path(inital_dir).parent if inital_dir else "/",
92-
title="Select configuration file",
93-
filetypes=(("LTSPice file", "*.asc"), ("Spice file", "*.cir *.sp"), ("Qcv file", "*.qcv")),
63+
def __init__(self, withdraw: bool = False):
64+
super().__init__(
65+
EXTENSION_TITLE,
66+
theme_color="light",
67+
withdraw=withdraw,
68+
add_custom_content=False,
69+
toggle_row=1,
70+
toggle_column=2,
9471
)
95-
text.insert(tkinter.END, filename)
96-
97-
b1 = ttk.Button(master, text="...", width=10, command=browse_asc_folder, style="PyAEDT.TButton")
98-
b1.grid(row=0, column=2, pady=10)
99-
100-
def toggle_theme():
101-
if master.theme == "light":
102-
set_dark_theme()
103-
master.theme = "dark"
104-
else:
105-
set_light_theme()
106-
master.theme = "light"
107-
108-
def set_light_theme():
109-
master.configure(bg=theme.light["widget_bg"])
110-
text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)
111-
theme.apply_light_theme(style)
112-
change_theme_button.config(text="\u263d") # Sun icon for light theme
113-
114-
def set_dark_theme():
115-
master.configure(bg=theme.dark["widget_bg"])
116-
text.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font)
117-
theme.apply_dark_theme(style)
118-
change_theme_button.config(text="\u2600") # Moon icon for dark theme
119-
120-
# Create a frame for the toggle button to position it correctly
121-
button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2)
122-
button_frame.grid(row=1, column=2, pady=10, padx=10) # Place it in the second row, third column
123-
124-
# Add the toggle theme button inside the frame
125-
change_theme_button = ttk.Button(button_frame, text="\u263d", command=toggle_theme, style="PyAEDT.TButton")
126-
change_theme_button.grid(row=0, column=0, padx=0)
127-
128-
def callback():
129-
master.flag = True
130-
master.asc_path_ui = text.get("1.0", tkinter.END).strip()
131-
master.destroy()
132-
133-
b3 = ttk.Button(master, text="Import", width=40, command=callback, style="PyAEDT.TButton")
134-
b3.grid(row=1, column=1, pady=10, padx=10)
135-
136-
tkinter.mainloop()
137-
138-
asc_file_ui = getattr(master, "asc_path_ui", extension_arguments["asc_file"])
139-
140-
output_dict = {}
141-
if master.flag:
142-
output_dict = {
143-
"asc_file": asc_file_ui,
144-
}
145-
return output_dict
72+
self._text_widget = None
73+
self.add_extension_content()
74+
75+
def add_extension_content(self):
76+
"""Add UI elements for file selection and import action."""
77+
label = ttk.Label(
78+
self.root,
79+
text="Browse file:",
80+
style="PyAEDT.TLabel",
81+
)
82+
label.grid(row=0, column=0, padx=15, pady=10)
14683

84+
self._text_widget = tkinter.Text(
85+
self.root,
86+
width=40,
87+
height=1,
88+
)
89+
self._text_widget.grid(row=0, column=1, padx=5, pady=10)
90+
91+
def browse_file():
92+
current = self._text_widget.get(
93+
"1.0",
94+
tkinter.END,
95+
).strip()
96+
initial = Path(current).parent if current else Path.home()
97+
filename = filedialog.askopenfilename(
98+
initialdir=initial,
99+
title="Select schematic file",
100+
filetypes=(
101+
("LTSPice file", "*.asc"),
102+
("Spice file", "*.cir *.sp"),
103+
("Qcv file", "*.qcv"),
104+
),
105+
)
106+
if filename:
107+
self._text_widget.delete("1.0", tkinter.END)
108+
self._text_widget.insert(tkinter.END, filename)
109+
110+
browse_button = ttk.Button(
111+
self.root,
112+
text="...",
113+
width=10,
114+
command=browse_file,
115+
style="PyAEDT.TButton",
116+
)
117+
browse_button.grid(row=0, column=2, padx=10, pady=10)
118+
119+
def callback():
120+
file_extension = self._text_widget.get(
121+
"1.0",
122+
tkinter.END,
123+
).strip()
124+
if not Path(file_extension).exists():
125+
raise ValueError("File does not exist.")
126+
self.data = ImportSchematicData(file_extension=file_extension)
127+
self.root.destroy()
128+
129+
import_button = ttk.Button(
130+
self.root,
131+
text="Import",
132+
width=40,
133+
command=callback,
134+
style="PyAEDT.TButton",
135+
)
136+
import_button.grid(row=1, column=1, padx=10, pady=10)
147137

148-
def main(extension_args):
149-
asc_file = Path(extension_args["asc_file"])
150-
if not asc_file.exists():
151-
raise Exception("File does not exist.")
152138

153-
app = ansys.aedt.core.Desktop(
139+
def main(data: ImportSchematicData) -> bool:
140+
"""Execute schematic import based on provided data."""
141+
file_extension = Path(data.file_extension)
142+
app = Desktop(
154143
new_desktop=False,
155-
version=version,
156-
port=port,
157-
aedt_process_id=aedt_process_id,
158-
student_version=is_student,
144+
version=VERSION,
145+
port=PORT,
146+
aedt_process_id=AEDT_PROCESS_ID,
147+
student_version=IS_STUDENT,
159148
)
149+
cir = Circuit(design=file_extension.stem)
160150

161-
cir = Circuit(design=asc_file.stem)
151+
if file_extension.suffix == ".asc":
152+
cir.create_schematic_from_asc_file(str(file_extension))
153+
elif file_extension.suffix in {".sp", ".cir"}:
154+
cir.create_schematic_from_netlist(str(file_extension))
155+
elif file_extension.suffix == ".qcv":
156+
cir.create_schematic_from_mentor_netlist(str(file_extension))
162157

163-
if asc_file.suffix == ".asc":
164-
cir.create_schematic_from_asc_file(str(asc_file))
165-
elif asc_file.suffix in {".sp", ".cir"}:
166-
cir.create_schematic_from_netlist(str(asc_file))
167-
elif asc_file.suffix == ".qcv":
168-
cir.create_schematic_from_mentor_netlist(str(asc_file))
169-
if not extension_args["is_test"]: # pragma: no cover
158+
if "PYTEST_CURRENT_TEST" not in os.environ:
170159
app.release_desktop(False, False)
171160
return True
172161

173162

174163
if __name__ == "__main__": # pragma: no cover
175-
args = get_arguments(extension_arguments, extension_description)
176-
177-
# Open UI
178-
if not args["is_batch"]: # pragma: no cover
179-
output = frontend()
180-
if output:
181-
for output_name, output_value in output.items():
182-
if output_name in extension_arguments:
183-
args[output_name] = output_value
184-
main(args)
164+
args = get_arguments(EXTENSION_DEFAULT_ARGUMENTS, EXTENSION_TITLE)
165+
if not args.get("is_batch", False):
166+
extension = ImportSchematicExtension(withdraw=False)
167+
tkinter.mainloop()
168+
if extension.data:
169+
main(extension.data)
185170
else:
186-
main(args)
171+
data = ImportSchematicData()
172+
for key, value in args.items():
173+
setattr(data, key, value)
174+
main(data)

tests/system/extensions/test_45_extensions.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,20 +262,21 @@ def test_13_parametrize_layout(self, local_scratch):
262262
def test_15_import_asc(self, local_scratch, add_app):
263263
aedtapp = add_app("Circuit", application=ansys.aedt.core.Circuit)
264264

265+
from ansys.aedt.core.extensions.circuit.import_schematic import ImportSchematicData
265266
from ansys.aedt.core.extensions.circuit.import_schematic import main
266267

267268
file_path = os.path.join(local_path, "example_models", "T21", "butter.asc")
268-
assert main({"is_test": True, "asc_file": file_path})
269+
assert main(ImportSchematicData(file_extension=file_path))
269270

270271
file_path = os.path.join(local_path, "example_models", "T21", "netlist_small.cir")
271-
assert main({"is_test": True, "asc_file": file_path})
272+
assert main(ImportSchematicData(file_extension=file_path))
272273

273274
file_path = os.path.join(local_path, "example_models", "T21", "Schematic1.qcv")
274-
assert main({"is_test": True, "asc_file": file_path})
275+
assert main(ImportSchematicData(file_extension=file_path))
275276

276277
file_path_invented = os.path.join(local_path, "example_models", "T21", "butter_invented.asc")
277278
with pytest.raises(Exception) as execinfo:
278-
main({"is_test": True, "asc_file": file_path_invented})
279+
main(ImportSchematicData(file_extension=file_path_invented))
279280
assert execinfo.args[0] == "File does not exist."
280281
aedtapp.close_project()
281282

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
4+
# SPDX-License-Identifier: MIT
5+
#
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
25+
import os
26+
import tempfile
27+
import tkinter
28+
29+
import pytest
30+
31+
from ansys.aedt.core.extensions.circuit.import_schematic import ImportSchematicData
32+
from ansys.aedt.core.extensions.circuit.import_schematic import ImportSchematicExtension
33+
from ansys.aedt.core.extensions.circuit.import_schematic import main
34+
35+
36+
def create_temp_file(suffix, content="*"):
37+
fd, path = tempfile.mkstemp(suffix=suffix)
38+
with os.fdopen(fd, "w") as f:
39+
f.write(content)
40+
return path
41+
42+
43+
def test_import_schematic_nonexistent_file():
44+
"""Test import_schematic main with a non-existent file."""
45+
46+
data = ImportSchematicData(file_extension="/nonexistent/file.asc")
47+
with pytest.raises(FileNotFoundError):
48+
main(data)
49+
50+
51+
def test_import_schematic_generate_button_with_circuit(add_app):
52+
"""Test pressing the Import button and running main with a real Circuit instance."""
53+
54+
# Create a temp file to simulate user input
55+
path = create_temp_file(".asc")
56+
57+
# Insert the file path into the text widget as a user would
58+
extension = ImportSchematicExtension(withdraw=True)
59+
extension._text_widget.insert(tkinter.END, path)
60+
import_button = extension.root.nametowidget("!button2")
61+
import_button.invoke()
62+
data = extension.data
63+
assert isinstance(data, ImportSchematicData)
64+
assert data.file_extension == path
65+
# Now run the main logic with the data and the Circuit instance
66+
assert main(data) is True
67+
os.remove(path)

0 commit comments

Comments
 (0)