setupEM 0.14.0__tar.gz → 0.14.2__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.14.2/PKG-INFO +75 -0
- setupem-0.14.2/README_pypi.md +48 -0
- {setupem-0.14.0 → setupem-0.14.2}/pyproject.toml +1 -1
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/__init__.py +1 -1
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/gds_hierarchy_scan.py +5 -58
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/momentum_import.py +59 -6
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/setup_common.py +2 -2
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/simplify_gds.py +10 -151
- setupem-0.14.2/src/setupEM.egg-info/PKG-INFO +75 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM.egg-info/requires.txt +1 -1
- setupem-0.14.0/PKG-INFO +0 -85
- setupem-0.14.0/README_pypi.md +0 -58
- setupem-0.14.0/src/setupEM.egg-info/PKG-INFO +0 -85
- {setupem-0.14.0 → setupem-0.14.2}/LICENSE +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/README.md +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/setup.cfg +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/__main__.py +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/data/SG13CMOS5L_200um.xml +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/data/SG13G2_100um.xml +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/data/SG13G2_200um.xml +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/data/SG13G2_FEM_200um.xml +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/data/SG13G2_FEM_200um_passi3D.xml +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/data/SG13G2_nosub.xml +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/field_viewer.py +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/layout_preview.py +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/palace_results.py +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/result_viewer.py +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/setupEM.py +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/setupThermal.py +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/stackupEditor.py +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/stackup_writer.py +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM/thermal_results.py +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM.egg-info/SOURCES.txt +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM.egg-info/dependency_links.txt +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM.egg-info/entry_points.txt +0 -0
- {setupem-0.14.0 → setupem-0.14.2}/src/setupEM.egg-info/top_level.txt +0 -0
setupem-0.14.2/PKG-INFO
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: setupEM
|
|
3
|
+
Version: 0.14.2
|
|
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.0
|
|
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
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Python GUI for gds2palace
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
pip install setupEM
|
|
35
|
+
|
|
36
|
+
**Full documentation, installation guide, and usage walkthrough:**
|
|
37
|
+
https://github.com/VolkerMuehlhaus/setupEM
|
|
38
|
+
|
|
39
|
+
## Recent changes
|
|
40
|
+
|
|
41
|
+
# What's New - October 21, 2026
|
|
42
|
+
|
|
43
|
+
**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.
|
|
44
|
+
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
# What's New - October 19, 2026
|
|
48
|
+
|
|
49
|
+
**Stackup Editor, ADS Momentum (*.ltd) import:** metals with an EXPAND thickness operation now make the dielectric next to them thicker, as in ADS, instead of reaching into the dielectric below. Before, a thick EXPAND DOWN metal (e.g. MET1/MET2 in a GaAs stackup) overlapped the layers underneath it, and a via could end up with zero height. Files where long lines are wrapped or END_... markers follow the last entry on the same line now import too; before, they failed with "No usable dielectric slabs".
|
|
50
|
+
|
|
51
|
+
# What's New - October 18, 2026
|
|
52
|
+
|
|
53
|
+
**Touchstone files with different port impedances:** the option line of the combined `.sNp` file now has one Z0 per port, in port order. Before, ports with the same Z0 as port 1 were left out.
|
|
54
|
+
|
|
55
|
+
Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
**Note:** the `setupEM` PyPI package itself requires:
|
|
60
|
+
|
|
61
|
+
- gds2palace>=0.9.0
|
|
62
|
+
- gds_prepare_for_EM>=1.6.0
|
|
63
|
+
- PySide6
|
|
64
|
+
- shiboken6
|
|
65
|
+
- scipy
|
|
66
|
+
- requests
|
|
67
|
+
- scikit-rf
|
|
68
|
+
- matplotlib
|
|
69
|
+
- numpy
|
|
70
|
+
- gdspy
|
|
71
|
+
- meshio
|
|
72
|
+
- pyvista
|
|
73
|
+
- pyvistaqt
|
|
74
|
+
|
|
75
|
+
(Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
|
|
@@ -0,0 +1,48 @@
|
|
|
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 21, 2026
|
|
15
|
+
|
|
16
|
+
**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.
|
|
17
|
+
|
|
18
|
+
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.
|
|
19
|
+
|
|
20
|
+
# What's New - October 19, 2026
|
|
21
|
+
|
|
22
|
+
**Stackup Editor, ADS Momentum (*.ltd) import:** metals with an EXPAND thickness operation now make the dielectric next to them thicker, as in ADS, instead of reaching into the dielectric below. Before, a thick EXPAND DOWN metal (e.g. MET1/MET2 in a GaAs stackup) overlapped the layers underneath it, and a via could end up with zero height. Files where long lines are wrapped or END_... markers follow the last entry on the same line now import too; before, they failed with "No usable dielectric slabs".
|
|
23
|
+
|
|
24
|
+
# What's New - October 18, 2026
|
|
25
|
+
|
|
26
|
+
**Touchstone files with different port impedances:** the option line of the combined `.sNp` file now has one Z0 per port, in port order. Before, ports with the same Z0 as port 1 were left out.
|
|
27
|
+
|
|
28
|
+
Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
**Note:** the `setupEM` PyPI package itself requires:
|
|
33
|
+
|
|
34
|
+
- gds2palace>=0.9.0
|
|
35
|
+
- gds_prepare_for_EM>=1.6.0
|
|
36
|
+
- PySide6
|
|
37
|
+
- shiboken6
|
|
38
|
+
- scipy
|
|
39
|
+
- requests
|
|
40
|
+
- scikit-rf
|
|
41
|
+
- matplotlib
|
|
42
|
+
- numpy
|
|
43
|
+
- gdspy
|
|
44
|
+
- meshio
|
|
45
|
+
- pyvista
|
|
46
|
+
- pyvistaqt
|
|
47
|
+
|
|
48
|
+
(Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
|
|
@@ -30,69 +30,16 @@ cell/reference tree directly instead, recursing into referenced cells
|
|
|
30
30
|
without expanding them, so cost scales with the (small) number of distinct
|
|
31
31
|
cells rather than the (potentially huge) number of instantiated copies.
|
|
32
32
|
|
|
33
|
+
open_gds_cell() and estimate_polygon_count() live in gds_prepare_for_EM's
|
|
34
|
+
gds_simplify_for_em module (shared with the gds2palace MCP server) and are
|
|
35
|
+
re-exported here, so callers keep using gds_hierarchy_scan.<name>.
|
|
36
|
+
|
|
33
37
|
No PySide6/setup_common/layout_preview dependency here - keeps this a leaf
|
|
34
38
|
module importable from both without any circular-import concern (setup_common.py
|
|
35
39
|
and layout_preview.py already need a deferred import between each other).
|
|
36
40
|
"""
|
|
37
41
|
|
|
38
|
-
import
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def open_gds_cell(gds_path, cellname):
|
|
42
|
-
"""Open gds_path and resolve the target cell: cellname if given and
|
|
43
|
-
found, else the file's first top-level cell. Shared first step for
|
|
44
|
-
every walker below. Raises on a bad file or a library with no top-level
|
|
45
|
-
cell, same as gdspy itself would - callers already wrap their own call
|
|
46
|
-
sites in a broad try/except.
|
|
47
|
-
"""
|
|
48
|
-
library = gdspy.GdsLibrary(infile=gds_path)
|
|
49
|
-
top_level = library.top_level()
|
|
50
|
-
if not top_level:
|
|
51
|
-
raise ValueError(f"no top-level cell in {gds_path}")
|
|
52
|
-
cell = library.cells.get(cellname, top_level[0])
|
|
53
|
-
return library, cell
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def estimate_polygon_count(gds_path, cellname, layernumbers, purposelist):
|
|
57
|
-
"""Fast pre-check of how many polygons a full read_gds()-based load
|
|
58
|
-
would end up processing, without flattening the GDS hierarchy. Walks
|
|
59
|
-
the cell/reference tree directly: a plain CellReference contributes its
|
|
60
|
-
referenced cell's own (memoized) count once, a CellArray contributes it
|
|
61
|
-
columns*rows times - the same arithmetic flatten() would otherwise do
|
|
62
|
-
by actually generating that many polygon objects. Returns None if the
|
|
63
|
-
file can't even be opened here (the real read_gds() call is expected to
|
|
64
|
-
surface that error properly instead).
|
|
65
|
-
"""
|
|
66
|
-
try:
|
|
67
|
-
library, cell = open_gds_cell(gds_path, cellname)
|
|
68
|
-
except Exception:
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
layernumbers = set(int(n) for n in layernumbers)
|
|
72
|
-
purposeset = set(int(p) for p in purposelist) if purposelist else None
|
|
73
|
-
|
|
74
|
-
memo = {}
|
|
75
|
-
|
|
76
|
-
def count(c):
|
|
77
|
-
if c.name in memo:
|
|
78
|
-
return memo[c.name]
|
|
79
|
-
total = 0
|
|
80
|
-
for polygonset in c.polygons:
|
|
81
|
-
for layer, datatype in zip(polygonset.layers, polygonset.datatypes):
|
|
82
|
-
if int(layer) in layernumbers and (purposeset is None or int(datatype) in purposeset):
|
|
83
|
-
total += 1
|
|
84
|
-
for ref in c.references:
|
|
85
|
-
ref_cell = ref.ref_cell
|
|
86
|
-
if isinstance(ref_cell, str):
|
|
87
|
-
ref_cell = library.cells.get(ref_cell)
|
|
88
|
-
if ref_cell is None:
|
|
89
|
-
continue
|
|
90
|
-
sub = count(ref_cell)
|
|
91
|
-
total += sub * ref.columns * ref.rows if isinstance(ref, gdspy.CellArray) else sub
|
|
92
|
-
memo[c.name] = total
|
|
93
|
-
return total
|
|
94
|
-
|
|
95
|
-
return count(cell)
|
|
42
|
+
from gds_simplify_for_em import open_gds_cell, estimate_polygon_count # noqa: F401 (re-export)
|
|
96
43
|
|
|
97
44
|
|
|
98
45
|
def layers_present_in_range(gds_path, cellname, layer_min, layer_max, purposelist):
|
|
@@ -294,12 +294,15 @@ def _build_tree(slabs, layers, materials, warnings, derived_layers=None):
|
|
|
294
294
|
# (both source formats define it that way) rather than that metal's TOP, where it
|
|
295
295
|
# actually needs to start - shift any via whose zmin coincides with a metal's zmin
|
|
296
296
|
# up by that metal's own thickness.
|
|
297
|
+
# A metal that only shares its zmin with the via by coincidence (e.g. a thick *.ltd
|
|
298
|
+
# EXPAND DOWN metal reaching the via's bottom interface) must not shrink the via to
|
|
299
|
+
# nothing - only nudge while the via keeps a positive height.
|
|
297
300
|
metal_spans = [(l.zmin, l.zmax - l.zmin) for l in layers if l.kind == "metal"]
|
|
298
301
|
for l in layers:
|
|
299
302
|
if l.kind != "via":
|
|
300
303
|
continue
|
|
301
304
|
for metal_zmin, metal_thickness in metal_spans:
|
|
302
|
-
if abs(l.zmin - metal_zmin) < _EPSILON_UM:
|
|
305
|
+
if abs(l.zmin - metal_zmin) < _EPSILON_UM and l.zmin + metal_thickness < l.zmax - _EPSILON_UM:
|
|
303
306
|
l.zmin += metal_thickness
|
|
304
307
|
break
|
|
305
308
|
|
|
@@ -584,7 +587,23 @@ def _ltd_kv(line):
|
|
|
584
587
|
return {key.upper(): value.strip('"') for key, value in _KV_RE.findall(line)}
|
|
585
588
|
|
|
586
589
|
|
|
590
|
+
# keywords that start a new statement in each section - any other line in these sections
|
|
591
|
+
# is a continuation of the previous statement (a long MATERIAL/MASK/LAYER line wrapped
|
|
592
|
+
# onto the next line, e.g. "IMAG_PERMEABILITY=0", "OPERATION=...", or a MASK's own
|
|
593
|
+
# "MASK_PROPERTIES = {...}" line) and is joined back onto it
|
|
594
|
+
_LTD_STATEMENT_KEYWORDS = {
|
|
595
|
+
"MATERIAL": {"MATERIAL"},
|
|
596
|
+
"OPERATION": {"OPERATION"},
|
|
597
|
+
"MASK": {"MASK"},
|
|
598
|
+
"DERIVEDMASK": {"DERIVEDMASK"},
|
|
599
|
+
"STACK": {"TOP", "INTERFACE", "LAYER", "BOTTOM"},
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
|
|
587
603
|
def _split_ltd_sections(text):
|
|
604
|
+
# END_<section> is not always on a line of its own - seen trailing the last statement
|
|
605
|
+
# ("OPERATION OperationTSV_Virtual_VIA1 DRILL END_OPERATION")
|
|
606
|
+
text = re.sub(r'\s+(END_\w+)\b', r'\n\1', text)
|
|
588
607
|
sections = {}
|
|
589
608
|
current = None
|
|
590
609
|
buf = []
|
|
@@ -603,7 +622,11 @@ def _split_ltd_sections(text):
|
|
|
603
622
|
current = None
|
|
604
623
|
continue
|
|
605
624
|
if current:
|
|
606
|
-
|
|
625
|
+
keywords = _LTD_STATEMENT_KEYWORDS.get(current)
|
|
626
|
+
if keywords and buf and line.split()[0] not in keywords:
|
|
627
|
+
buf[-1] = buf[-1] + " " + line
|
|
628
|
+
else:
|
|
629
|
+
buf.append(line)
|
|
607
630
|
return sections
|
|
608
631
|
|
|
609
632
|
|
|
@@ -679,10 +702,13 @@ def _parse_ltd(ltd_path, air_thickness_um, warnings):
|
|
|
679
702
|
elif "INTRUDE" in kv or "EXPAND" in kv:
|
|
680
703
|
# both give a thickness in meters; DOWN (vs. the default UP) means this metal
|
|
681
704
|
# grows downward from its interface into the slab below - same concept *.subst
|
|
682
|
-
# expresses via a negative thick value, here via an explicit direction token
|
|
705
|
+
# expresses via a negative thick value, here via an explicit direction token.
|
|
706
|
+
# INTRUDE places the metal inside the existing slab; EXPAND makes that slab
|
|
707
|
+
# thicker by the metal thickness (same as *.subst's expand="1"), handled when
|
|
708
|
+
# the stack is walked below.
|
|
683
709
|
magnitude_um = float(kv.get("INTRUDE") or kv.get("EXPAND")) * 1e6
|
|
684
710
|
thickness_um = -magnitude_um if "DOWN" in tokens else magnitude_um
|
|
685
|
-
operations[name] = ("metal", thickness_um)
|
|
711
|
+
operations[name] = ("metal", thickness_um, "EXPAND" in kv)
|
|
686
712
|
elif "SHEET" in tokens:
|
|
687
713
|
operations[name] = ("sheet", None)
|
|
688
714
|
elif "WALL" in tokens:
|
|
@@ -773,7 +799,33 @@ def _parse_ltd(ltd_path, air_thickness_um, warnings):
|
|
|
773
799
|
groundplane_detected = False
|
|
774
800
|
bottom_state = None
|
|
775
801
|
|
|
776
|
-
|
|
802
|
+
# EXPAND metals make the dielectric LAYER next to their INTERFACE thicker (DOWN: the
|
|
803
|
+
# LAYER below, UP: the one above) - by the thickest such metal on that interface, not
|
|
804
|
+
# the sum (ADS shows TFR 0.05um + MET1 1um EXPAND DOWN on one interface as +1um). A
|
|
805
|
+
# LAYER can grow from both sides (metal EXPAND UP below it, another EXPAND DOWN above
|
|
806
|
+
# it), which adds up. Must be known before the bottom-up walk reaches that LAYER, so
|
|
807
|
+
# collected up front: (line index in file order, top-to-bottom; +1 DOWN / -1 UP) ->
|
|
808
|
+
# extra thickness in um
|
|
809
|
+
expand_um = {}
|
|
810
|
+
for idx, line in enumerate(stack_lines):
|
|
811
|
+
if line.split()[0] != "INTERFACE":
|
|
812
|
+
continue
|
|
813
|
+
mask_match = _MASK_BRACE_RE.search(line)
|
|
814
|
+
for token in mask_match.group(1).split() if mask_match else []:
|
|
815
|
+
mask = _resolve_mask_token(token, masks, masks_by_layer)
|
|
816
|
+
op = operations.get(mask["operation"]) if mask else None
|
|
817
|
+
if op is None or op[0] != "metal" or not op[2]:
|
|
818
|
+
continue
|
|
819
|
+
side = 1 if op[1] < 0 else -1
|
|
820
|
+
target = idx + side
|
|
821
|
+
if 0 <= target < len(stack_lines) and stack_lines[target].split()[0] == "LAYER":
|
|
822
|
+
expand_um[(target, side)] = max(expand_um.get((target, side), 0.0), abs(op[1]))
|
|
823
|
+
else:
|
|
824
|
+
warnings.append(f"Mask '{mask['name']}' uses EXPAND but has no dielectric LAYER on that "
|
|
825
|
+
f"side of its INTERFACE - placed without growing any layer")
|
|
826
|
+
|
|
827
|
+
for idx in reversed(range(len(stack_lines))):
|
|
828
|
+
line = stack_lines[idx]
|
|
777
829
|
tokens = line.split()
|
|
778
830
|
if not tokens:
|
|
779
831
|
continue
|
|
@@ -809,7 +861,8 @@ def _parse_ltd(ltd_path, air_thickness_um, warnings):
|
|
|
809
861
|
if material_name not in materials:
|
|
810
862
|
warnings.append(f"Dielectric slab material '{material_name}' not found, skipped")
|
|
811
863
|
continue
|
|
812
|
-
thickness_um = float(height_m) * 1e6 if height_m else 0.0
|
|
864
|
+
thickness_um = ((float(height_m) * 1e6 if height_m else 0.0)
|
|
865
|
+
+ expand_um.get((idx, 1), 0.0) + expand_um.get((idx, -1), 0.0))
|
|
813
866
|
zmin = z
|
|
814
867
|
z += thickness_um
|
|
815
868
|
slabs.append(DielectricSlab(material_name, thickness_um))
|
|
@@ -4912,8 +4912,8 @@ class MainWindowBase(QMainWindow):
|
|
|
4912
4912
|
self.layout_preview_window.show()
|
|
4913
4913
|
|
|
4914
4914
|
def open_simplify_gds(self):
|
|
4915
|
-
# local import:
|
|
4916
|
-
#
|
|
4915
|
+
# local import: gds_simplify_for_em.run_simplify() imports gds_prepare_for_EM
|
|
4916
|
+
# lazily, but import the dialog module itself here
|
|
4917
4917
|
# (not at module load time) for the same circular-import reason as
|
|
4918
4918
|
# open_stackup_editor()/open_layout_preview() above
|
|
4919
4919
|
if __package__ in (None, ""):
|
|
@@ -21,19 +21,18 @@ simplify_gds.py
|
|
|
21
21
|
|
|
22
22
|
Tools > Simplify GDS... : removes floating (unconnected) metal fill, fills
|
|
23
23
|
in small cutouts and/or merges via arrays on the currently loaded GDS file,
|
|
24
|
-
writing the result to a new GDS file.
|
|
25
|
-
gds_prepare_for_EM (
|
|
26
|
-
|
|
24
|
+
writing the result to a new GDS file. The pipeline itself is
|
|
25
|
+
gds_prepare_for_EM's gds_simplify_for_em.run_simplify() (shared with the
|
|
26
|
+
gds2palace MCP server), called in-process - no subprocess/CLI involved.
|
|
27
27
|
|
|
28
28
|
No technology-specific layer numbers are hardcoded here: the metal layers to
|
|
29
29
|
process, the via layers and the metal layers above/below each via are derived
|
|
30
30
|
entirely from the XML stackup already parsed into MainWindow.metals_list
|
|
31
|
-
(gds2palace's stackup_reader),
|
|
32
|
-
to know "which GDS layers are metal".
|
|
31
|
+
(gds2palace's stackup_reader), with gds_simplify_for_em.layers_from_stackup().
|
|
33
32
|
"""
|
|
34
33
|
|
|
35
|
-
import os, io, contextlib, traceback
|
|
36
|
-
import
|
|
34
|
+
import os, io, contextlib, traceback
|
|
35
|
+
from gds_simplify_for_em import run_simplify, layers_from_stackup
|
|
37
36
|
from PySide6.QtWidgets import (
|
|
38
37
|
QDialog, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel, QLineEdit,
|
|
39
38
|
QPushButton, QPlainTextEdit, QFileDialog, QMessageBox, QCheckBox,
|
|
@@ -112,138 +111,6 @@ def _parse_layer_list(text):
|
|
|
112
111
|
f"comma-separated list of layer numbers: {text!r}")
|
|
113
112
|
|
|
114
113
|
|
|
115
|
-
def _flatten_cell(lib, cellname):
|
|
116
|
-
"""Return (lib, top_cell) with top_cell flattened in place. Round-trips
|
|
117
|
-
through a temporary GDS file first: gdspy's flatten()/get_polygonsets()
|
|
118
|
-
hits an internal bug on some in-memory-only reference structures ('tuple'
|
|
119
|
-
object does not support item assignment) that only a GDS write+read
|
|
120
|
-
cycle avoids - same workaround gds_prepare_for_EM.py's own CLI pipeline
|
|
121
|
-
uses before every flatten() call in its main()."""
|
|
122
|
-
with tempfile.TemporaryDirectory(prefix="setupEM_simplify_") as tmp_dir:
|
|
123
|
-
tmp_path = os.path.join(tmp_dir, "pre_flatten.gds")
|
|
124
|
-
lib.write_gds(tmp_path)
|
|
125
|
-
lib = gdspy.GdsLibrary(infile=tmp_path)
|
|
126
|
-
top_cell = lib.cells.get(cellname, lib.top_level()[0])
|
|
127
|
-
top_cell.flatten()
|
|
128
|
-
return lib, top_cell
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def run_simplify(gds_path, metal_layers, output_path, cellname="",
|
|
132
|
-
do_cutouts=False, max_hole_area=None,
|
|
133
|
-
do_floating=False, fill_minsize=1.0, fill_maxsize=None, fill_mincount=20,
|
|
134
|
-
do_merge=False, via_layers=None, via_merge_size=0):
|
|
135
|
-
"""Load gds_path, optionally fill small cutouts, remove floating metal
|
|
136
|
-
fill, and/or merge (boolean OR) polygons per layer as a final pass, on
|
|
137
|
-
the given metal_layers, write the result to output_path. Mirrors the
|
|
138
|
-
step order gds_prepare_for_EM.py's own main() uses: cutout removal first
|
|
139
|
-
(works on the hierarchical design), floating-fill removal after that
|
|
140
|
-
(needs a flattened, per-layer-merged cell first so that touching fill
|
|
141
|
-
tiles aren't mistaken for isolated ones - see
|
|
142
|
-
find_isolated_same_size_polygons_by_layer()'s own docstring), per-layer
|
|
143
|
-
merge, then via array merging.
|
|
144
|
-
|
|
145
|
-
via_layers: list of (via layer, metal layers above, metal layers below),
|
|
146
|
-
GDS layer numbers. Vias closer than via_merge_size (microns) are merged
|
|
147
|
-
if they connect the same metal shapes above and below, see
|
|
148
|
-
gds_prepare_for_EM.merge_via_array_by_metal_overlap(). Vias without metal
|
|
149
|
-
above or below are kept unchanged. None or via_merge_size <= 0: no via
|
|
150
|
-
merging.
|
|
151
|
-
"""
|
|
152
|
-
from gds_prepare_for_EM import (
|
|
153
|
-
remove_cutout_keep_hierarchy,
|
|
154
|
-
merge_polygons_by_layer,
|
|
155
|
-
find_isolated_same_size_polygons_by_layer,
|
|
156
|
-
merge_polygons,
|
|
157
|
-
merge_via_array_by_metal_overlap,
|
|
158
|
-
)
|
|
159
|
-
from gds_geometry_utils import validate_and_repair_polygons
|
|
160
|
-
|
|
161
|
-
# merge_polygons_by_layer()/find_isolated_same_size_polygons_by_layer()
|
|
162
|
-
# create cells via the bare gdspy.Cell(...) constructor, which
|
|
163
|
-
# auto-registers each one into gdspy's persistent global
|
|
164
|
-
# gdspy.current_library - harmless for the CLI (a fresh process every
|
|
165
|
-
# run) but in this long-running GUI, a second Run on the same file
|
|
166
|
-
# would reuse the same cell names and gdspy raises "already present in
|
|
167
|
-
# library". Starting from a fresh current_library each call clears that
|
|
168
|
-
# accumulated state without touching gds_prepare_for_EM itself.
|
|
169
|
-
gdspy.current_library = gdspy.GdsLibrary()
|
|
170
|
-
|
|
171
|
-
lib = gdspy.GdsLibrary()
|
|
172
|
-
lib.read_gds(gds_path)
|
|
173
|
-
top_cell = lib.cells.get(cellname, lib.top_level()[0])
|
|
174
|
-
|
|
175
|
-
if do_cutouts:
|
|
176
|
-
design_bbox = top_cell.get_bounding_box()
|
|
177
|
-
lib = remove_cutout_keep_hierarchy(lib, metal_layers, design_bbox=design_bbox,
|
|
178
|
-
max_hole_area=max_hole_area)
|
|
179
|
-
top_cell = lib.cells.get(cellname, lib.top_level()[0])
|
|
180
|
-
|
|
181
|
-
# floating-fill removal already ends with a flattened, per-layer-merged
|
|
182
|
-
# cell (it needs that itself, to tell a floating fill cluster apart from
|
|
183
|
-
# touching real metal) - so a separate final merge pass is only extra
|
|
184
|
-
# work when floating-fill removal did NOT already run
|
|
185
|
-
already_merged_flat = False
|
|
186
|
-
|
|
187
|
-
if do_floating:
|
|
188
|
-
lib, top_cell = _flatten_cell(lib, cellname)
|
|
189
|
-
merged_lib = merge_polygons_by_layer(top_cell, layers_list=metal_layers)
|
|
190
|
-
merged_top = merged_lib.top_level()[0]
|
|
191
|
-
lib = find_isolated_same_size_polygons_by_layer(
|
|
192
|
-
merged_top, metal_layers,
|
|
193
|
-
minsize=fill_minsize, maxsize=fill_maxsize, mincount=fill_mincount)
|
|
194
|
-
already_merged_flat = True
|
|
195
|
-
|
|
196
|
-
if do_merge and not already_merged_flat:
|
|
197
|
-
lib, top_cell = _flatten_cell(lib, cellname)
|
|
198
|
-
lib = merge_polygons_by_layer(top_cell, layers_list=metal_layers)
|
|
199
|
-
|
|
200
|
-
if via_layers and via_merge_size > 0:
|
|
201
|
-
lib, top_cell = _flatten_cell(lib, cellname)
|
|
202
|
-
lpp = top_cell.get_polygons(by_spec=True)
|
|
203
|
-
print(f'Via array merging (distance {via_merge_size} um):')
|
|
204
|
-
|
|
205
|
-
def metal_polygons(layers):
|
|
206
|
-
polygons = [p for (layer, purpose), polys in lpp.items() if layer in layers for p in polys]
|
|
207
|
-
# touching metal pieces are one metal shape for via grouping
|
|
208
|
-
return merge_polygons(polygons) if polygons else []
|
|
209
|
-
|
|
210
|
-
for via_layer, above_layers, below_layers in via_layers:
|
|
211
|
-
purposes = [purpose for (layer, purpose) in lpp if layer == via_layer]
|
|
212
|
-
if not purposes:
|
|
213
|
-
continue
|
|
214
|
-
above, below = metal_polygons(above_layers), metal_polygons(below_layers)
|
|
215
|
-
for purpose in purposes:
|
|
216
|
-
vias = lpp[(via_layer, purpose)]
|
|
217
|
-
merged = merge_via_array_by_metal_overlap(
|
|
218
|
-
vias, above, below, via_merge_size, keep_unmatched=True)
|
|
219
|
-
top_cell.remove_polygons(
|
|
220
|
-
lambda pts, layer, datatype: layer == via_layer and datatype == purpose)
|
|
221
|
-
for pts in merged:
|
|
222
|
-
top_cell.add(gdspy.Polygon(pts, layer=via_layer, datatype=purpose))
|
|
223
|
-
print(f' layer {via_layer}:{purpose}: {len(vias)} -> {len(merged)} polygons')
|
|
224
|
-
|
|
225
|
-
# Final polygon validity check/repair - always runs, regardless of which
|
|
226
|
-
# operations above were selected. Catches any polygon still geometrically
|
|
227
|
-
# invalid (self-intersecting) no matter the reason - e.g. a cutout
|
|
228
|
-
# deliberately left alone by remove_cutout_keep_hierarchy() for being
|
|
229
|
-
# larger than max_hole_area, or a merge step re-introducing an invalid
|
|
230
|
-
# "bridge" hole encoding while fusing polygons back together. A
|
|
231
|
-
# solid-body mesher like gds2palace cannot handle this even though
|
|
232
|
-
# gdspy/GDSII themselves tolerate it.
|
|
233
|
-
repaired_count, unresolved = validate_and_repair_polygons(lib, metal_layers)
|
|
234
|
-
if repaired_count:
|
|
235
|
-
print(f'Final validity check: repaired {repaired_count} invalid polygon(s)')
|
|
236
|
-
if unresolved:
|
|
237
|
-
print(f'Final validity check: WARNING - {len(unresolved)} polygon(s) could not be '
|
|
238
|
-
f'auto-repaired, still geometrically invalid in the output file:')
|
|
239
|
-
for cell_name, n, layer, reason in unresolved:
|
|
240
|
-
print(f' {cell_name} polygon #{n} layer {layer}: {reason}')
|
|
241
|
-
if not repaired_count and not unresolved:
|
|
242
|
-
print('Final validity check: no invalid polygons found')
|
|
243
|
-
|
|
244
|
-
lib.write_gds(output_path)
|
|
245
|
-
|
|
246
|
-
|
|
247
114
|
class SimplifyGdsDialog(QDialog):
|
|
248
115
|
"""Own top-level window (WA_DeleteOnClose, non-modal), same lifecycle as
|
|
249
116
|
LayoutPreviewWindow/StackupEditorWindow."""
|
|
@@ -266,18 +133,10 @@ class SimplifyGdsDialog(QDialog):
|
|
|
266
133
|
app_name = self.MainWindow.APP_NAME
|
|
267
134
|
port_layer_min, port_layer_max = _port_layer_range(app_name)
|
|
268
135
|
metals_list = self.MainWindow.metals_list
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
# via layers with the metal layers above/below them, for via array merging
|
|
274
|
-
self._via_layers = [
|
|
275
|
-
(int(m.layernum),
|
|
276
|
-
[int(n.layernum) for n in m.above if n.is_metal],
|
|
277
|
-
[int(n.layernum) for n in m.below if n.is_metal])
|
|
278
|
-
for m in metals_list.metals
|
|
279
|
-
if m.is_via and not (port_layer_min <= int(m.layernum) <= port_layer_max)
|
|
280
|
-
] if metals_list is not None else []
|
|
136
|
+
# metal layers to process, and via layers with the metal layers above/below them
|
|
137
|
+
# for via array merging
|
|
138
|
+
self._metal_layers, self._via_layers = layers_from_stackup(
|
|
139
|
+
metals_list, (port_layer_min, port_layer_max)) if metals_list is not None else ([], [])
|
|
281
140
|
|
|
282
141
|
layout = QVBoxLayout(self)
|
|
283
142
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: setupEM
|
|
3
|
+
Version: 0.14.2
|
|
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.0
|
|
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
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Python GUI for gds2palace
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
pip install setupEM
|
|
35
|
+
|
|
36
|
+
**Full documentation, installation guide, and usage walkthrough:**
|
|
37
|
+
https://github.com/VolkerMuehlhaus/setupEM
|
|
38
|
+
|
|
39
|
+
## Recent changes
|
|
40
|
+
|
|
41
|
+
# What's New - October 21, 2026
|
|
42
|
+
|
|
43
|
+
**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.
|
|
44
|
+
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
# What's New - October 19, 2026
|
|
48
|
+
|
|
49
|
+
**Stackup Editor, ADS Momentum (*.ltd) import:** metals with an EXPAND thickness operation now make the dielectric next to them thicker, as in ADS, instead of reaching into the dielectric below. Before, a thick EXPAND DOWN metal (e.g. MET1/MET2 in a GaAs stackup) overlapped the layers underneath it, and a via could end up with zero height. Files where long lines are wrapped or END_... markers follow the last entry on the same line now import too; before, they failed with "No usable dielectric slabs".
|
|
50
|
+
|
|
51
|
+
# What's New - October 18, 2026
|
|
52
|
+
|
|
53
|
+
**Touchstone files with different port impedances:** the option line of the combined `.sNp` file now has one Z0 per port, in port order. Before, ports with the same Z0 as port 1 were left out.
|
|
54
|
+
|
|
55
|
+
Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
**Note:** the `setupEM` PyPI package itself requires:
|
|
60
|
+
|
|
61
|
+
- gds2palace>=0.9.0
|
|
62
|
+
- gds_prepare_for_EM>=1.6.0
|
|
63
|
+
- PySide6
|
|
64
|
+
- shiboken6
|
|
65
|
+
- scipy
|
|
66
|
+
- requests
|
|
67
|
+
- scikit-rf
|
|
68
|
+
- matplotlib
|
|
69
|
+
- numpy
|
|
70
|
+
- gdspy
|
|
71
|
+
- meshio
|
|
72
|
+
- pyvista
|
|
73
|
+
- pyvistaqt
|
|
74
|
+
|
|
75
|
+
(Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
|
setupem-0.14.0/PKG-INFO
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: setupEM
|
|
3
|
-
Version: 0.14.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.0
|
|
14
|
-
Requires-Dist: gds_prepare_for_EM>=1.3.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
|
-
Dynamic: license-file
|
|
27
|
-
|
|
28
|
-
# Python GUI for gds2palace
|
|
29
|
-
|
|
30
|
-

|
|
31
|
-
|
|
32
|
-
## Install
|
|
33
|
-
|
|
34
|
-
pip install setupEM
|
|
35
|
-
|
|
36
|
-
**Full documentation, installation guide, and usage walkthrough:**
|
|
37
|
-
https://github.com/VolkerMuehlhaus/setupEM
|
|
38
|
-
|
|
39
|
-
## Recent changes
|
|
40
|
-
|
|
41
|
-
# What's New - October 17, 2026
|
|
42
|
-
|
|
43
|
-
**Conductor model** replaces **Conductor meshing** on the Mesh and Boundaries tab (Palace only), with a third choice: **Surface impedance** (default, as before), **Solve inside (volume mesh)** and the new **Solve inside + skin layers**, which adds thin layers along the conductor faces for accurate Q and loss at high frequency. Generated model scripts now use `settings['conductor_model']` instead of `settings['filled_metals']`. Older projects and model scripts with `filled_metals` still load. setupEM now requires gds2palace 0.9.0 or later; `pip install --upgrade setupEM` updates it.
|
|
44
|
-
|
|
45
|
-
# What's New - October 16, 2026
|
|
46
|
-
|
|
47
|
-
**Boundary conditions per side:** **Advanced...** next to **Boundary conditions** on the Mesh and Boundaries tab opens a table with the six sides of the simulation box. Each side can have its own boundary condition (**Absorbing**, **PEC** or **PMC**) and air layer thickness; a blank entry uses the value from the tab. For example, **PEC** at zmin with 0 µm air models a backside ground plane at the box boundary. This replaces the "same on all sides / different per side" choice for the air layer, which now lives in the same table.
|
|
48
|
-
|
|
49
|
-
Loading a model with different boundary conditions per side no longer changes it to one condition for all sides: before, a single **PEC** side turned all six sides into PEC.
|
|
50
|
-
|
|
51
|
-
**Stackup Editor:** while the stackup has errors, the cross-section preview can't be redrawn and keeps the last valid drawing. It is now marked as not up to date: dimmed, crossed by a thin red line, with a banner above it. **Show errors** in the banner opens the editor's XML Preview tab, which lists the errors. The status line now points there too, instead of to Save.
|
|
52
|
-
|
|
53
|
-
# What's New - October 12, 2026
|
|
54
|
-
|
|
55
|
-
**3D Field Viewer: value markers.** Check **Probe** in the View box (or press **P**), then click the model to place a numbered marker with the field value at that exact point. Clicking works from any angle, on the full model, the clipped solid, its cut face or the **2D plane only** section; dragging still rotates. Markers are drawn on top of the model, so a marker on a buried body stays visible. A **Markers** list opens beside the view with the marker number, value and x / y / z in µm, plus **Remove**, **Clear** and **Copy** (tab-separated, for spreadsheets). For Elmer thermal temperatures, a dropdown switches the list and labels between **K**, **°C** and **ΔT** (rise above the lowest temperature in the model, in K). Right-click the model for **Jump to Maximum in Cut Plane** (with the clip plane on), **Jump to Maximum on Surface of Body N** (the outer surface of the body you clicked, e.g. the chip; results without body IDs use the whole shown surface), **Add Marker Here**, **Remove Marker N** and **Clear Markers**. Markers are updated when you change the field, result file or cycle; markers outside the new model are removed. Ported from EMStudio's field viewer.
|
|
56
|
-
|
|
57
|
-
**3D Field Viewer: keys.** With the 3D view focused: **R / F / Home** reset camera, **I** isometric, **X / Y / Z** axis views (**Shift**: negative side), **+ / −** zoom, arrow keys pan, **O** parallel projection, **M** find max., **A** arrows on/off, **P** probe, **PgUp / PgDn** move the clip plane. VTK's own letter keys (e.g. **w** wireframe, **3** stereo, **q** / **e** which stopped the interactor) no longer do anything. The right-click menu now opens only on a click: a right-drag zooms without the menu popping up afterwards.
|
|
58
|
-
|
|
59
|
-
**3D Field Viewer:** the **Cycle** picker of a Palace field dump showed Palace's extra AMR error-indicator cycle as a frequency, e.g. "999 GHz (cycle 2)". It is now listed as "error indicator (cycle 2)". This happened when that cycle stores its Indicator/Rank values as point data. Dumps that store them as cell data were already listed as "geometry".
|
|
60
|
-
|
|
61
|
-
**3D Field Viewer:** running `field_viewer.py` / `fieldViewer` standalone with a window failed in Python environments that also have PyQt5 installed. Fixed.
|
|
62
|
-
|
|
63
|
-
**Port de-embedding in `scripts/combine_extend_snp.py` fixed** (the `_deembedded.sNp` and `_dc_deembedded.sNp` files): the negative inductance of all ports was put in series at port 1, and the other ports were not de-embedded. For a 3-port, port 1 got the sum of all three values. Now each port gets the value for its own port number; a port without geometry data in `port_information.json` is not de-embedded. The raw `.sNp` and the `_dc.sNp` files were not affected. Please run `combine_snp` again to create the de-embedded files again. The same bug was fixed in gds2palace, where Gianluca Simone and David Lurz (FAU) found it.
|
|
64
|
-
|
|
65
|
-
Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
**Note:** the `setupEM` PyPI package itself requires:
|
|
70
|
-
|
|
71
|
-
- gds2palace>=0.9.0
|
|
72
|
-
- gds_prepare_for_EM>=1.3.0
|
|
73
|
-
- PySide6
|
|
74
|
-
- shiboken6
|
|
75
|
-
- scipy
|
|
76
|
-
- requests
|
|
77
|
-
- scikit-rf
|
|
78
|
-
- matplotlib
|
|
79
|
-
- numpy
|
|
80
|
-
- gdspy
|
|
81
|
-
- meshio
|
|
82
|
-
- pyvista
|
|
83
|
-
- pyvistaqt
|
|
84
|
-
|
|
85
|
-
(Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
|
setupem-0.14.0/README_pypi.md
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
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 17, 2026
|
|
15
|
-
|
|
16
|
-
**Conductor model** replaces **Conductor meshing** on the Mesh and Boundaries tab (Palace only), with a third choice: **Surface impedance** (default, as before), **Solve inside (volume mesh)** and the new **Solve inside + skin layers**, which adds thin layers along the conductor faces for accurate Q and loss at high frequency. Generated model scripts now use `settings['conductor_model']` instead of `settings['filled_metals']`. Older projects and model scripts with `filled_metals` still load. setupEM now requires gds2palace 0.9.0 or later; `pip install --upgrade setupEM` updates it.
|
|
17
|
-
|
|
18
|
-
# What's New - October 16, 2026
|
|
19
|
-
|
|
20
|
-
**Boundary conditions per side:** **Advanced...** next to **Boundary conditions** on the Mesh and Boundaries tab opens a table with the six sides of the simulation box. Each side can have its own boundary condition (**Absorbing**, **PEC** or **PMC**) and air layer thickness; a blank entry uses the value from the tab. For example, **PEC** at zmin with 0 µm air models a backside ground plane at the box boundary. This replaces the "same on all sides / different per side" choice for the air layer, which now lives in the same table.
|
|
21
|
-
|
|
22
|
-
Loading a model with different boundary conditions per side no longer changes it to one condition for all sides: before, a single **PEC** side turned all six sides into PEC.
|
|
23
|
-
|
|
24
|
-
**Stackup Editor:** while the stackup has errors, the cross-section preview can't be redrawn and keeps the last valid drawing. It is now marked as not up to date: dimmed, crossed by a thin red line, with a banner above it. **Show errors** in the banner opens the editor's XML Preview tab, which lists the errors. The status line now points there too, instead of to Save.
|
|
25
|
-
|
|
26
|
-
# What's New - October 12, 2026
|
|
27
|
-
|
|
28
|
-
**3D Field Viewer: value markers.** Check **Probe** in the View box (or press **P**), then click the model to place a numbered marker with the field value at that exact point. Clicking works from any angle, on the full model, the clipped solid, its cut face or the **2D plane only** section; dragging still rotates. Markers are drawn on top of the model, so a marker on a buried body stays visible. A **Markers** list opens beside the view with the marker number, value and x / y / z in µm, plus **Remove**, **Clear** and **Copy** (tab-separated, for spreadsheets). For Elmer thermal temperatures, a dropdown switches the list and labels between **K**, **°C** and **ΔT** (rise above the lowest temperature in the model, in K). Right-click the model for **Jump to Maximum in Cut Plane** (with the clip plane on), **Jump to Maximum on Surface of Body N** (the outer surface of the body you clicked, e.g. the chip; results without body IDs use the whole shown surface), **Add Marker Here**, **Remove Marker N** and **Clear Markers**. Markers are updated when you change the field, result file or cycle; markers outside the new model are removed. Ported from EMStudio's field viewer.
|
|
29
|
-
|
|
30
|
-
**3D Field Viewer: keys.** With the 3D view focused: **R / F / Home** reset camera, **I** isometric, **X / Y / Z** axis views (**Shift**: negative side), **+ / −** zoom, arrow keys pan, **O** parallel projection, **M** find max., **A** arrows on/off, **P** probe, **PgUp / PgDn** move the clip plane. VTK's own letter keys (e.g. **w** wireframe, **3** stereo, **q** / **e** which stopped the interactor) no longer do anything. The right-click menu now opens only on a click: a right-drag zooms without the menu popping up afterwards.
|
|
31
|
-
|
|
32
|
-
**3D Field Viewer:** the **Cycle** picker of a Palace field dump showed Palace's extra AMR error-indicator cycle as a frequency, e.g. "999 GHz (cycle 2)". It is now listed as "error indicator (cycle 2)". This happened when that cycle stores its Indicator/Rank values as point data. Dumps that store them as cell data were already listed as "geometry".
|
|
33
|
-
|
|
34
|
-
**3D Field Viewer:** running `field_viewer.py` / `fieldViewer` standalone with a window failed in Python environments that also have PyQt5 installed. Fixed.
|
|
35
|
-
|
|
36
|
-
**Port de-embedding in `scripts/combine_extend_snp.py` fixed** (the `_deembedded.sNp` and `_dc_deembedded.sNp` files): the negative inductance of all ports was put in series at port 1, and the other ports were not de-embedded. For a 3-port, port 1 got the sum of all three values. Now each port gets the value for its own port number; a port without geometry data in `port_information.json` is not de-embedded. The raw `.sNp` and the `_dc.sNp` files were not affected. Please run `combine_snp` again to create the de-embedded files again. The same bug was fixed in gds2palace, where Gianluca Simone and David Lurz (FAU) found it.
|
|
37
|
-
|
|
38
|
-
Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
**Note:** the `setupEM` PyPI package itself requires:
|
|
43
|
-
|
|
44
|
-
- gds2palace>=0.9.0
|
|
45
|
-
- gds_prepare_for_EM>=1.3.0
|
|
46
|
-
- PySide6
|
|
47
|
-
- shiboken6
|
|
48
|
-
- scipy
|
|
49
|
-
- requests
|
|
50
|
-
- scikit-rf
|
|
51
|
-
- matplotlib
|
|
52
|
-
- numpy
|
|
53
|
-
- gdspy
|
|
54
|
-
- meshio
|
|
55
|
-
- pyvista
|
|
56
|
-
- pyvistaqt
|
|
57
|
-
|
|
58
|
-
(Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: setupEM
|
|
3
|
-
Version: 0.14.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.0
|
|
14
|
-
Requires-Dist: gds_prepare_for_EM>=1.3.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
|
-
Dynamic: license-file
|
|
27
|
-
|
|
28
|
-
# Python GUI for gds2palace
|
|
29
|
-
|
|
30
|
-

|
|
31
|
-
|
|
32
|
-
## Install
|
|
33
|
-
|
|
34
|
-
pip install setupEM
|
|
35
|
-
|
|
36
|
-
**Full documentation, installation guide, and usage walkthrough:**
|
|
37
|
-
https://github.com/VolkerMuehlhaus/setupEM
|
|
38
|
-
|
|
39
|
-
## Recent changes
|
|
40
|
-
|
|
41
|
-
# What's New - October 17, 2026
|
|
42
|
-
|
|
43
|
-
**Conductor model** replaces **Conductor meshing** on the Mesh and Boundaries tab (Palace only), with a third choice: **Surface impedance** (default, as before), **Solve inside (volume mesh)** and the new **Solve inside + skin layers**, which adds thin layers along the conductor faces for accurate Q and loss at high frequency. Generated model scripts now use `settings['conductor_model']` instead of `settings['filled_metals']`. Older projects and model scripts with `filled_metals` still load. setupEM now requires gds2palace 0.9.0 or later; `pip install --upgrade setupEM` updates it.
|
|
44
|
-
|
|
45
|
-
# What's New - October 16, 2026
|
|
46
|
-
|
|
47
|
-
**Boundary conditions per side:** **Advanced...** next to **Boundary conditions** on the Mesh and Boundaries tab opens a table with the six sides of the simulation box. Each side can have its own boundary condition (**Absorbing**, **PEC** or **PMC**) and air layer thickness; a blank entry uses the value from the tab. For example, **PEC** at zmin with 0 µm air models a backside ground plane at the box boundary. This replaces the "same on all sides / different per side" choice for the air layer, which now lives in the same table.
|
|
48
|
-
|
|
49
|
-
Loading a model with different boundary conditions per side no longer changes it to one condition for all sides: before, a single **PEC** side turned all six sides into PEC.
|
|
50
|
-
|
|
51
|
-
**Stackup Editor:** while the stackup has errors, the cross-section preview can't be redrawn and keeps the last valid drawing. It is now marked as not up to date: dimmed, crossed by a thin red line, with a banner above it. **Show errors** in the banner opens the editor's XML Preview tab, which lists the errors. The status line now points there too, instead of to Save.
|
|
52
|
-
|
|
53
|
-
# What's New - October 12, 2026
|
|
54
|
-
|
|
55
|
-
**3D Field Viewer: value markers.** Check **Probe** in the View box (or press **P**), then click the model to place a numbered marker with the field value at that exact point. Clicking works from any angle, on the full model, the clipped solid, its cut face or the **2D plane only** section; dragging still rotates. Markers are drawn on top of the model, so a marker on a buried body stays visible. A **Markers** list opens beside the view with the marker number, value and x / y / z in µm, plus **Remove**, **Clear** and **Copy** (tab-separated, for spreadsheets). For Elmer thermal temperatures, a dropdown switches the list and labels between **K**, **°C** and **ΔT** (rise above the lowest temperature in the model, in K). Right-click the model for **Jump to Maximum in Cut Plane** (with the clip plane on), **Jump to Maximum on Surface of Body N** (the outer surface of the body you clicked, e.g. the chip; results without body IDs use the whole shown surface), **Add Marker Here**, **Remove Marker N** and **Clear Markers**. Markers are updated when you change the field, result file or cycle; markers outside the new model are removed. Ported from EMStudio's field viewer.
|
|
56
|
-
|
|
57
|
-
**3D Field Viewer: keys.** With the 3D view focused: **R / F / Home** reset camera, **I** isometric, **X / Y / Z** axis views (**Shift**: negative side), **+ / −** zoom, arrow keys pan, **O** parallel projection, **M** find max., **A** arrows on/off, **P** probe, **PgUp / PgDn** move the clip plane. VTK's own letter keys (e.g. **w** wireframe, **3** stereo, **q** / **e** which stopped the interactor) no longer do anything. The right-click menu now opens only on a click: a right-drag zooms without the menu popping up afterwards.
|
|
58
|
-
|
|
59
|
-
**3D Field Viewer:** the **Cycle** picker of a Palace field dump showed Palace's extra AMR error-indicator cycle as a frequency, e.g. "999 GHz (cycle 2)". It is now listed as "error indicator (cycle 2)". This happened when that cycle stores its Indicator/Rank values as point data. Dumps that store them as cell data were already listed as "geometry".
|
|
60
|
-
|
|
61
|
-
**3D Field Viewer:** running `field_viewer.py` / `fieldViewer` standalone with a window failed in Python environments that also have PyQt5 installed. Fixed.
|
|
62
|
-
|
|
63
|
-
**Port de-embedding in `scripts/combine_extend_snp.py` fixed** (the `_deembedded.sNp` and `_dc_deembedded.sNp` files): the negative inductance of all ports was put in series at port 1, and the other ports were not de-embedded. For a 3-port, port 1 got the sum of all three values. Now each port gets the value for its own port number; a port without geometry data in `port_information.json` is not de-embedded. The raw `.sNp` and the `_dc.sNp` files were not affected. Please run `combine_snp` again to create the de-embedded files again. The same bug was fixed in gds2palace, where Gianluca Simone and David Lurz (FAU) found it.
|
|
64
|
-
|
|
65
|
-
Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
**Note:** the `setupEM` PyPI package itself requires:
|
|
70
|
-
|
|
71
|
-
- gds2palace>=0.9.0
|
|
72
|
-
- gds_prepare_for_EM>=1.3.0
|
|
73
|
-
- PySide6
|
|
74
|
-
- shiboken6
|
|
75
|
-
- scipy
|
|
76
|
-
- requests
|
|
77
|
-
- scikit-rf
|
|
78
|
-
- matplotlib
|
|
79
|
-
- numpy
|
|
80
|
-
- gdspy
|
|
81
|
-
- meshio
|
|
82
|
-
- pyvista
|
|
83
|
-
- pyvistaqt
|
|
84
|
-
|
|
85
|
-
(Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|