setupEM 0.6.0__tar.gz → 0.6.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {setupem-0.6.0 → setupem-0.6.4}/PKG-INFO +17 -3
  2. {setupem-0.6.0 → setupem-0.6.4}/README.md +10 -0
  3. setupem-0.6.0/src/setupEM.egg-info/PKG-INFO → setupem-0.6.4/README_pypi.md +13 -20
  4. {setupem-0.6.0 → setupem-0.6.4}/pyproject.toml +4 -2
  5. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/__init__.py +1 -1
  6. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/palace_results.py +23 -0
  7. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/setupEM.py +187 -38
  8. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/setupThermal.py +87 -3
  9. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/setup_common.py +665 -296
  10. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/stackupEditor.py +112 -14
  11. setupem-0.6.4/src/setupEM/thermal_results.py +191 -0
  12. setupem-0.6.0/README_pypi.md → setupem-0.6.4/src/setupEM.egg-info/PKG-INFO +34 -1
  13. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM.egg-info/SOURCES.txt +1 -0
  14. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM.egg-info/requires.txt +3 -1
  15. {setupem-0.6.0 → setupem-0.6.4}/LICENSE +0 -0
  16. {setupem-0.6.0 → setupem-0.6.4}/setup.cfg +0 -0
  17. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/__main__.py +0 -0
  18. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/data/SG13G2_100um.xml +0 -0
  19. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/data/SG13G2_200um.xml +0 -0
  20. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/data/SG13G2_200um_with_parameters.xml +0 -0
  21. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/data/SG13G2_nosub.xml +0 -0
  22. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/momentum_import.py +0 -0
  23. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/result_viewer.py +0 -0
  24. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/stackup_writer.py +0 -0
  25. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM.egg-info/dependency_links.txt +0 -0
  26. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM.egg-info/entry_points.txt +0 -0
  27. {setupem-0.6.0 → setupem-0.6.4}/src/setupEM.egg-info/top_level.txt +0 -0
@@ -1,20 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: setupEM
3
- Version: 0.6.0
3
+ Version: 0.6.4
4
4
  Summary: Python tool for configuration of gds2palace workflow with GUI.
5
5
  Author-email: Volker Muehlhaus <volker@muehlhaus.com>
6
6
  License-Expression: GPL-3.0-or-later
7
7
  Requires-Python: >=3.9
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
- Requires-Dist: gds2palace>=0.4.0
10
+ Requires-Dist: gds2palace>=0.4.3
11
11
  Requires-Dist: PySide6
12
+ Requires-Dist: shiboken6
12
13
  Requires-Dist: scipy
13
14
  Requires-Dist: requests
14
15
  Requires-Dist: scikit-rf
15
16
  Requires-Dist: matplotlib
16
17
  Requires-Dist: numpy
17
18
  Requires-Dist: gdspy
19
+ Requires-Dist: meshio
18
20
  Dynamic: license-file
19
21
 
20
22
  # Python GUI for gds2palace
@@ -27,6 +29,7 @@ setupEM includes two built-in tools for working with simulation results directly
27
29
  - **Model Fit** (Create Model tab > Model Fit...) launches [snp2le](https://github.com/iic-jku/snp2le), an external open-source tool that extracts a lumped-element SPICE/Spectre netlist from S-parameter results - offering to install it via pip if it isn't already present. See section "[Model Fit](#model-fit)".
28
30
 
29
31
  - A graphical **Stackup XML Editor** (Tools > Edit Stackup XML...), including Variables, Reference-relative positioning, Derived Layers, and Thermal Tables
32
+ - The stackup preview graphics are **interactive**: click a shape for its properties, and selection syncs both ways with the Stackup Editor's tables
30
33
  - Input Files tab can now **override stackup Variables** (e.g. `total_thickness`) directly, without hand-editing the XML or the generated model
31
34
  - **setupThermal**, a companion app for Elmer thermal simulation, alongside setupEM
32
35
 
@@ -42,6 +45,11 @@ The setupEM package now includes setupThermal also, which is the equivalent of s
42
45
 
43
46
  An overview of the SetupEM user interface is given below in chapter "Using setupEM"
44
47
 
48
+ Two more external tools are used by parts of the workflow, and are not installed automatically:
49
+
50
+ - [ParaView](https://www.paraview.org/) — to view field-dump output (Palace/Elmer EM) and Elmer thermal result files via the "View fields/results in Paraview" buttons.
51
+ - An MPI implementation — only needed for multi-process Elmer runs (the Elmer solver settings' multithreading option). Use OpenMPI or MPICH on Linux/macOS; on Windows, install [Microsoft MPI](https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi) (setupEM checks for this and shows a download link if it's missing).
52
+
45
53
 
46
54
  ## Installing the AWS Palace FEM solver engine
47
55
  **setupEM** creates and runs simulation models for the AWS Palace FEM solver engine. The underlying solver **AWS Palace** can be installed in multiple ways. For a smooth interaction with the gds2palace workflow, it is recommended to create some scripts that help running the model and convert the Palace results to SnP Touchstone files.
@@ -101,10 +109,14 @@ sudo apt install libxcb-cursor0 libxcb-xinerama0 libxcb-xkb1 libxcb-icccm4 libxc
101
109
  The setupEM module also installs these dependencies:
102
110
  - gds2palace
103
111
  - PySide6
112
+ - shiboken6
104
113
  - scipy
105
114
  - requests
106
115
  - scikit-rf
107
116
  - matplotlib
117
+ - numpy
118
+ - gdspy
119
+ - meshio
108
120
 
109
121
  ---
110
122
 
@@ -321,13 +333,15 @@ This project is licensed under the GNU General Public License v3.0 or later (GPL
321
333
 
322
334
  **Note:** the `setupEM` PyPI package itself requires:
323
335
 
324
- - gds2palace>=0.4.0
336
+ - gds2palace>=0.4.3
325
337
  - PySide6
338
+ - shiboken6
326
339
  - scipy
327
340
  - requests
328
341
  - scikit-rf
329
342
  - matplotlib
330
343
  - numpy
331
344
  - gdspy
345
+ - meshio
332
346
 
333
347
  (Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
@@ -8,6 +8,7 @@ setupEM includes two built-in tools for working with simulation results directly
8
8
  - **Model Fit** (Create Model tab > Model Fit...) launches [snp2le](https://github.com/iic-jku/snp2le), an external open-source tool that extracts a lumped-element SPICE/Spectre netlist from S-parameter results - offering to install it via pip if it isn't already present. See section "[Model Fit](#model-fit)".
9
9
 
10
10
  - A graphical **Stackup XML Editor** (Tools > Edit Stackup XML...), including Variables, Reference-relative positioning, Derived Layers, and Thermal Tables
11
+ - The stackup preview graphics are **interactive**: click a shape for its properties, and selection syncs both ways with the Stackup Editor's tables
11
12
  - Input Files tab can now **override stackup Variables** (e.g. `total_thickness`) directly, without hand-editing the XML or the generated model
12
13
  - **setupThermal**, a companion app for Elmer thermal simulation, alongside setupEM
13
14
 
@@ -23,6 +24,11 @@ The setupEM package now includes setupThermal also, which is the equivalent of s
23
24
 
24
25
  An overview of the SetupEM user interface is given below in chapter "Using setupEM"
25
26
 
27
+ Two more external tools are used by parts of the workflow, and are not installed automatically:
28
+
29
+ - [ParaView](https://www.paraview.org/) — to view field-dump output (Palace/Elmer EM) and Elmer thermal result files via the "View fields/results in Paraview" buttons.
30
+ - An MPI implementation — only needed for multi-process Elmer runs (the Elmer solver settings' multithreading option). Use OpenMPI or MPICH on Linux/macOS; on Windows, install [Microsoft MPI](https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi) (setupEM checks for this and shows a download link if it's missing).
31
+
26
32
 
27
33
  ## Installing the AWS Palace FEM solver engine
28
34
  **setupEM** creates and runs simulation models for the AWS Palace FEM solver engine. The underlying solver **AWS Palace** can be installed in multiple ways. For a smooth interaction with the gds2palace workflow, it is recommended to create some scripts that help running the model and convert the Palace results to SnP Touchstone files.
@@ -82,10 +88,14 @@ sudo apt install libxcb-cursor0 libxcb-xinerama0 libxcb-xkb1 libxcb-icccm4 libxc
82
88
  The setupEM module also installs these dependencies:
83
89
  - gds2palace
84
90
  - PySide6
91
+ - shiboken6
85
92
  - scipy
86
93
  - requests
87
94
  - scikit-rf
88
95
  - matplotlib
96
+ - numpy
97
+ - gdspy
98
+ - meshio
89
99
 
90
100
  ---
91
101
 
@@ -1,22 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: setupEM
3
- Version: 0.6.0
4
- Summary: Python tool for configuration of gds2palace workflow with GUI.
5
- Author-email: Volker Muehlhaus <volker@muehlhaus.com>
6
- License-Expression: GPL-3.0-or-later
7
- Requires-Python: >=3.9
8
- Description-Content-Type: text/markdown
9
- License-File: LICENSE
10
- Requires-Dist: gds2palace>=0.4.0
11
- Requires-Dist: PySide6
12
- Requires-Dist: scipy
13
- Requires-Dist: requests
14
- Requires-Dist: scikit-rf
15
- Requires-Dist: matplotlib
16
- Requires-Dist: numpy
17
- Requires-Dist: gdspy
18
- Dynamic: license-file
19
-
20
1
  # Python GUI for gds2palace
21
2
 
22
3
  ## What's New
@@ -27,6 +8,7 @@ setupEM includes two built-in tools for working with simulation results directly
27
8
  - **Model Fit** (Create Model tab > Model Fit...) launches [snp2le](https://github.com/iic-jku/snp2le), an external open-source tool that extracts a lumped-element SPICE/Spectre netlist from S-parameter results - offering to install it via pip if it isn't already present. See section "[Model Fit](#model-fit)".
28
9
 
29
10
  - A graphical **Stackup XML Editor** (Tools > Edit Stackup XML...), including Variables, Reference-relative positioning, Derived Layers, and Thermal Tables
11
+ - The stackup preview graphics are **interactive**: click a shape for its properties, and selection syncs both ways with the Stackup Editor's tables
30
12
  - Input Files tab can now **override stackup Variables** (e.g. `total_thickness`) directly, without hand-editing the XML or the generated model
31
13
  - **setupThermal**, a companion app for Elmer thermal simulation, alongside setupEM
32
14
 
@@ -42,6 +24,11 @@ The setupEM package now includes setupThermal also, which is the equivalent of s
42
24
 
43
25
  An overview of the SetupEM user interface is given below in chapter "Using setupEM"
44
26
 
27
+ Two more external tools are used by parts of the workflow, and are not installed automatically:
28
+
29
+ - [ParaView](https://www.paraview.org/) — to view field-dump output (Palace/Elmer EM) and Elmer thermal result files via the "View fields/results in Paraview" buttons.
30
+ - An MPI implementation — only needed for multi-process Elmer runs (the Elmer solver settings' multithreading option). Use OpenMPI or MPICH on Linux/macOS; on Windows, install [Microsoft MPI](https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi) (setupEM checks for this and shows a download link if it's missing).
31
+
45
32
 
46
33
  ## Installing the AWS Palace FEM solver engine
47
34
  **setupEM** creates and runs simulation models for the AWS Palace FEM solver engine. The underlying solver **AWS Palace** can be installed in multiple ways. For a smooth interaction with the gds2palace workflow, it is recommended to create some scripts that help running the model and convert the Palace results to SnP Touchstone files.
@@ -101,10 +88,14 @@ sudo apt install libxcb-cursor0 libxcb-xinerama0 libxcb-xkb1 libxcb-icccm4 libxc
101
88
  The setupEM module also installs these dependencies:
102
89
  - gds2palace
103
90
  - PySide6
91
+ - shiboken6
104
92
  - scipy
105
93
  - requests
106
94
  - scikit-rf
107
95
  - matplotlib
96
+ - numpy
97
+ - gdspy
98
+ - meshio
108
99
 
109
100
  ---
110
101
 
@@ -321,13 +312,15 @@ This project is licensed under the GNU General Public License v3.0 or later (GPL
321
312
 
322
313
  **Note:** the `setupEM` PyPI package itself requires:
323
314
 
324
- - gds2palace>=0.4.0
315
+ - gds2palace>=0.4.3
325
316
  - PySide6
317
+ - shiboken6
326
318
  - scipy
327
319
  - requests
328
320
  - scikit-rf
329
321
  - matplotlib
330
322
  - numpy
331
323
  - gdspy
324
+ - meshio
332
325
 
333
326
  (Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
@@ -12,14 +12,16 @@ authors = [
12
12
  { name = "Volker Muehlhaus", email = "volker@muehlhaus.com" }
13
13
  ]
14
14
  dependencies = [
15
- "gds2palace>=0.4.0",
15
+ "gds2palace>=0.4.3",
16
16
  "PySide6",
17
+ "shiboken6",
17
18
  "scipy",
18
19
  "requests",
19
20
  "scikit-rf",
20
21
  "matplotlib",
21
22
  "numpy",
22
- "gdspy"
23
+ "gdspy",
24
+ "meshio"
23
25
  ]
24
26
  requires-python = ">=3.9"
25
27
 
@@ -24,4 +24,4 @@ A Python tool for EM setup using gds2palace.
24
24
  from .setupEM import main
25
25
 
26
26
  __all__ = ["main"]
27
- __version__ = "0.6.0"
27
+ __version__ = "0.6.4"
@@ -27,6 +27,7 @@ import csv
27
27
  import re
28
28
  import math
29
29
  import cmath
30
+ import glob
30
31
 
31
32
  _ITERATION_RE = re.compile(r'^iteration(\d+)$')
32
33
 
@@ -47,6 +48,28 @@ def find_output_dir(run_path, model_basename):
47
48
  return os.path.normpath(os.path.join(run_path, output_rel))
48
49
 
49
50
 
51
+ def find_paraview_files(run_path, model_basename):
52
+ """All ParaView collection files under this run's Palace output directory, found
53
+ recursively. Palace's own field-dump feature (triggered by fdump/SaveStep) writes
54
+ its category/excitation folder structure without gds2palace's control - confirmed
55
+ (on two Palace versions) to only keep the last-solved excitation's Cycle files, so
56
+ no fixed filename or count can be assumed here. Prefers .pvd, then falls back to
57
+ .pvtu (the multi-partition index - ties per-partition pieces back into one dataset),
58
+ then loose .vtu if neither exists yet. Empty list if the output directory doesn't
59
+ exist / has nothing yet.
60
+ """
61
+ output_dir = find_output_dir(run_path, model_basename)
62
+ if not os.path.isdir(output_dir):
63
+ return []
64
+ pvd_files = sorted(glob.glob(os.path.join(output_dir, '**', '*.pvd'), recursive=True))
65
+ if pvd_files:
66
+ return pvd_files
67
+ pvtu_files = sorted(glob.glob(os.path.join(output_dir, '**', '*.pvtu'), recursive=True))
68
+ if pvtu_files:
69
+ return pvtu_files
70
+ return sorted(glob.glob(os.path.join(output_dir, '**', '*.vtu'), recursive=True))
71
+
72
+
50
73
  def _read_palace_json(dir_path):
51
74
  path = os.path.join(dir_path, 'palace.json')
52
75
  if not os.path.isfile(path):
@@ -17,7 +17,7 @@
17
17
  ########################################################################
18
18
 
19
19
 
20
- import sys, json, os, pathlib, ast, webbrowser, argparse, shutil, re
20
+ import sys, json, os, pathlib, ast, webbrowser, argparse, shutil, re, glob
21
21
  import numpy as np
22
22
  import importlib.metadata
23
23
  import importlib.util
@@ -55,7 +55,7 @@ if __package__ in (None, ""):
55
55
  VectorWidget, PopUpWindow, CreateModelTabBase, MainWindowBase,
56
56
  epsilon_to_color, default_stackup_dielectric_label, default_stackup_metal_label,
57
57
  )
58
- from palace_results import build_results_summary, find_output_dir
58
+ from palace_results import build_results_summary, find_output_dir, find_paraview_files
59
59
  else:
60
60
  from .setup_common import (
61
61
  EDIT_STYLE_OPTIONAL, EDIT_STYLE_REQUIRED, COMBO_STYLE_REQUIRED, COMBO_STYLE_OPTIONAL,
@@ -63,7 +63,7 @@ else:
63
63
  VectorWidget, PopUpWindow, CreateModelTabBase, MainWindowBase,
64
64
  epsilon_to_color, default_stackup_dielectric_label, default_stackup_metal_label,
65
65
  )
66
- from .palace_results import build_results_summary, find_output_dir
66
+ from .palace_results import build_results_summary, find_output_dir, find_paraview_files
67
67
 
68
68
 
69
69
  '''
@@ -165,10 +165,21 @@ class FrequenciesTab(QWidget):
165
165
  self.dump_layout = QHBoxLayout()
166
166
 
167
167
  self.fdump_layout = QVBoxLayout()
168
- self.fdump_layout.addWidget(QLabel("fdump [GHz], values separated by comma"))
168
+ self.fdump_label = QLabel("fdump [GHz], values separated by comma")
169
+ self.fdump_layout.addWidget(self.fdump_label)
169
170
  self.fdump_edit = QLineEdit("")
170
171
  self.fdump_edit.setStyleSheet(EDIT_STYLE_OPTIONAL)
171
172
  self.fdump_layout.addWidget(self.fdump_edit)
173
+
174
+ # Elmer has no per-sample SaveStep like Palace - any fdump value turns on
175
+ # field-dump output at every solved frequency (sweep + fpoint together), so a
176
+ # per-frequency list is misleading there. Elmer mode shows this checkbox instead
177
+ # of fdump_edit; only one of the two is ever visible, toggled in setPalaceMode()/
178
+ # setElmerMode(). Both widgets always exist so mode switches are just a visibility
179
+ # flip, no rebuilding.
180
+ self.fdump_enabled_checkbox = QCheckBox("Enable field dump at all frequencies")
181
+ self.fdump_layout.addWidget(self.fdump_enabled_checkbox)
182
+
172
183
  self.dump_layout.addLayout(self.fdump_layout)
173
184
  self.dump_group.setLayout(self.dump_layout)
174
185
 
@@ -184,7 +195,9 @@ class FrequenciesTab(QWidget):
184
195
  def save_values(self):
185
196
  # fstart/fstop may be left empty only if fpoint or fdump supplies at least
186
197
  # one frequency instead (gds2palace treats fstart/fstop as fully optional)
187
- discrete_freqs_given = (self.fpoint_edit.text() != "" or self.fdump_edit.text() != "")
198
+ fdump_given = (self.fdump_enabled_checkbox.isChecked() if self.MainWindow.ElmerMode
199
+ else self.fdump_edit.text() != "")
200
+ discrete_freqs_given = (self.fpoint_edit.text() != "" or fdump_given)
188
201
 
189
202
  fstart = None
190
203
  if self.start_edit.text() == "":
@@ -239,16 +252,35 @@ class FrequenciesTab(QWidget):
239
252
  else:
240
253
  saved_values.pop("fpoint",None) # delete key
241
254
 
242
- text = self.fdump_edit.text()
243
- if text != "":
244
- saved_values ["fdump"] = ast.literal_eval('['+text+']')
255
+ if self.MainWindow.ElmerMode:
256
+ saved_values["fdump_enabled"] = self.fdump_enabled_checkbox.isChecked()
257
+ # don't touch saved_values["fdump"] here - it's Palace-only data (a real
258
+ # frequency list) and must survive a round-trip through Elmer mode untouched
259
+ have_sweep = ("fstart" in saved_values and "fstop" in saved_values)
260
+ have_fpoint = bool(saved_values.get("fpoint"))
261
+ if self.fdump_enabled_checkbox.isChecked() and not have_sweep and not have_fpoint:
262
+ QMessageBox.warning(self, "Error",
263
+ "Field dump needs at least one frequency: set fstart/fstop or fpoint first.")
264
+ self.fdump_enabled_checkbox.setChecked(False)
265
+ saved_values["fdump_enabled"] = False
266
+ return False
245
267
  else:
246
- saved_values.pop("fdump",None)
268
+ text = self.fdump_edit.text()
269
+ if text != "":
270
+ saved_values ["fdump"] = ast.literal_eval('['+text+']')
271
+ else:
272
+ saved_values.pop("fdump",None)
273
+ # don't touch saved_values["fdump_enabled"] here either, same reasoning
274
+ # "View fields in Paraview" is only relevant once fdump is set
275
+ self.MainWindow.create_model_tab._update_paraview_button_visibility()
247
276
 
248
277
  # if fstart == fstop == fdump or fstart == fstop == fstep, then remove fstart, fstop
249
278
  if fstart is not None and fstop is not None and fstart == fstop:
250
279
  discrete_list1 = saved_values.get("fpoint", [])
251
- discrete_list2 = saved_values.get("fdump", [])
280
+ # fdump no longer holds real per-frequency data in Elmer mode, and collapsing
281
+ # the sweep away here would remove the very fstop value the field-dump
282
+ # checkbox's generated code depends on (see ModelEditorTab.create_model_text)
283
+ discrete_list2 = [] if self.MainWindow.ElmerMode else saved_values.get("fdump", [])
252
284
  if fstart in discrete_list1 or fstart in discrete_list2:
253
285
  saved_values.pop("fstart", None)
254
286
  saved_values.pop("fstop", None)
@@ -263,7 +295,9 @@ class FrequenciesTab(QWidget):
263
295
  def load_values(self):
264
296
  # if fstart/fstop were intentionally omitted in favor of fpoint/fdump, keep the
265
297
  # fields blank on reload instead of repopulating the "0"/"50" sweep defaults
266
- discrete_freqs_given = ("fpoint" in saved_values) or ("fdump" in saved_values)
298
+ fdump_given = saved_values.get("fdump_enabled", False) if self.MainWindow.ElmerMode \
299
+ else ("fdump" in saved_values)
300
+ discrete_freqs_given = ("fpoint" in saved_values) or fdump_given
267
301
  fstart_default = "" if discrete_freqs_given else "0"
268
302
  fstop_default = "" if discrete_freqs_given else "50"
269
303
  self.start_edit.setText(str(saved_values.get("fstart",fstart_default)))
@@ -275,6 +309,7 @@ class FrequenciesTab(QWidget):
275
309
 
276
310
  float_list = saved_values.get("fdump","")
277
311
  self.fdump_edit.setText(','.join(map(str, float_list)))
312
+ self.fdump_enabled_checkbox.setChecked(bool(saved_values.get("fdump_enabled", False)))
278
313
 
279
314
 
280
315
  class PortsTab(QWidget):
@@ -1285,13 +1320,26 @@ class CreateModelTab(CreateModelTabBase):
1285
1320
  # up exactly with Preview/Create Mesh/Start Simulation above, in the same
1286
1321
  # Actions group.
1287
1322
  row = self.buttons_grid.rowCount()
1288
- self.view_results_btn = QPushButton("📈 View Results...")
1323
+ self.view_results_btn = QPushButton("📈 View S-Parameters...")
1289
1324
  self.view_results_btn.clicked.connect(self.MainWindow.open_result_viewer)
1290
1325
  self.buttons_grid.addWidget(self.view_results_btn, row, 0)
1291
1326
  self.model_fit_btn = QPushButton("🧩 Model Fit...")
1292
1327
  self.model_fit_btn.clicked.connect(self.open_model_fit)
1293
1328
  self.buttons_grid.addWidget(self.model_fit_btn, row, 1)
1294
1329
 
1330
+ # "View fields in Paraview" opens field-dump data (Palace fdump / Elmer EM
1331
+ # fields*.vtu), a separate row since it's independent of the S-parameter
1332
+ # viewer/model fit above. Only meaningful when fdump is set (otherwise there's
1333
+ # never any field data to open), so it's hidden rather than shown greyed-out -
1334
+ # kept in sync with saved_values['fdump'] via _update_paraview_button_visibility(),
1335
+ # called from here, from load_values() (project/model import), and from
1336
+ # FrequenciesTab.save_values() (live edits to the fdump field).
1337
+ row = self.buttons_grid.rowCount()
1338
+ self.paraview_btn = QPushButton("🖼️ View fields in Paraview...")
1339
+ self.paraview_btn.clicked.connect(self.launch_paraview)
1340
+ self.buttons_grid.addWidget(self.paraview_btn, row, 0)
1341
+ self._update_paraview_button_visibility()
1342
+
1295
1343
  def open_model_fit(self):
1296
1344
  SNP2LE_URL = "https://github.com/iic-jku/snp2le"
1297
1345
 
@@ -1370,6 +1418,47 @@ class CreateModelTab(CreateModelTabBase):
1370
1418
  summary = build_results_summary(run_path, saved_values['model_basename'])
1371
1419
  self.log_area.appendPlainText("\n" + summary + "\n")
1372
1420
 
1421
+ def _update_paraview_button_visibility(self):
1422
+ if self.MainWindow.ElmerMode:
1423
+ visible = bool(saved_values.get('fdump_enabled'))
1424
+ else:
1425
+ visible = bool(saved_values.get('fdump'))
1426
+ self.paraview_btn.setVisible(visible)
1427
+
1428
+ def load_values(self):
1429
+ super().load_values()
1430
+ self._update_paraview_button_visibility()
1431
+
1432
+ def launch_paraview(self):
1433
+ if self.MainWindow.PalaceMode:
1434
+ run_path = saved_values['sim_path'] + "/palace_model/" + saved_values['model_basename'] + "_data"
1435
+ file_paths = find_paraview_files(run_path, saved_values['model_basename'])
1436
+ not_found = (
1437
+ f"⚠️ No Palace field-dump output found under "
1438
+ f"{find_output_dir(run_path, saved_values['model_basename'])}\n"
1439
+ "(set fdump to specific frequencies before running the simulation)\n"
1440
+ )
1441
+ else:
1442
+ run_path = saved_values['sim_path'] + "/elmer_model/" + saved_values['model_basename'] + "_data"
1443
+ # Output File Name = File "fields" has no path prefix, so Elmer resolves it
1444
+ # relative to the Mesh DB directory ("mesh" under run_path) rather than
1445
+ # run_path itself - confirmed against a real run (same resolution mechanism
1446
+ # found for thermal_results.vtu). Check run_path too, defensively.
1447
+ search_dirs = [os.path.join(run_path, "mesh"), run_path]
1448
+ file_paths = []
1449
+ for pattern in ("fields*.pvd", "fields*.pvtu", "fields*.vtu"):
1450
+ for d in search_dirs:
1451
+ file_paths = sorted(glob.glob(os.path.join(d, pattern)))
1452
+ if file_paths:
1453
+ break
1454
+ if file_paths:
1455
+ break
1456
+ not_found = (
1457
+ f"⚠️ No Elmer field-dump output found under {run_path}\n"
1458
+ "(enable field dump before running the simulation)\n"
1459
+ )
1460
+ self._open_in_paraview(file_paths, not_found)
1461
+
1373
1462
  def on_finished(self, exit_code, exit_status):
1374
1463
  super().on_finished(exit_code, exit_status)
1375
1464
  # Auto-append the results summary after a real simulation run (not after mesh creation)
@@ -1431,9 +1520,13 @@ class CreateModelTab(CreateModelTabBase):
1431
1520
 
1432
1521
  Palace writes all of its results into one dedicated subdirectory (named in
1433
1522
  config.json's Problem.Output, resolved via find_output_dir()), so that whole
1434
- subtree is the deletion target. Elmer has no such subdirectory - its solver
1435
- writes scalar_results.*/fields*.vtu*/*.sNp files directly into run_path
1436
- alongside the mesh/config - so those are matched and removed individually.
1523
+ subtree is the deletion target. Elmer's SaveScalars/ResultOutputSolver write
1524
+ bare filenames (no path prefix) in their .sif config, so Elmer resolves them
1525
+ relative to the Mesh DB directory ("mesh" under run_path) rather than run_path
1526
+ itself - confirmed against real runs (same resolution mechanism found for
1527
+ thermal_results.vtu, and combine_snp.py itself expects "scalar_results" under
1528
+ a "mesh" parent and writes the resulting .sNp file there too). run_path itself
1529
+ is also checked, defensively, in case some variant writes there directly.
1437
1530
  """
1438
1531
  if self.MainWindow.PalaceMode:
1439
1532
  output_dir = find_output_dir(run_path, saved_values['model_basename'])
@@ -1442,12 +1535,14 @@ class CreateModelTab(CreateModelTabBase):
1442
1535
  targets = [output_dir]
1443
1536
  else:
1444
1537
  targets = []
1445
- if os.path.isdir(run_path):
1446
- for fn in os.listdir(run_path):
1447
- full_path = os.path.join(run_path, fn)
1538
+ for search_dir in (os.path.join(run_path, "mesh"), run_path):
1539
+ if not os.path.isdir(search_dir):
1540
+ continue
1541
+ for fn in os.listdir(search_dir):
1542
+ full_path = os.path.join(search_dir, fn)
1448
1543
  if not os.path.isfile(full_path):
1449
1544
  continue
1450
- if fn in ("scalar_results.dat", "scalar_results.names") or \
1545
+ if fn in ("scalar_results", "scalar_results.names") or \
1451
1546
  fn.startswith("fields") or re.search(r'\.s\d+p$', fn, re.IGNORECASE):
1452
1547
  targets.append(full_path)
1453
1548
  if not targets:
@@ -1546,17 +1641,27 @@ class CreateModelTab(CreateModelTabBase):
1546
1641
 
1547
1642
  self.log_area.appendPlainText('Setting work directory ' + run_path)
1548
1643
 
1549
- # rename file to batch file, but only if that has not already
1550
- # been done by a previous run (otherwise os.rename() fails on
1551
- # the second run because run_elmer no longer exists)
1552
- run_file_orig = os.path.join(run_path, 'run_elmer')
1553
- run_file = run_file_orig.replace('run_elmer','run_elmer.bat')
1554
- if os.path.exists(run_file_orig):
1555
- os.rename(run_file_orig, run_file)
1556
-
1644
+ # create_elmer_run_script() (util_utilities.py) writes run_elmer.bat
1645
+ # directly on Windows (proper .bat content - no "#!/bin/bash" shebang,
1646
+ # and MS-MPI's "mpiexec -n N" instead of the Linux-only "mpirun -np N"),
1647
+ # so no rename step is needed here any more.
1648
+ if saved_values.get('ELMER_MPI_THREADS', 1) > 1 and shutil.which("mpiexec") is None:
1649
+ self.log_area.appendPlainText(
1650
+ "⚠️ This model requests MPI multithreading, but 'mpiexec' was not "
1651
+ "found on PATH. On Windows, Elmer uses Microsoft MPI - download and "
1652
+ "install it from "
1653
+ "https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi "
1654
+ "and restart setupEM, or switch to 1 thread (no multithreading) on the "
1655
+ "Mesh and Boundaries tab.\n"
1656
+ )
1657
+ return
1557
1658
  self.process.setWorkingDirectory(run_path)
1558
- # start simulation
1559
- self.process.start("run_elmer.bat")
1659
+ # start simulation - full path, not just "run_elmer.bat": Windows'
1660
+ # CreateProcess resolves a bare relative program name against the
1661
+ # CALLING process's own cwd/PATH, not the child's setWorkingDirectory(),
1662
+ # so a bare filename here silently fails with FailedToStart even though
1663
+ # setWorkingDirectory() is set correctly.
1664
+ self.process.start(os.path.join(run_path, "run_elmer.bat"))
1560
1665
  else:
1561
1666
  # Linux
1562
1667
  self.log_area.appendPlainText('Setting work directory ' + run_path)
@@ -1663,13 +1768,22 @@ class ModelEditorTab(QWidget):
1663
1768
  special_keylist = ['simulation_ports','materials_list','dielectrics_list','metals_list',
1664
1769
  'layernumbers','allpolygons']
1665
1770
  # List of keys that we don't write to Python model code editor
1666
- ignore_list = ['model_basename','sim_path']
1771
+ # fdump_enabled is a GUI-only toggle (Elmer mode), never a real gds2palace setting
1772
+ ignore_list = ['model_basename','sim_path','fdump_enabled']
1667
1773
 
1668
1774
 
1669
1775
  # Keywords that are excluded in Palace mode
1670
1776
  if self.MainWindow.PalaceMode:
1671
1777
  ignore_list.append('iterative')
1672
1778
 
1779
+ if self.MainWindow.ElmerMode:
1780
+ # Elmer mode shows a plain on/off checkbox (fdump_enabled) instead of Palace's
1781
+ # per-frequency fdump list - suppress the generic per-key emission of fdump
1782
+ # here so a stale Palace-mode list saved earlier in the same session doesn't
1783
+ # leak into an Elmer-mode script; the synthesized settings['fdump'] line
1784
+ # (below, after special_keylist) is emitted instead when the checkbox is on.
1785
+ ignore_list.append('fdump')
1786
+
1673
1787
  if forExport:
1674
1788
  # these commands are only used within this GUI application to control gmsh
1675
1789
  ignore_list.extend(['preview_only','no_preview'])
@@ -1679,6 +1793,23 @@ class ModelEditorTab(QWidget):
1679
1793
  if not key in ignore_list:
1680
1794
  add_key(key)
1681
1795
 
1796
+ if self.MainWindow.ElmerMode and bool(saved_values.get('fdump_enabled')):
1797
+ # Elmer has no per-frequency SaveStep like Palace - it dumps fields at every
1798
+ # solved frequency once any fdump value is set, so reuse an already-solved
1799
+ # frequency here rather than asking the user to pick one. This is harmless
1800
+ # (adds no extra solve) as long as util_elmer.write_elmer_frequencies()
1801
+ # dedupes the combined frequency list, which it does.
1802
+ # Known limitation: this line's RHS references "settings", so
1803
+ # parse_assignments() (setup_common.py) skips it entirely on Import -
1804
+ # re-importing an exported Elmer+fdump script loses fdump_enabled (loads
1805
+ # unchecked). Not fixed here since it's a silent no-op, not a crash.
1806
+ add_text("\n# 'Enable field dump' checkbox: Elmer dumps fields at every solved")
1807
+ add_text("# frequency once fdump is non-empty, so reuse an already-solved one")
1808
+ if 'fstop' in saved_values:
1809
+ add_text("settings['fdump'] = [settings['fstop']]")
1810
+ elif saved_values.get('fpoint'):
1811
+ add_text("settings['fdump'] = [settings['fpoint'][0]]")
1812
+
1682
1813
 
1683
1814
  add_text("\n# ===================== port definitions =======================")
1684
1815
  add_text("simulation_ports = simulation_setup.all_simulation_ports()")
@@ -1883,6 +2014,10 @@ class MainWindow(MainWindowBase):
1883
2014
  self.ElmerMode = False
1884
2015
  self.setWindowTitle(APP_NAME + ' Palace')
1885
2016
  self.frequencies_tab.dump_group.setVisible(True)
2017
+ self.frequencies_tab.dump_group.setTitle("Optional list of fixed frequencies creating field dump data for visualization (Paraview files))")
2018
+ self.frequencies_tab.fdump_label.setVisible(True)
2019
+ self.frequencies_tab.fdump_edit.setVisible(True)
2020
+ self.frequencies_tab.fdump_enabled_checkbox.setVisible(False)
1886
2021
  self.mesh_tab.AMR_group.setVisible(True)
1887
2022
  self.mesh_tab.Elmer_group.setVisible(False)
1888
2023
 
@@ -1895,7 +2030,14 @@ class MainWindow(MainWindowBase):
1895
2030
  self.PalaceMode = False
1896
2031
  self.ElmerMode = True
1897
2032
  self.setWindowTitle(APP_NAME + ' Elmer')
1898
- self.frequencies_tab.dump_group.setVisible(False)
2033
+ # Elmer has no per-sample SaveStep like Palace - any fdump value turns on
2034
+ # field-dump output at every solved frequency, so it shows a plain on/off
2035
+ # checkbox here instead of Palace's per-frequency fdump list.
2036
+ self.frequencies_tab.dump_group.setVisible(True)
2037
+ self.frequencies_tab.dump_group.setTitle("Create field dump data for visualization (Paraview files)")
2038
+ self.frequencies_tab.fdump_label.setVisible(False)
2039
+ self.frequencies_tab.fdump_edit.setVisible(False)
2040
+ self.frequencies_tab.fdump_enabled_checkbox.setVisible(True)
1899
2041
  self.mesh_tab.AMR_group.setVisible(False)
1900
2042
  self.mesh_tab.Elmer_group.setVisible(True)
1901
2043
 
@@ -1925,8 +2067,8 @@ class MainWindow(MainWindowBase):
1925
2067
 
1926
2068
 
1927
2069
  def show_version(self):
1928
- setupEM_version = importlib.metadata.version("setupEM")
1929
- gds2palace_version = importlib.metadata.version("gds2palace")
2070
+ setupEM_version = self.get_setupEM_version()
2071
+ gds2palace_version = self.get_gds2palace_version()
1930
2072
  # snp2le (used by Model Fit) is an optional dependency, not installed by default
1931
2073
  try:
1932
2074
  snp2le_version = importlib.metadata.version("snp2le")
@@ -1969,13 +2111,20 @@ class MainWindow(MainWindowBase):
1969
2111
  ports = parse_python_ports_definitions(file_path)
1970
2112
  self.ports_tab.update_port_from_import(ports)
1971
2113
 
1972
- # Elmer/Palace mode is expressed only by which simulation_setup.create_*()
1973
- # call the script makes, never as a literal settings['elmer'] assignment
1974
- # (parse_assignments() skips lines whose value contains "settings"), so
1975
- # detect it directly from the source text instead.
2114
+ # Elmer/Palace mode isn't captured by the general settings-dict import
2115
+ # (parse_assignments() skips lines whose value contains "settings", and
2116
+ # 'elmer' isn't in import_mapping anyway), so detect it directly from the
2117
+ # source text instead. Two valid ways a script selects Elmer mode:
2118
+ # - calling simulation_setup.create_elmer(...) (what create_model_text()
2119
+ # itself generates, with a space before "(" - a plain "create_elmer("
2120
+ # substring check never matches that)
2121
+ # - setting settings['elmer'] = True directly and calling create_model()
2122
+ # itself (create_elmer() is only a thin wrapper that sets this same flag)
1976
2123
  with open(file_path) as f:
1977
2124
  text = f.read()
1978
- if "create_elmer(" in text:
2125
+ elmer_call = re.search(r'create_elmer\s*\(', text)
2126
+ elmer_flag = re.search(r'settings\s*\[\s*[\'"]elmer[\'"]\s*\]\s*=\s*True\b', text, re.IGNORECASE)
2127
+ if elmer_call or elmer_flag:
1979
2128
  self.setElmerMode()
1980
2129
  else:
1981
2130
  self.setPalaceMode()