setupEM 0.14.2__tar.gz → 0.16.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.
- setupem-0.16.0/PKG-INFO +97 -0
- {setupem-0.14.2 → setupem-0.16.0}/README.md +4 -2
- setupem-0.16.0/README_pypi.md +68 -0
- {setupem-0.14.2 → setupem-0.16.0}/pyproject.toml +4 -1
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/__init__.py +1 -1
- setupem-0.16.0/src/setupEM/config_update.py +179 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/result_viewer.py +8 -3
- setupem-0.16.0/src/setupEM/run_with_overrides.py +287 -0
- setupem-0.16.0/src/setupEM/script_model.py +1054 -0
- setupem-0.16.0/src/setupEM/script_probe.py +275 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/setupEM.py +210 -70
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/setupThermal.py +97 -44
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/setup_common.py +1348 -193
- setupem-0.16.0/src/setupEM.egg-info/PKG-INFO +97 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM.egg-info/SOURCES.txt +10 -1
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM.egg-info/requires.txt +4 -1
- setupem-0.16.0/tests/test_config_update.py +83 -0
- setupem-0.16.0/tests/test_config_update_gui.py +169 -0
- setupem-0.16.0/tests/test_run_with_overrides.py +147 -0
- setupem-0.16.0/tests/test_script_model.py +535 -0
- setupem-0.16.0/tests/test_script_probe.py +164 -0
- setupem-0.14.2/PKG-INFO +0 -75
- setupem-0.14.2/README_pypi.md +0 -48
- setupem-0.14.2/src/setupEM.egg-info/PKG-INFO +0 -75
- {setupem-0.14.2 → setupem-0.16.0}/LICENSE +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/setup.cfg +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/__main__.py +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/data/SG13CMOS5L_200um.xml +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/data/SG13G2_100um.xml +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/data/SG13G2_200um.xml +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/data/SG13G2_FEM_200um.xml +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/data/SG13G2_FEM_200um_passi3D.xml +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/data/SG13G2_nosub.xml +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/field_viewer.py +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/gds_hierarchy_scan.py +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/layout_preview.py +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/momentum_import.py +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/palace_results.py +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/simplify_gds.py +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/stackupEditor.py +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/stackup_writer.py +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM/thermal_results.py +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM.egg-info/dependency_links.txt +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM.egg-info/entry_points.txt +0 -0
- {setupem-0.14.2 → setupem-0.16.0}/src/setupEM.egg-info/top_level.txt +0 -0
setupem-0.16.0/PKG-INFO
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: setupEM
|
|
3
|
+
Version: 0.16.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
|
+
Project-URL: Homepage, https://github.com/VolkerMuehlhaus/setupEM
|
|
8
|
+
Project-URL: Repository, https://github.com/VolkerMuehlhaus/setupEM
|
|
9
|
+
Project-URL: Changelog, https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: gds2palace>=0.9.3
|
|
14
|
+
Requires-Dist: gds_prepare_for_EM>=1.6.0
|
|
15
|
+
Requires-Dist: PySide6
|
|
16
|
+
Requires-Dist: shiboken6
|
|
17
|
+
Requires-Dist: scipy
|
|
18
|
+
Requires-Dist: requests
|
|
19
|
+
Requires-Dist: scikit-rf
|
|
20
|
+
Requires-Dist: matplotlib
|
|
21
|
+
Requires-Dist: numpy
|
|
22
|
+
Requires-Dist: gdspy
|
|
23
|
+
Requires-Dist: meshio
|
|
24
|
+
Requires-Dist: pyvista
|
|
25
|
+
Requires-Dist: pyvistaqt
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest; extra == "test"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Python GUI for gds2palace
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
pip install setupEM
|
|
37
|
+
|
|
38
|
+
**Full documentation, installation guide, and usage walkthrough:**
|
|
39
|
+
https://github.com/VolkerMuehlhaus/setupEM
|
|
40
|
+
|
|
41
|
+
## Recent changes
|
|
42
|
+
|
|
43
|
+
# What's New - October 31, 2026 (setupEM 0.16.0)
|
|
44
|
+
|
|
45
|
+
**After a new export from the layout tool** (setupEM): a layout tool such as the KLayout plug-in klayout_em_export writes the GDSII file and a `.simcfg`, and writes both again when the layout changes. The settings you made in setupEM since are only in your model script, because Create Model writes the script, not the `.simcfg`. setupEM now takes from such a `.simcfg` only the ports, GDSII file, stackup, cell and purpose, and keeps everything else as in the script:
|
|
46
|
+
- Opening a `.simcfg` when a model script for the same GDSII file exists next to it asks whether to **Continue with the script** (edit it in place, ports and layout settings from the `.simcfg`) or **Start from the config**.
|
|
47
|
+
- Loading a `.simcfg` while editing a script in place lists the ports and layout settings that differ and asks whether to **Update the script from it** or **Open it as a new model**. Before, loading it ended editing in place and replaced all settings.
|
|
48
|
+
- While editing a script in place, the bar below the menu points out a newer `.simcfg` of the same GDSII file with other ports or layout settings, with **Update from it**.
|
|
49
|
+
|
|
50
|
+
**Load Config, Import from \*.py model and Edit \*.py model in place** start in the folder of the model you are working on (the config or script loaded last, else the GDSII file). Before, they started in the folder setupEM was launched from, e.g. KLayout's when the KLayout plug-in opened setupEM.
|
|
51
|
+
|
|
52
|
+
setupEM 0.16.0 needs gds2palace 0.9.3 or later: a layout with several shapes on one port layer (e.g. a transistor terminal on several fingers, from the KLayout plug-in) makes one port with several elements there; older gds2palace versions used only the last shape.
|
|
53
|
+
|
|
54
|
+
**Fixes:**
|
|
55
|
+
- `scripts/combine_extend_snp.py` is the same as gds2palace's again. It had missed three fixes made there in September: the port impedance lookup for AMR iterations, the missing `.sNp` extension for a model name that contains a dot, and Touchstone output after an interrupted Palace run.
|
|
56
|
+
- On Linux with GNOME (Wayland), the mouse cursor no longer keeps spinning for about 10 seconds after setupEM starts. setupEM briefly opened three invisible windows while building the Input Files tab, and GNOME waited for them to finish starting.
|
|
57
|
+
- Via ports with direction `-Z` keep their from and to layers. Before, loading a `.simcfg` dropped them, and Create Model wrote such a port as an in-plane port.
|
|
58
|
+
- An incomplete port (direction and layers that don't fit, e.g. from a `.simcfg` written by another tool) gives a warning in the Ports tab. Before, gds2palace exited and setupEM closed. A lowercase `z` counts as a via port too.
|
|
59
|
+
- `combine_extend_snp` (Touchstone output after a Palace run) handles ports with several elements (several shapes on one port layer, gds2palace 0.9.3): one Z0 per port in the option line, and the elements' port inductances combined in parallel for de-embedding. Before, the last element overwrote the others.
|
|
60
|
+
|
|
61
|
+
# What's New - October 29, 2026
|
|
62
|
+
|
|
63
|
+
**File > Edit \*.py model in place** (new, setupEM and setupThermal): edits a gds2palace model script directly, e.g. one from the gds2palace MCP server, an example or your own code. Create Model and **File > Save** write only the values you change; the script's loops, comments and custom code stay. Settings the script computes itself are greyed out. A script that builds several models (a parameter sweep) lists them on the Create Model tab, and Start Simulation runs the ticked ones. After importing a script that setupEM didn't write, a bar below the menu offers **Edit in place instead**.
|
|
64
|
+
|
|
65
|
+
**File > Import from \*.py model** now runs a gds2palace script to read it, without reading the GDSII file and without meshing. The run stops where the script builds the model, so its solver start never runs. The import now gets exactly what gds2palace would get, also values and ports the script computes. Before, ports made in a loop (as in many gds2palace MCP scripts) were left out, and the imported model was missing them. The script's own code before building the model does run, e.g. a script that generates its GDSII file writes it. If the run fails, the import reads the script text as before, and the bar below the menu says why.
|
|
66
|
+
|
|
67
|
+
**File menu:** the three Recent submenus are now one **Open Recent** submenu, with sections for configs, imported \*.py models and \*.py models edited in place. A file reopens the way it was opened. The menu is grouped as open, recent, save, preferences. **Load Default Config** and **Save as Default Config** are removed: defaults for new models are set in **File > Preferences**. An existing `default.simcfg` / `default.tsimcfg` in your home directory still opens with **Load Config**, and **Save Config** can write it.
|
|
68
|
+
|
|
69
|
+
Import and edit in place now read a stackup given as `utilities.get_stackup_path("SG13G2_FEM_200um.xml")`, the way the gds2palace MCP server writes it (needs gds2palace 0.9.1 or later). Before, an imported model using that syntax had no stackup file.
|
|
70
|
+
|
|
71
|
+
# What's New - October 21, 2026
|
|
72
|
+
|
|
73
|
+
**Tools > Simplify GDS:** the simplification code moved to gds_prepare_for_EM (module `gds_simplify_for_em`), where the gds2palace MCP server uses it too. setupEM now requires gds_prepare_for_EM 1.6.0 or later; `pip install --upgrade setupEM` updates it.
|
|
74
|
+
|
|
75
|
+
Fix: with **Merge via arrays** (and no fill removal or merging per layer), the simplified file still contained the original subcells as extra top-level cells. Without a cell name, gds2palace reads the first top-level cell, which could be one of them, e.g. a pad cell instead of the design. These left-over cells are now removed.
|
|
76
|
+
|
|
77
|
+
Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
**Note:** the `setupEM` PyPI package itself requires:
|
|
82
|
+
|
|
83
|
+
- gds2palace>=0.9.3
|
|
84
|
+
- gds_prepare_for_EM>=1.6.0
|
|
85
|
+
- PySide6
|
|
86
|
+
- shiboken6
|
|
87
|
+
- scipy
|
|
88
|
+
- requests
|
|
89
|
+
- scikit-rf
|
|
90
|
+
- matplotlib
|
|
91
|
+
- numpy
|
|
92
|
+
- gdspy
|
|
93
|
+
- meshio
|
|
94
|
+
- pyvista
|
|
95
|
+
- pyvistaqt
|
|
96
|
+
|
|
97
|
+
(Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
|
|
@@ -269,9 +269,11 @@ Behind the scenes, the setupEM user interface created Python model code for gds2
|
|
|
269
269
|
<img src="./doc/png/code1.png" alt="code" width="700">
|
|
270
270
|
|
|
271
271
|
## File menu
|
|
272
|
-
In the setupEM File menu, you can save and load simulation configurations
|
|
272
|
+
In the setupEM File menu, you can save and load simulation configurations. This includes the choice of simulation target directory and all other settings. Configurations are stored in a JSON file with file extension ".simcfg". Default values for new models are set in File > Preferences.
|
|
273
273
|
|
|
274
|
-
Using "File > Import from *.py model", you can load settings from existing simulation model code, e.g. the examples included in the gds2palace repository.
|
|
274
|
+
Using "File > Import from *.py model", you can load settings from existing simulation model code, e.g. the examples included in the gds2palace repository. A gds2palace script is run to read it, without reading the GDSII file and without meshing, so also values and ports the script computes are imported. If that fails, and for openEMS Python models, the import detects known keywords in the script text, with or without the settings[] syntax. Note that openEMS substrates model the MIM differently, and parameter "refined_cellsize" will usually be smaller in openEMS simulation, so you need to adjust these settings.
|
|
275
|
+
|
|
276
|
+
Using "File > Edit *.py model in place", setupEM edits a gds2palace model script directly, e.g. one created by the gds2palace MCP server or written by hand: it writes only the values you change back into the script, its loops, comments and custom code stay.
|
|
275
277
|
|
|
276
278
|
If you are on the "Code" tab, you can also export the Python model code using "File > Export to *.py model". This option is only required if you want to save the model code **without** running it. Buttons "Create mesh and model file" and "Run Palace" on the "Create Model" tab will also save the model code to the target directory, and run it from there.
|
|
277
279
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Python GUI for gds2palace
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
pip install setupEM
|
|
8
|
+
|
|
9
|
+
**Full documentation, installation guide, and usage walkthrough:**
|
|
10
|
+
https://github.com/VolkerMuehlhaus/setupEM
|
|
11
|
+
|
|
12
|
+
## Recent changes
|
|
13
|
+
|
|
14
|
+
# What's New - October 31, 2026 (setupEM 0.16.0)
|
|
15
|
+
|
|
16
|
+
**After a new export from the layout tool** (setupEM): a layout tool such as the KLayout plug-in klayout_em_export writes the GDSII file and a `.simcfg`, and writes both again when the layout changes. The settings you made in setupEM since are only in your model script, because Create Model writes the script, not the `.simcfg`. setupEM now takes from such a `.simcfg` only the ports, GDSII file, stackup, cell and purpose, and keeps everything else as in the script:
|
|
17
|
+
- Opening a `.simcfg` when a model script for the same GDSII file exists next to it asks whether to **Continue with the script** (edit it in place, ports and layout settings from the `.simcfg`) or **Start from the config**.
|
|
18
|
+
- Loading a `.simcfg` while editing a script in place lists the ports and layout settings that differ and asks whether to **Update the script from it** or **Open it as a new model**. Before, loading it ended editing in place and replaced all settings.
|
|
19
|
+
- While editing a script in place, the bar below the menu points out a newer `.simcfg` of the same GDSII file with other ports or layout settings, with **Update from it**.
|
|
20
|
+
|
|
21
|
+
**Load Config, Import from \*.py model and Edit \*.py model in place** start in the folder of the model you are working on (the config or script loaded last, else the GDSII file). Before, they started in the folder setupEM was launched from, e.g. KLayout's when the KLayout plug-in opened setupEM.
|
|
22
|
+
|
|
23
|
+
setupEM 0.16.0 needs gds2palace 0.9.3 or later: a layout with several shapes on one port layer (e.g. a transistor terminal on several fingers, from the KLayout plug-in) makes one port with several elements there; older gds2palace versions used only the last shape.
|
|
24
|
+
|
|
25
|
+
**Fixes:**
|
|
26
|
+
- `scripts/combine_extend_snp.py` is the same as gds2palace's again. It had missed three fixes made there in September: the port impedance lookup for AMR iterations, the missing `.sNp` extension for a model name that contains a dot, and Touchstone output after an interrupted Palace run.
|
|
27
|
+
- On Linux with GNOME (Wayland), the mouse cursor no longer keeps spinning for about 10 seconds after setupEM starts. setupEM briefly opened three invisible windows while building the Input Files tab, and GNOME waited for them to finish starting.
|
|
28
|
+
- Via ports with direction `-Z` keep their from and to layers. Before, loading a `.simcfg` dropped them, and Create Model wrote such a port as an in-plane port.
|
|
29
|
+
- An incomplete port (direction and layers that don't fit, e.g. from a `.simcfg` written by another tool) gives a warning in the Ports tab. Before, gds2palace exited and setupEM closed. A lowercase `z` counts as a via port too.
|
|
30
|
+
- `combine_extend_snp` (Touchstone output after a Palace run) handles ports with several elements (several shapes on one port layer, gds2palace 0.9.3): one Z0 per port in the option line, and the elements' port inductances combined in parallel for de-embedding. Before, the last element overwrote the others.
|
|
31
|
+
|
|
32
|
+
# What's New - October 29, 2026
|
|
33
|
+
|
|
34
|
+
**File > Edit \*.py model in place** (new, setupEM and setupThermal): edits a gds2palace model script directly, e.g. one from the gds2palace MCP server, an example or your own code. Create Model and **File > Save** write only the values you change; the script's loops, comments and custom code stay. Settings the script computes itself are greyed out. A script that builds several models (a parameter sweep) lists them on the Create Model tab, and Start Simulation runs the ticked ones. After importing a script that setupEM didn't write, a bar below the menu offers **Edit in place instead**.
|
|
35
|
+
|
|
36
|
+
**File > Import from \*.py model** now runs a gds2palace script to read it, without reading the GDSII file and without meshing. The run stops where the script builds the model, so its solver start never runs. The import now gets exactly what gds2palace would get, also values and ports the script computes. Before, ports made in a loop (as in many gds2palace MCP scripts) were left out, and the imported model was missing them. The script's own code before building the model does run, e.g. a script that generates its GDSII file writes it. If the run fails, the import reads the script text as before, and the bar below the menu says why.
|
|
37
|
+
|
|
38
|
+
**File menu:** the three Recent submenus are now one **Open Recent** submenu, with sections for configs, imported \*.py models and \*.py models edited in place. A file reopens the way it was opened. The menu is grouped as open, recent, save, preferences. **Load Default Config** and **Save as Default Config** are removed: defaults for new models are set in **File > Preferences**. An existing `default.simcfg` / `default.tsimcfg` in your home directory still opens with **Load Config**, and **Save Config** can write it.
|
|
39
|
+
|
|
40
|
+
Import and edit in place now read a stackup given as `utilities.get_stackup_path("SG13G2_FEM_200um.xml")`, the way the gds2palace MCP server writes it (needs gds2palace 0.9.1 or later). Before, an imported model using that syntax had no stackup file.
|
|
41
|
+
|
|
42
|
+
# What's New - October 21, 2026
|
|
43
|
+
|
|
44
|
+
**Tools > Simplify GDS:** the simplification code moved to gds_prepare_for_EM (module `gds_simplify_for_em`), where the gds2palace MCP server uses it too. setupEM now requires gds_prepare_for_EM 1.6.0 or later; `pip install --upgrade setupEM` updates it.
|
|
45
|
+
|
|
46
|
+
Fix: with **Merge via arrays** (and no fill removal or merging per layer), the simplified file still contained the original subcells as extra top-level cells. Without a cell name, gds2palace reads the first top-level cell, which could be one of them, e.g. a pad cell instead of the design. These left-over cells are now removed.
|
|
47
|
+
|
|
48
|
+
Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
**Note:** the `setupEM` PyPI package itself requires:
|
|
53
|
+
|
|
54
|
+
- gds2palace>=0.9.3
|
|
55
|
+
- gds_prepare_for_EM>=1.6.0
|
|
56
|
+
- PySide6
|
|
57
|
+
- shiboken6
|
|
58
|
+
- scipy
|
|
59
|
+
- requests
|
|
60
|
+
- scikit-rf
|
|
61
|
+
- matplotlib
|
|
62
|
+
- numpy
|
|
63
|
+
- gdspy
|
|
64
|
+
- meshio
|
|
65
|
+
- pyvista
|
|
66
|
+
- pyvistaqt
|
|
67
|
+
|
|
68
|
+
(Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
|
|
@@ -12,7 +12,7 @@ authors = [
|
|
|
12
12
|
{ name = "Volker Muehlhaus", email = "volker@muehlhaus.com" }
|
|
13
13
|
]
|
|
14
14
|
dependencies = [
|
|
15
|
-
"gds2palace>=0.9.
|
|
15
|
+
"gds2palace>=0.9.3",
|
|
16
16
|
"gds_prepare_for_EM>=1.6.0",
|
|
17
17
|
"PySide6",
|
|
18
18
|
"shiboken6",
|
|
@@ -28,6 +28,9 @@ dependencies = [
|
|
|
28
28
|
]
|
|
29
29
|
requires-python = ">=3.9"
|
|
30
30
|
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
test = ["pytest"]
|
|
33
|
+
|
|
31
34
|
[project.urls]
|
|
32
35
|
Homepage = "https://github.com/VolkerMuehlhaus/setupEM"
|
|
33
36
|
Repository = "https://github.com/VolkerMuehlhaus/setupEM"
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
########################################################################
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 Volker Muehlhaus and IHP PDK Authors
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the GNU General Public License, Version 3.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://www.gnu.org/licenses/gpl-3.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
########################################################################
|
|
18
|
+
|
|
19
|
+
"""Update a script edited in place from a settings file (*.simcfg) of the same layout.
|
|
20
|
+
|
|
21
|
+
A layout tool such as the KLayout plug-in klayout_em_export writes the GDS file and a *.simcfg next to it. When the
|
|
22
|
+
layout changes, it exports again: same GDS file name, new ports (numbered 1..N without gaps, so they can shift).
|
|
23
|
+
A model script made before, maybe edited by hand, still has the old ports. setupEM's edit-in-place mode can update
|
|
24
|
+
it: the settings that belong to the layout (LAYOUT_KEYS and the ports) come from the *.simcfg, everything else
|
|
25
|
+
(frequencies, mesh, the script's own code) stays.
|
|
26
|
+
|
|
27
|
+
No Qt in here, so the module can be tested on its own.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import ast
|
|
31
|
+
import glob
|
|
32
|
+
import json
|
|
33
|
+
import os
|
|
34
|
+
|
|
35
|
+
__all__ = ["LAYOUT_KEYS", "port_differences", "layout_differences", "newer_configs", "model_scripts"]
|
|
36
|
+
|
|
37
|
+
# saved_values that belong to the layout export, besides the ports
|
|
38
|
+
LAYOUT_KEYS = ("GdsFile", "SubstrateFile", "cellname", "purpose")
|
|
39
|
+
LAYOUT_LABELS = {"GdsFile": "GDSII file", "SubstrateFile": "stackup", "cellname": "cell", "purpose": "purpose"}
|
|
40
|
+
|
|
41
|
+
_PORT_KEYS = ("source_layernum", "direction", "from_layername", "to_layername", "target_layername")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _port_signature(port):
|
|
45
|
+
"""(source layer, direction, from, to, target) of a port dict; Z0 and excitation are not part of it."""
|
|
46
|
+
values = []
|
|
47
|
+
for key in _PORT_KEYS:
|
|
48
|
+
value = port.get(key)
|
|
49
|
+
if key == "source_layernum" and value not in (None, ""):
|
|
50
|
+
value = int(value)
|
|
51
|
+
elif key == "direction" and isinstance(value, str):
|
|
52
|
+
value = value.upper()
|
|
53
|
+
values.append(value or None)
|
|
54
|
+
layer, direction, frm, to, target = values
|
|
55
|
+
if direction and "Z" in direction:
|
|
56
|
+
target = None
|
|
57
|
+
else:
|
|
58
|
+
frm = to = None
|
|
59
|
+
return layer, direction, frm, to, target
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _describe(signature):
|
|
63
|
+
layer, direction, frm, to, target = signature
|
|
64
|
+
if direction and "Z" in direction:
|
|
65
|
+
return f"via {frm} -> {to} ({direction}) on layer {layer}"
|
|
66
|
+
return f"in-plane {direction} on {target}, layer {layer}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def port_differences(old_ports, new_ports):
|
|
70
|
+
"""Readable differences from old_ports to new_ports (lists of port dicts), [] if they match. Compared per port
|
|
71
|
+
number: layer, direction, from/to/target layer; not Z0 or excitation."""
|
|
72
|
+
old = {int(p["portnumber"]): _port_signature(p) for p in old_ports if p.get("portnumber") not in (None, "")}
|
|
73
|
+
new = {int(p["portnumber"]): _port_signature(p) for p in new_ports if p.get("portnumber") not in (None, "")}
|
|
74
|
+
out = []
|
|
75
|
+
for number in sorted(set(old) | set(new)):
|
|
76
|
+
if number not in new:
|
|
77
|
+
out.append(f"port {number}: removed")
|
|
78
|
+
elif number not in old:
|
|
79
|
+
out.append(f"port {number}: new, {_describe(new[number])}")
|
|
80
|
+
elif old[number] != new[number]:
|
|
81
|
+
out.append(f"port {number}: was {_describe(old[number])}, now {_describe(new[number])}")
|
|
82
|
+
return out
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _same_path(a, b, base_dir=""):
|
|
86
|
+
if not a or not b:
|
|
87
|
+
return a == b
|
|
88
|
+
norm = lambda p: os.path.normcase(os.path.abspath(os.path.join(base_dir, p)))
|
|
89
|
+
return norm(a) == norm(b)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def layout_differences(old_values, new_values, new_base_dir=""):
|
|
93
|
+
"""Readable differences of the LAYOUT_KEYS from old_values to new_values (saved_values dicts); file paths in
|
|
94
|
+
new_values relative to new_base_dir."""
|
|
95
|
+
out = []
|
|
96
|
+
for key in LAYOUT_KEYS:
|
|
97
|
+
if key not in new_values:
|
|
98
|
+
continue
|
|
99
|
+
old, new = old_values.get(key), new_values.get(key)
|
|
100
|
+
if key in ("GdsFile", "SubstrateFile"):
|
|
101
|
+
if not _same_path(old, new, new_base_dir):
|
|
102
|
+
out.append(f"{LAYOUT_LABELS[key]}: {os.path.basename(old or '-')} -> {os.path.basename(new or '-')}")
|
|
103
|
+
elif key == "purpose":
|
|
104
|
+
if [int(p) for p in old or []] != [int(p) for p in new or []]:
|
|
105
|
+
out.append(f"{LAYOUT_LABELS[key]}: {old} -> {new}")
|
|
106
|
+
elif old != new:
|
|
107
|
+
out.append(f"{LAYOUT_LABELS[key]}: {old} -> {new}")
|
|
108
|
+
return out
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def newer_configs(script_path, gds_path, folders, suffix=".simcfg", application="setupEM"):
|
|
112
|
+
"""[(path, data)] of the settings files in folders that belong to gds_path (their GdsFile is that file) and are
|
|
113
|
+
newer than script_path, newest first."""
|
|
114
|
+
try:
|
|
115
|
+
script_time = os.path.getmtime(script_path)
|
|
116
|
+
except OSError:
|
|
117
|
+
return []
|
|
118
|
+
seen, out = set(), []
|
|
119
|
+
for folder in folders:
|
|
120
|
+
if not folder or not os.path.isdir(folder):
|
|
121
|
+
continue
|
|
122
|
+
for path in glob.glob(os.path.join(folder, "*" + suffix)):
|
|
123
|
+
key = os.path.normcase(os.path.abspath(path))
|
|
124
|
+
if key in seen:
|
|
125
|
+
continue
|
|
126
|
+
seen.add(key)
|
|
127
|
+
try:
|
|
128
|
+
if os.path.getmtime(path) <= script_time:
|
|
129
|
+
continue
|
|
130
|
+
with open(path, encoding="utf-8") as f:
|
|
131
|
+
data = json.load(f)
|
|
132
|
+
except (OSError, ValueError):
|
|
133
|
+
continue
|
|
134
|
+
if not isinstance(data, dict) or data.get("application") != application:
|
|
135
|
+
continue
|
|
136
|
+
gds = (data.get("saved_values") or {}).get("GdsFile")
|
|
137
|
+
if gds and _same_path(gds, gds_path, os.path.dirname(path)):
|
|
138
|
+
out.append((path, data))
|
|
139
|
+
out.sort(key=lambda item: os.path.getmtime(item[0]), reverse=True)
|
|
140
|
+
return out
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _reads_gds(script_path, gds_path):
|
|
144
|
+
"""True if one of the script's string literals, taken relative to the script's folder, is gds_path (the script
|
|
145
|
+
is read with ast, never run)."""
|
|
146
|
+
try:
|
|
147
|
+
with open(script_path, encoding="utf-8", errors="replace") as f:
|
|
148
|
+
text = f.read()
|
|
149
|
+
except OSError:
|
|
150
|
+
return False
|
|
151
|
+
if os.path.basename(gds_path) not in text: # cheap test before parsing
|
|
152
|
+
return False
|
|
153
|
+
try:
|
|
154
|
+
tree = ast.parse(text)
|
|
155
|
+
except (SyntaxError, ValueError):
|
|
156
|
+
return False
|
|
157
|
+
folder = os.path.dirname(os.path.abspath(script_path))
|
|
158
|
+
return any(isinstance(n, ast.Constant) and isinstance(n.value, str) and n.value.lower().endswith(".gds")
|
|
159
|
+
and _same_path(n.value, gds_path, folder) for n in ast.walk(tree))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def model_scripts(gds_path, folders):
|
|
163
|
+
"""Model scripts (*.py) in folders that read gds_path, newest first. Their age says nothing against a settings
|
|
164
|
+
file of the same layout: a layout tool writes that file again on every export, also after the script was made,
|
|
165
|
+
while the settings made in setupEM since are only in the script (Create Model writes the script, not the
|
|
166
|
+
settings file)."""
|
|
167
|
+
seen, out = set(), []
|
|
168
|
+
for folder in folders:
|
|
169
|
+
if not folder or not os.path.isdir(folder):
|
|
170
|
+
continue
|
|
171
|
+
for path in glob.glob(os.path.join(folder, "*.py")):
|
|
172
|
+
key = os.path.normcase(os.path.abspath(path))
|
|
173
|
+
if key in seen:
|
|
174
|
+
continue
|
|
175
|
+
seen.add(key)
|
|
176
|
+
if _reads_gds(path, gds_path):
|
|
177
|
+
out.append(path)
|
|
178
|
+
out.sort(key=os.path.getmtime, reverse=True)
|
|
179
|
+
return out
|
|
@@ -636,7 +636,12 @@ class ResultViewerWindow(QDialog):
|
|
|
636
636
|
# regardless of which solver produced it.
|
|
637
637
|
saved_values = self.MainWindow.saved_values
|
|
638
638
|
model_basename = saved_values.get('model_basename', '') if isinstance(saved_values, dict) else ''
|
|
639
|
-
|
|
639
|
+
current_model_dirs = {f"{model_basename}_data"} if model_basename else set()
|
|
640
|
+
# a script edited in place may create several models (e.g. a parameter
|
|
641
|
+
# sweep), each with its own run folder name: all of them are "this model"
|
|
642
|
+
script_runs = getattr(self.MainWindow, "script_runs", None) or []
|
|
643
|
+
if getattr(self.MainWindow, "script_model", None) is not None and script_runs:
|
|
644
|
+
current_model_dirs = {os.path.basename(os.path.normpath(run["sim_path"])) for run in script_runs}
|
|
640
645
|
|
|
641
646
|
result = []
|
|
642
647
|
for path in files:
|
|
@@ -645,8 +650,8 @@ class ResultViewerWindow(QDialog):
|
|
|
645
650
|
continue
|
|
646
651
|
if not include_deembedded and '_deembedded' in name:
|
|
647
652
|
continue
|
|
648
|
-
if not include_all_models and
|
|
649
|
-
if
|
|
653
|
+
if not include_all_models and current_model_dirs:
|
|
654
|
+
if not current_model_dirs & set(os.path.normpath(path).split(os.sep)):
|
|
650
655
|
continue
|
|
651
656
|
result.append(path)
|
|
652
657
|
return result
|