setupEM 0.14.1__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.
Files changed (33) hide show
  1. {setupem-0.14.1/src/setupEM.egg-info → setupem-0.14.2}/PKG-INFO +9 -7
  2. {setupem-0.14.1 → setupem-0.14.2}/README_pypi.md +7 -5
  3. {setupem-0.14.1 → setupem-0.14.2}/pyproject.toml +1 -1
  4. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/__init__.py +1 -1
  5. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/gds_hierarchy_scan.py +5 -58
  6. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/setup_common.py +2 -2
  7. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/simplify_gds.py +10 -151
  8. {setupem-0.14.1 → setupem-0.14.2/src/setupEM.egg-info}/PKG-INFO +9 -7
  9. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM.egg-info/requires.txt +1 -1
  10. {setupem-0.14.1 → setupem-0.14.2}/LICENSE +0 -0
  11. {setupem-0.14.1 → setupem-0.14.2}/README.md +0 -0
  12. {setupem-0.14.1 → setupem-0.14.2}/setup.cfg +0 -0
  13. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/__main__.py +0 -0
  14. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/data/SG13CMOS5L_200um.xml +0 -0
  15. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/data/SG13G2_100um.xml +0 -0
  16. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/data/SG13G2_200um.xml +0 -0
  17. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/data/SG13G2_FEM_200um.xml +0 -0
  18. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/data/SG13G2_FEM_200um_passi3D.xml +0 -0
  19. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/data/SG13G2_nosub.xml +0 -0
  20. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/field_viewer.py +0 -0
  21. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/layout_preview.py +0 -0
  22. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/momentum_import.py +0 -0
  23. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/palace_results.py +0 -0
  24. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/result_viewer.py +0 -0
  25. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/setupEM.py +0 -0
  26. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/setupThermal.py +0 -0
  27. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/stackupEditor.py +0 -0
  28. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/stackup_writer.py +0 -0
  29. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM/thermal_results.py +0 -0
  30. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM.egg-info/SOURCES.txt +0 -0
  31. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM.egg-info/dependency_links.txt +0 -0
  32. {setupem-0.14.1 → setupem-0.14.2}/src/setupEM.egg-info/entry_points.txt +0 -0
  33. {setupem-0.14.1 → setupem-0.14.2}/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.14.1
3
+ Version: 0.14.2
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
@@ -11,7 +11,7 @@ Requires-Python: >=3.9
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: gds2palace>=0.9.0
14
- Requires-Dist: gds_prepare_for_EM>=1.3.0
14
+ Requires-Dist: gds_prepare_for_EM>=1.6.0
15
15
  Requires-Dist: PySide6
16
16
  Requires-Dist: shiboken6
17
17
  Requires-Dist: scipy
@@ -38,6 +38,12 @@ https://github.com/VolkerMuehlhaus/setupEM
38
38
 
39
39
  ## Recent changes
40
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
+
41
47
  # What's New - October 19, 2026
42
48
 
43
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".
@@ -46,10 +52,6 @@ https://github.com/VolkerMuehlhaus/setupEM
46
52
 
47
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.
48
54
 
49
- # What's New - October 17, 2026
50
-
51
- **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.
52
-
53
55
  Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
54
56
 
55
57
  ---
@@ -57,7 +59,7 @@ Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/
57
59
  **Note:** the `setupEM` PyPI package itself requires:
58
60
 
59
61
  - gds2palace>=0.9.0
60
- - gds_prepare_for_EM>=1.3.0
62
+ - gds_prepare_for_EM>=1.6.0
61
63
  - PySide6
62
64
  - shiboken6
63
65
  - scipy
@@ -11,6 +11,12 @@ https://github.com/VolkerMuehlhaus/setupEM
11
11
 
12
12
  ## Recent changes
13
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
+
14
20
  # What's New - October 19, 2026
15
21
 
16
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".
@@ -19,10 +25,6 @@ https://github.com/VolkerMuehlhaus/setupEM
19
25
 
20
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.
21
27
 
22
- # What's New - October 17, 2026
23
-
24
- **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.
25
-
26
28
  Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
27
29
 
28
30
  ---
@@ -30,7 +32,7 @@ Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/
30
32
  **Note:** the `setupEM` PyPI package itself requires:
31
33
 
32
34
  - gds2palace>=0.9.0
33
- - gds_prepare_for_EM>=1.3.0
35
+ - gds_prepare_for_EM>=1.6.0
34
36
  - PySide6
35
37
  - shiboken6
36
38
  - scipy
@@ -13,7 +13,7 @@ authors = [
13
13
  ]
14
14
  dependencies = [
15
15
  "gds2palace>=0.9.0",
16
- "gds_prepare_for_EM>=1.3.0",
16
+ "gds_prepare_for_EM>=1.6.0",
17
17
  "PySide6",
18
18
  "shiboken6",
19
19
  "scipy",
@@ -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.14.1"
27
+ __version__ = "0.14.2"
@@ -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 gdspy
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):
@@ -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: simplify_gds.py imports gds_prepare_for_EM lazily
4916
- # inside run_simplify() too, but import the dialog module itself here
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. All operations come from
25
- gds_prepare_for_EM (a sibling package, already an editable install in this
26
- venv) and are called in-process - no subprocess/CLI involved.
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), same as every other setupEM feature that needs
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, tempfile
36
- import gdspy
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
- self._metal_layers = sorted(
270
- int(m.layernum) for m in metals_list.getallplanarmetals()
271
- if not (port_layer_min <= int(m.layernum) <= port_layer_max)
272
- ) if metals_list is not None else []
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: setupEM
3
- Version: 0.14.1
3
+ Version: 0.14.2
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
@@ -11,7 +11,7 @@ Requires-Python: >=3.9
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: gds2palace>=0.9.0
14
- Requires-Dist: gds_prepare_for_EM>=1.3.0
14
+ Requires-Dist: gds_prepare_for_EM>=1.6.0
15
15
  Requires-Dist: PySide6
16
16
  Requires-Dist: shiboken6
17
17
  Requires-Dist: scipy
@@ -38,6 +38,12 @@ https://github.com/VolkerMuehlhaus/setupEM
38
38
 
39
39
  ## Recent changes
40
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
+
41
47
  # What's New - October 19, 2026
42
48
 
43
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".
@@ -46,10 +52,6 @@ https://github.com/VolkerMuehlhaus/setupEM
46
52
 
47
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.
48
54
 
49
- # What's New - October 17, 2026
50
-
51
- **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.
52
-
53
55
  Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/doc/CHANGES.md)
54
56
 
55
57
  ---
@@ -57,7 +59,7 @@ Full history: [CHANGES.md](https://github.com/VolkerMuehlhaus/setupEM/blob/main/
57
59
  **Note:** the `setupEM` PyPI package itself requires:
58
60
 
59
61
  - gds2palace>=0.9.0
60
- - gds_prepare_for_EM>=1.3.0
62
+ - gds_prepare_for_EM>=1.6.0
61
63
  - PySide6
62
64
  - shiboken6
63
65
  - scipy
@@ -1,5 +1,5 @@
1
1
  gds2palace>=0.9.0
2
- gds_prepare_for_EM>=1.3.0
2
+ gds_prepare_for_EM>=1.6.0
3
3
  PySide6
4
4
  shiboken6
5
5
  scipy
File without changes
File without changes
File without changes