setupEM 0.5.1__tar.gz → 0.6.0__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 (26) hide show
  1. {setupem-0.5.1/src/setupEM.egg-info → setupem-0.6.0}/PKG-INFO +1 -1
  2. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/__init__.py +1 -1
  3. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/result_viewer.py +30 -5
  4. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/setupEM.py +203 -37
  5. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/setupThermal.py +4 -4
  6. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/setup_common.py +19 -4
  7. {setupem-0.5.1 → setupem-0.6.0/src/setupEM.egg-info}/PKG-INFO +1 -1
  8. {setupem-0.5.1 → setupem-0.6.0}/LICENSE +0 -0
  9. {setupem-0.5.1 → setupem-0.6.0}/README.md +0 -0
  10. {setupem-0.5.1 → setupem-0.6.0}/README_pypi.md +0 -0
  11. {setupem-0.5.1 → setupem-0.6.0}/pyproject.toml +0 -0
  12. {setupem-0.5.1 → setupem-0.6.0}/setup.cfg +0 -0
  13. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/__main__.py +0 -0
  14. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/data/SG13G2_100um.xml +0 -0
  15. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/data/SG13G2_200um.xml +0 -0
  16. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/data/SG13G2_200um_with_parameters.xml +0 -0
  17. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/data/SG13G2_nosub.xml +0 -0
  18. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/momentum_import.py +0 -0
  19. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/palace_results.py +0 -0
  20. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/stackupEditor.py +0 -0
  21. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM/stackup_writer.py +0 -0
  22. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM.egg-info/SOURCES.txt +0 -0
  23. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM.egg-info/dependency_links.txt +0 -0
  24. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM.egg-info/entry_points.txt +0 -0
  25. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM.egg-info/requires.txt +0 -0
  26. {setupem-0.5.1 → setupem-0.6.0}/src/setupEM.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: setupEM
3
- Version: 0.5.1
3
+ Version: 0.6.0
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
@@ -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.5.1"
27
+ __version__ = "0.6.0"
@@ -205,6 +205,32 @@ def find_touchstone_files(target_dir):
205
205
  return sorted(matches)
206
206
 
207
207
 
208
+ # AWS Palace adaptive mesh refinement writes one result snapshot per
209
+ # iteration<N>/ subfolder alongside the final, fully-refined result in the
210
+ # parent directory itself (see _ITERATION_RE in palace_results.py, reverse
211
+ # engineered from real Palace output) - same convention, applied per path
212
+ # component so it matches regardless of how deep target_dir's own scan went.
213
+ _AMR_ITERATION_DIR_RE = re.compile(r'^iteration\d+$')
214
+
215
+
216
+ def is_amr_iteration_snapshot(path):
217
+ """True if path sits inside an AMR "iterationN" output folder, i.e. it's
218
+ a per-iteration snapshot rather than the final result."""
219
+ parts = os.path.normpath(path).split(os.sep)
220
+ return any(_AMR_ITERATION_DIR_RE.match(part) for part in parts)
221
+
222
+
223
+ def pick_final_result_file(paths):
224
+ """Given a list of touchstone file paths, prefer the final result (any
225
+ path with no "iterationN" component) over AMR per-iteration snapshots;
226
+ break ties (or fall back, if every path is a snapshot) by newest mtime.
227
+ Returns None for an empty list."""
228
+ if not paths:
229
+ return None
230
+ final_candidates = [p for p in paths if not is_amr_iteration_snapshot(p)] or paths
231
+ return max(final_candidates, key=os.path.getmtime)
232
+
233
+
208
234
  # ------------------------------------------------------------------
209
235
  # Result Viewer window
210
236
  # ------------------------------------------------------------------
@@ -364,13 +390,12 @@ class ResultViewerWindow(QDialog):
364
390
  item.setFlags(Qt.NoItemFlags)
365
391
  self.file_list.addTopLevelItem(item)
366
392
  else:
367
- # drop checked paths that no longer exist; auto-check the newest
368
- # file if nothing is checked (e.g. first open), so the window
369
- # isn't blank
393
+ # drop checked paths that no longer exist; auto-check the final
394
+ # result (preferring it over any AMR per-iteration snapshot) if
395
+ # nothing is checked (e.g. first open), so the window isn't blank
370
396
  self._checked_paths &= set(self._master_files)
371
397
  if not self._checked_paths:
372
- newest = max(self._master_files, key=os.path.getmtime)
373
- self._checked_paths = {newest}
398
+ self._checked_paths = {pick_final_result_file(self._master_files)}
374
399
 
375
400
  # group by each file's immediate parent directory (relative to
376
401
  # target_dir), not a Palace/Elmer-specific convention like
@@ -644,6 +644,130 @@ class PortsTab(QWidget):
644
644
 
645
645
 
646
646
 
647
+ class RefinedCellsizeOverrideDialog(QDialog):
648
+ """Popup editor for settings['refined_cellsize_override'], opened from
649
+ MeshTab's "Advanced..." button. Lets the user set a different mesh
650
+ refinement cell size for specific metal/sheet layers, instead of the
651
+ single global "Mesh refinement at metal edges" value. The Layer column
652
+ is restricted to metals_list.getallplanarmetals() (conductor/sheet
653
+ layers) - vias and dielectrics never produce boundary curves in
654
+ gds2palace's meshing code, so a name outside that list would silently
655
+ have no effect if it were allowed to be typed in.
656
+ """
657
+
658
+ def __init__(self, parent, layer_choices, current_overrides):
659
+ super().__init__(parent)
660
+ self.setAttribute(Qt.WA_DeleteOnClose)
661
+ self.setWindowTitle("Mesh Refinement Overrides")
662
+ self.resize(420, 400)
663
+ self.setModal(True)
664
+
665
+ self._layer_choices = layer_choices
666
+ self._result = []
667
+
668
+ layout = QVBoxLayout()
669
+
670
+ info = QLabel("Override the mesh refinement cell size for specific layers\n"
671
+ "(all other layers keep using the default set above).")
672
+ layout.addWidget(info)
673
+
674
+ self.table = QTableWidget()
675
+ self.table.setColumnCount(3)
676
+ self.table.setHorizontalHeaderLabels(["Layer", "Cell size (µm)", ""])
677
+ self.table.verticalHeader().setVisible(False)
678
+ self.table.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
679
+ header = self.table.horizontalHeader()
680
+ header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
681
+ header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
682
+ header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
683
+ layout.addWidget(self.table)
684
+
685
+ for name, value in current_overrides:
686
+ self._add_row(name, value)
687
+
688
+ add_row_layout = QHBoxLayout()
689
+ add_btn = QPushButton("+ Add Row")
690
+ add_btn.clicked.connect(lambda: self._add_row())
691
+ add_row_layout.addWidget(add_btn)
692
+ add_row_layout.addStretch()
693
+ layout.addLayout(add_row_layout)
694
+
695
+ button_layout = QHBoxLayout()
696
+ button_layout.addStretch()
697
+ ok_btn = QPushButton("OK")
698
+ ok_btn.clicked.connect(self._on_ok)
699
+ button_layout.addWidget(ok_btn)
700
+ cancel_btn = QPushButton("Cancel")
701
+ cancel_btn.clicked.connect(self.reject)
702
+ button_layout.addWidget(cancel_btn)
703
+ layout.addLayout(button_layout)
704
+
705
+ self.setLayout(layout)
706
+
707
+ def _add_row(self, name="", value=""):
708
+ row = self.table.rowCount()
709
+ self.table.insertRow(row)
710
+
711
+ combo = QComboBox()
712
+ combo.setStyleSheet(COMBO_STYLE_OPTIONAL)
713
+ combo.addItem("")
714
+ combo.addItems(self._layer_choices)
715
+ if name and name not in self._layer_choices:
716
+ # tolerate a saved override for a layer no longer in the current
717
+ # stackup, same tolerant style as the Cellname combo box
718
+ combo.addItem(name)
719
+ if name:
720
+ combo.setCurrentText(name)
721
+ self.table.setCellWidget(row, 0, combo)
722
+
723
+ value_text = "" if value == "" else str(value)
724
+ self.table.setItem(row, 1, QTableWidgetItem(value_text))
725
+
726
+ remove_btn = QPushButton("✕")
727
+ remove_btn.setFixedWidth(28)
728
+ remove_btn.clicked.connect(lambda: self._remove_row_containing(remove_btn))
729
+ self.table.setCellWidget(row, 2, remove_btn)
730
+
731
+ def _remove_row_containing(self, button):
732
+ # look up the row by identity rather than a captured index, since
733
+ # row indices shift whenever an earlier row is removed
734
+ for row in range(self.table.rowCount()):
735
+ if self.table.cellWidget(row, 2) is button:
736
+ self.table.removeRow(row)
737
+ return
738
+
739
+ def _on_ok(self):
740
+ overrides = []
741
+ seen_layers = set()
742
+ for row in range(self.table.rowCount()):
743
+ combo = self.table.cellWidget(row, 0)
744
+ layer = combo.currentText().strip() if combo else ""
745
+ if not layer:
746
+ continue # blank row - not yet configured, skip silently
747
+
748
+ value_item = self.table.item(row, 1)
749
+ value_text = value_item.text().strip() if value_item else ""
750
+ try:
751
+ value = float(value_text)
752
+ if value <= 0:
753
+ raise ValueError
754
+ except ValueError:
755
+ QMessageBox.warning(self, "Error", f"Not a valid cell size for layer '{layer}'")
756
+ return
757
+
758
+ if layer in seen_layers:
759
+ QMessageBox.warning(self, "Error", f"Layer '{layer}' is selected more than once")
760
+ return
761
+ seen_layers.add(layer)
762
+ overrides.append([layer, value])
763
+
764
+ self._result = overrides
765
+ self.accept()
766
+
767
+ def get_overrides(self):
768
+ return self._result
769
+
770
+
647
771
  class MeshTab(QWidget):
648
772
  def __init__(self, MainWindow):
649
773
  super().__init__()
@@ -662,42 +786,44 @@ class MeshTab(QWidget):
662
786
  self.mesh_layout = QVBoxLayout()
663
787
 
664
788
  self.refinement_layout = QHBoxLayout()
665
- self.label2 = QLabel("Mesh refinement at metal edges")
789
+ self.label2 = QLabel("Mesh refinement at metal edges (µm)")
666
790
  self.label2.setFixedWidth(label_width)
667
791
  self.refinement_layout.addWidget(self.label2)
668
792
  self.refinement_edit = QLineEdit("5")
669
793
  self.refinement_edit.setFixedWidth(edit_width)
670
794
  self.refinement_edit.setStyleSheet(EDIT_STYLE_REQUIRED)
671
795
  self.refinement_layout.addWidget(self.refinement_edit)
672
- self.label3 = QLabel(" µm ")
673
- self.refinement_layout.addWidget(self.label3)
796
+ self.refined_override_btn = QPushButton("Advanced...")
797
+ self.refined_override_btn.clicked.connect(self.open_refined_cellsize_override_dialog)
798
+ self.refinement_layout.addWidget(self.refined_override_btn)
674
799
  self.refinement_layout.addStretch()
675
800
  self.mesh_layout.addLayout(self.refinement_layout)
676
801
 
802
+ # in-memory copy of settings['refined_cellsize_override'] (list of
803
+ # [layername, value] pairs), edited via the "Advanced..." dialog;
804
+ # harvested into saved_values by save_values() like every other field
805
+ self._refined_cellsize_override = []
806
+
677
807
  self.cells_lambda_layout = QHBoxLayout()
678
- self.label4 = QLabel("Mesh cells per wavelength")
808
+ self.label4 = QLabel("Mesh cells per wavelength (min 10)")
679
809
  self.label4.setFixedWidth(label_width)
680
810
  self.cells_lambda_layout.addWidget(self.label4)
681
811
  self.cells_lambda_edit = QLineEdit("10")
682
812
  self.cells_lambda_edit.setFixedWidth(edit_width)
683
813
  self.cells_lambda_edit.setStyleSheet(EDIT_STYLE_OPTIONAL)
684
814
  self.cells_lambda_layout.addWidget(self.cells_lambda_edit)
685
- self.label5 = QLabel(" (min 10)")
686
- self.cells_lambda_layout.addWidget(self.label5)
687
815
  self.cells_lambda_layout.addStretch()
688
816
  self.mesh_layout.addLayout(self.cells_lambda_layout)
689
817
 
690
818
 
691
819
  self.cells_maxsize_layout = QHBoxLayout()
692
- self.label6 = QLabel("Mesh cell maximum size absolute")
820
+ self.label6 = QLabel("Mesh cell maximum size absolute (µm)")
693
821
  self.label6.setFixedWidth(label_width)
694
822
  self.cells_maxsize_layout.addWidget(self.label6)
695
823
  self.cells_maxsize_edit = QLineEdit("100")
696
824
  self.cells_maxsize_edit.setFixedWidth(edit_width)
697
825
  self.cells_maxsize_edit.setStyleSheet(EDIT_STYLE_OPTIONAL)
698
826
  self.cells_maxsize_layout.addWidget(self.cells_maxsize_edit)
699
- self.label5 = QLabel(" µm ")
700
- self.cells_maxsize_layout.addWidget(self.label5)
701
827
  self.cells_maxsize_layout.addStretch()
702
828
  self.mesh_layout.addLayout(self.cells_maxsize_layout)
703
829
 
@@ -916,6 +1042,22 @@ class MeshTab(QWidget):
916
1042
  self.setLayout(self.main_layout)
917
1043
 
918
1044
 
1045
+ def _update_refined_override_button_label(self):
1046
+ n = len(self._refined_cellsize_override)
1047
+ self.refined_override_btn.setText(f"Advanced... ({n})" if n > 0 else "Advanced...")
1048
+
1049
+ def open_refined_cellsize_override_dialog(self):
1050
+ metals_list = self.MainWindow.metals_list
1051
+ if metals_list is None:
1052
+ QMessageBox.warning(self, "Error", "Load a GDSII file and XML stackup first")
1053
+ return
1054
+
1055
+ layer_choices = [metal.name for metal in metals_list.getallplanarmetals()]
1056
+ dialog = RefinedCellsizeOverrideDialog(self, layer_choices, self._refined_cellsize_override)
1057
+ if dialog.exec() == QDialog.Accepted:
1058
+ self._refined_cellsize_override = dialog.get_overrides()
1059
+ self._update_refined_override_button_label()
1060
+
919
1061
  def on_meshorder_changed(self, value):
920
1062
  # callback when mesh order changed, so that we can show/hide edit fields
921
1063
  try:
@@ -952,6 +1094,7 @@ class MeshTab(QWidget):
952
1094
  self.refinement_edit.setText("5")
953
1095
  return False
954
1096
  saved_values ["refined_cellsize"] = float(value)
1097
+ saved_values ["refined_cellsize_override"] = self._refined_cellsize_override
955
1098
 
956
1099
  saved_values ["order"] = self.mesh_order_box.currentIndex()+1
957
1100
 
@@ -1053,6 +1196,8 @@ class MeshTab(QWidget):
1053
1196
 
1054
1197
  def load_values(self):
1055
1198
  self.refinement_edit.setText(str(saved_values.get("refined_cellsize","5")))
1199
+ self._refined_cellsize_override = list(saved_values.get("refined_cellsize_override", []))
1200
+ self._update_refined_override_button_label()
1056
1201
  self.cells_lambda_edit.setText(str(saved_values.get("cells_per_wavelength","10")))
1057
1202
  self.cells_maxsize_edit.setText(str(saved_values.get("meshsize_max","100")))
1058
1203
  self.AMR_iterations_edit.setText(str(saved_values.get("adaptive_mesh_iterations","0")))
@@ -1080,17 +1225,23 @@ class MeshTab(QWidget):
1080
1225
  self.airaround_edit.setText(saved_values.get("margin","200"))
1081
1226
  self.airaround_box.setCurrentIndex(0)
1082
1227
  else:
1083
- if "," in str(air):
1084
- # we have a list of 6 values
1085
- air_as_list = air.split(',')
1086
- if len(air_as_list) == 6:
1087
- self.airaround_box.setCurrentIndex(1)
1088
- self.airxmin_edit.setText(air_as_list[0])
1089
- self.airxmax_edit.setText(air_as_list[1])
1090
- self.airymin_edit.setText(air_as_list[2])
1091
- self.airymax_edit.setText(air_as_list[3])
1092
- self.airzmin_edit.setText(air_as_list[4])
1093
- self.airzmax_edit.setText(air_as_list[5])
1228
+ # native JSON round-trip stores this as a real list of floats;
1229
+ # .py import stores it as a comma-separated string instead
1230
+ if isinstance(air, list):
1231
+ air_as_list = [str(v) for v in air]
1232
+ elif "," in str(air):
1233
+ air_as_list = [v.strip() for v in str(air).split(',')]
1234
+ else:
1235
+ air_as_list = None
1236
+
1237
+ if air_as_list and len(air_as_list) == 6:
1238
+ self.airaround_box.setCurrentIndex(1)
1239
+ self.airxmin_edit.setText(air_as_list[0])
1240
+ self.airxmax_edit.setText(air_as_list[1])
1241
+ self.airymin_edit.setText(air_as_list[2])
1242
+ self.airymax_edit.setText(air_as_list[3])
1243
+ self.airzmin_edit.setText(air_as_list[4])
1244
+ self.airzmax_edit.setText(air_as_list[5])
1094
1245
  else:
1095
1246
  # we have air_around defined as a single value
1096
1247
  self.airaround_box.setCurrentIndex(0)
@@ -1171,9 +1322,9 @@ class CreateModelTab(CreateModelTabBase):
1171
1322
  # local import: only needed once snp2le is actually launched, same lazy-import
1172
1323
  # style used by open_result_viewer() for its own heavy imports.
1173
1324
  if __package__ in (None, ""):
1174
- from result_viewer import find_touchstone_files
1325
+ from result_viewer import find_touchstone_files, is_amr_iteration_snapshot
1175
1326
  else:
1176
- from .result_viewer import find_touchstone_files
1327
+ from .result_viewer import find_touchstone_files, is_amr_iteration_snapshot
1177
1328
 
1178
1329
  if self.MainWindow.PalaceMode:
1179
1330
  run_path = saved_values['sim_path'] + "/palace_model/" + saved_values['model_basename'] + "_data"
@@ -1189,23 +1340,28 @@ class CreateModelTab(CreateModelTabBase):
1189
1340
  QMessageBox.warning(self, "Model Fit",
1190
1341
  f"No raw S-parameter result file found in {run_path}.\nRun a simulation first.")
1191
1342
  return
1192
- if len(raw_files) > 1:
1193
- self.log_area.appendPlainText("⚠️ Multiple raw S-parameter result files found, using the first one:")
1194
- for path in raw_files:
1343
+
1344
+ # adaptive mesh refinement leaves one snapshot touchstone file per
1345
+ # iteration<N>/ subfolder alongside the final, fully-refined result
1346
+ # directly in run_path - prefer that final result over the snapshots.
1347
+ final_candidates = [p for p in raw_files if not is_amr_iteration_snapshot(p)] or raw_files
1348
+ if len(final_candidates) > 1:
1349
+ self.log_area.appendPlainText("⚠️ Multiple raw S-parameter result files found, using the newest one:")
1350
+ for path in final_candidates:
1195
1351
  self.log_area.appendPlainText(" " + path)
1196
- raw_file = raw_files[0]
1352
+ raw_file = max(final_candidates, key=os.path.getmtime)
1197
1353
 
1198
- # snp2le's GUI mode takes no filename argument (only its "-b" batch mode does) -
1199
- # the file has to be loaded by hand via the GUI's own file picker, so point the
1200
- # user at it explicitly and start snp2le in that file's directory, so its file
1201
- # picker opens there instead of wherever setupEM happened to be launched from.
1354
+ # naming a .sNp file on the command line opens snp2le's GUI on it directly
1355
+ # (see https://github.com/iic-jku/snp2le/blob/main/doc/architecture.md);
1356
+ # still start it in that file's directory, so any relative paths it uses
1357
+ # (e.g. for exporting a fit) resolve there instead of wherever setupEM
1358
+ # happened to be launched from.
1202
1359
  raw_dir = os.path.dirname(raw_file)
1203
- self.log_area.appendPlainText("Starting snp2le ...")
1360
+ self.log_area.appendPlainText(f"Starting snp2le on {raw_file} ...")
1204
1361
  self.log_area.appendPlainText(SNP2LE_URL)
1205
- self.log_area.appendPlainText(f"In the snp2le window, load: {raw_file}")
1206
1362
  self._process_purpose = "model_fit"
1207
1363
  self.process.setWorkingDirectory(raw_dir)
1208
- self.process.start(sys.executable, ["-m", "snp2le"])
1364
+ self.process.start(sys.executable, ["-m", "snp2le", raw_file])
1209
1365
 
1210
1366
  def _append_results_summary(self):
1211
1367
  # Palace-only: parse palace.json / error-indicators.csv and append a results summary
@@ -1516,7 +1672,7 @@ class ModelEditorTab(QWidget):
1516
1672
 
1517
1673
  if forExport:
1518
1674
  # these commands are only used within this GUI application to control gmsh
1519
- ignore_list.append(['preview_only','no_preview'])
1675
+ ignore_list.extend(['preview_only','no_preview'])
1520
1676
 
1521
1677
  for key in saved_values.keys():
1522
1678
  if not key in special_keylist:
@@ -1802,20 +1958,30 @@ class MainWindow(MainWindowBase):
1802
1958
  def apply_native_config_data(self, data):
1803
1959
  # update ports, they are separate from the other internal data
1804
1960
  self.ports_tab.update_port_from_import(data.get("ports"))
1961
+ # restore simulator mode (not part of saved_values, see native_config_extra_struct)
1962
+ if data.get("elmer_mode", False):
1963
+ self.setElmerMode()
1964
+ else:
1965
+ self.setPalaceMode()
1805
1966
 
1806
1967
  def apply_python_import_data(self, file_path):
1807
1968
  # read port assignments in workflow syntax for gds2palace Python code
1808
1969
  ports = parse_python_ports_definitions(file_path)
1809
1970
  self.ports_tab.update_port_from_import(ports)
1810
1971
 
1811
- # set simulator
1812
- if self.saved_values.get("elmer", False):
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.
1976
+ with open(file_path) as f:
1977
+ text = f.read()
1978
+ if "create_elmer(" in text:
1813
1979
  self.setElmerMode()
1814
1980
  else:
1815
1981
  self.setPalaceMode()
1816
1982
 
1817
1983
  def native_config_extra_struct(self):
1818
- return {"ports": simulation_ports_to_struct(simulation_ports)}
1984
+ return {"ports": simulation_ports_to_struct(simulation_ports), "elmer_mode": self.ElmerMode}
1819
1985
 
1820
1986
  def update_target_layer_choices(self, metals_list):
1821
1987
  self.ports_tab.update_layers(metals_list)
@@ -486,7 +486,7 @@ class MeshTab(QWidget):
486
486
  self.label6 = QLabel("Mesh cell maximum size")
487
487
  self.label6.setFixedWidth(label_width)
488
488
  self.cells_maxsize_layout.addWidget(self.label6)
489
- self.cells_maxsize_edit = QLineEdit("10")
489
+ self.cells_maxsize_edit = QLineEdit("100")
490
490
  self.cells_maxsize_edit.setFixedWidth(edit_width)
491
491
  self.cells_maxsize_edit.setStyleSheet(EDIT_STYLE_OPTIONAL)
492
492
  self.cells_maxsize_layout.addWidget(self.cells_maxsize_edit)
@@ -507,7 +507,7 @@ class MeshTab(QWidget):
507
507
  self.label7 = QLabel("Dielectric stackup: oversize by")
508
508
  self.label7.setFixedWidth(label_width)
509
509
  self.margins_layout.addWidget(self.label7)
510
- self.margins_edit = QLineEdit("200")
510
+ self.margins_edit = QLineEdit("100")
511
511
  self.margins_edit.setFixedWidth(edit_width)
512
512
  self.margins_edit.setStyleSheet(EDIT_STYLE_REQUIRED)
513
513
  self.margins_layout.addWidget(self.margins_edit)
@@ -545,7 +545,7 @@ class MeshTab(QWidget):
545
545
  value = float(self.margins_edit.text())
546
546
  except Exception:
547
547
  QMessageBox.warning(self, "Error", "Not a valid value for dielectric oversize margin")
548
- self.margins_edit.setText("200")
548
+ self.margins_edit.setText("100")
549
549
  return False
550
550
  saved_values ["margin"] = float(value)
551
551
 
@@ -737,7 +737,7 @@ class ModelEditorTab(QWidget):
737
737
 
738
738
  if forExport:
739
739
  # these commands are only used within this GUI application to control gmsh
740
- ignore_list.append(['preview_only','no_preview'])
740
+ ignore_list.extend(['preview_only','no_preview'])
741
741
 
742
742
  for key in saved_values.keys():
743
743
  if not key in special_keylist:
@@ -324,7 +324,7 @@ class FileInputTab(QWidget):
324
324
  self.viamerge_layout = QHBoxLayout()
325
325
  self.viamerge_label1 = QLabel("Merge via arrays with spacing ")
326
326
  self.viamerge_label1.setFixedWidth(left_label_width)
327
- self.viamerge_edit = QLineEdit("0")
327
+ self.viamerge_edit = QLineEdit("0.5")
328
328
  self.viamerge_edit.setStyleSheet(EDIT_STYLE_OPTIONAL)
329
329
  self.viamerge_edit.setFixedWidth(70)
330
330
  self.viamerge_label2 = QLabel(" micron or more, value 0 disables via array merging")
@@ -643,7 +643,7 @@ class FileInputTab(QWidget):
643
643
  merge_polygon_size = float(self.viamerge_edit.text())
644
644
  except Exception:
645
645
  QMessageBox.warning(self, "Error", f"Not a valid value for via array merging")
646
- self.viamerge_edit.setText("0")
646
+ self.viamerge_edit.setText("0.5")
647
647
  return False
648
648
  saved_values["merge_polygon_size"] = float(merge_polygon_size)
649
649
 
@@ -1669,6 +1669,8 @@ class MainWindowBase(QMainWindow):
1669
1669
  "XML_filename": "SubstrateFile",
1670
1670
  "GdsFile": "GdsFile",
1671
1671
  "purpose": "purpose",
1672
+ "cellname": "cellname",
1673
+ "variable_overrides": "variable_overrides",
1672
1674
  "SubstrateFile": "SubstrateFile",
1673
1675
  "merge_polygon_size": "merge_polygon_size",
1674
1676
  "preprocess_gds": "preprocess_gds",
@@ -1681,12 +1683,12 @@ class MainWindowBase(QMainWindow):
1681
1683
  "fpoint": "fpoint",
1682
1684
  "fdump": "fdump",
1683
1685
  "refined_cellsize": "refined_cellsize",
1686
+ "refined_cellsize_override": "refined_cellsize_override",
1684
1687
  "cells_per_wavelength": "cells_per_wavelength",
1685
1688
  "meshsize_max": "meshsize_max",
1686
1689
  "adaptive_mesh_iterations": "adaptive_mesh_iterations",
1687
1690
  "order": "order",
1688
1691
  "iterative": "iterative",
1689
- "elmer": "elmer",
1690
1692
  "ELMER_MPI_THREADS": "ELMER_MPI_THREADS"
1691
1693
  }
1692
1694
 
@@ -1706,7 +1708,7 @@ class MainWindowBase(QMainWindow):
1706
1708
  if import_key not in import_value: # skip the section where key might appear in different context
1707
1709
  # get the internal name for this variable
1708
1710
  varname = import_mapping.get(import_key, '')
1709
- if varname == "fpoint" or varname == "fdump":
1711
+ if varname in ("fpoint", "fdump", "variable_overrides", "refined_cellsize_override"):
1710
1712
  saved_values[varname] = ast.literal_eval(import_value)
1711
1713
  elif varname in ["gds_filename", "XML_filename", "GdsFile", "SubstrateFile"]:
1712
1714
  # check if we have full path for files in imported Python script,
@@ -1727,6 +1729,19 @@ class MainWindowBase(QMainWindow):
1727
1729
  else:
1728
1730
  saved_values[varname] = raw
1729
1731
 
1732
+ # ask whether future "Create Model" output should overwrite this same
1733
+ # file, or start a fresh model (today's GDS-derived default)
1734
+ reuse = QMessageBox.question(
1735
+ self, "Import Model",
1736
+ f"Use '{os.path.basename(file_path)}' as the output file for this model too?\n\n"
1737
+ "Yes: Create Model / Start Simulation will overwrite this file.\n"
1738
+ "No: pick a model name and target directory on the Create Model(s) tab.",
1739
+ QMessageBox.Yes | QMessageBox.No, QMessageBox.No
1740
+ ) == QMessageBox.Yes
1741
+ if reuse:
1742
+ saved_values['sim_path'] = os.path.dirname(file_path).replace('\\', '/')
1743
+ saved_values['model_basename'] = pathlib.Path(file_path).stem
1744
+
1730
1745
  # read port/thermal assignments in workflow syntax for gds2palace Python code, and
1731
1746
  # apply any app-specific post-import state (e.g. setupEM's simulator mode)
1732
1747
  self.apply_python_import_data(file_path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: setupEM
3
- Version: 0.5.1
3
+ Version: 0.6.0
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes