setupEM 0.1.3__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {setupem-0.1.3 → setupem-0.1.4}/PKG-INFO +3 -2
- {setupem-0.1.3 → setupem-0.1.4}/pyproject.toml +4 -3
- {setupem-0.1.3 → setupem-0.1.4}/src/setupEM/setupEM.py +362 -18
- {setupem-0.1.3 → setupem-0.1.4}/src/setupEM.egg-info/PKG-INFO +3 -2
- setupem-0.1.4/src/setupEM.egg-info/requires.txt +3 -0
- setupem-0.1.3/src/setupEM.egg-info/requires.txt +0 -2
- {setupem-0.1.3 → setupem-0.1.4}/README.md +0 -0
- {setupem-0.1.3 → setupem-0.1.4}/setup.cfg +0 -0
- {setupem-0.1.3 → setupem-0.1.4}/src/examples/pcb_lowpass/palace_pcb_lowpass.py +0 -0
- {setupem-0.1.3 → setupem-0.1.4}/src/examples/sg13_balun_140-170/palace_balun_mesh2.py +0 -0
- {setupem-0.1.3 → setupem-0.1.4}/src/examples/sg13_microstrip_fast/palace_demo_line.py +0 -0
- {setupem-0.1.3 → setupem-0.1.4}/src/examples/sg13_rfcmim/palace_rfcmim.py +0 -0
- {setupem-0.1.3 → setupem-0.1.4}/src/setupEM/__init__.py +0 -0
- {setupem-0.1.3 → setupem-0.1.4}/src/setupEM.egg-info/SOURCES.txt +0 -0
- {setupem-0.1.3 → setupem-0.1.4}/src/setupEM.egg-info/dependency_links.txt +0 -0
- {setupem-0.1.3 → setupem-0.1.4}/src/setupEM.egg-info/entry_points.txt +0 -0
- {setupem-0.1.3 → setupem-0.1.4}/src/setupEM.egg-info/top_level.txt +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: setupEM
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Python tool for configuration of gds2palace workflow with GUI.
|
|
5
5
|
Author-email: Volker Muehlhaus <volker@muehlhaus.com>
|
|
6
6
|
Requires-Python: >=3.9
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: gds2palace>=0.1.
|
|
8
|
+
Requires-Dist: gds2palace>=0.1.4
|
|
9
9
|
Requires-Dist: PySide6
|
|
10
|
+
Requires-Dist: scipy
|
|
10
11
|
|
|
11
12
|
# Create EM Simulation model from GDSII layout files
|
|
12
13
|
|
|
@@ -4,15 +4,16 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "setupEM"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "Python tool for configuration of gds2palace workflow with GUI."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
11
11
|
{ name = "Volker Muehlhaus", email = "volker@muehlhaus.com" }
|
|
12
12
|
]
|
|
13
13
|
dependencies = [
|
|
14
|
-
"gds2palace>=0.1.
|
|
15
|
-
"PySide6"
|
|
14
|
+
"gds2palace>=0.1.4",
|
|
15
|
+
"PySide6",
|
|
16
|
+
"scipy"
|
|
16
17
|
]
|
|
17
18
|
requires-python = ">=3.9"
|
|
18
19
|
|
|
@@ -17,15 +17,17 @@
|
|
|
17
17
|
########################################################################
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
import sys, json, os,
|
|
20
|
+
import sys, json, os, pathlib, ast, webbrowser
|
|
21
|
+
import numpy as np
|
|
22
|
+
from scipy.interpolate import interp1d
|
|
21
23
|
from PySide6.QtWidgets import (
|
|
22
24
|
QApplication, QMainWindow, QWidget, QHBoxLayout, QVBoxLayout,
|
|
23
25
|
QLabel, QLineEdit,QComboBox,QTableWidget,QHeaderView,
|
|
24
26
|
QPushButton, QFileDialog, QTabWidget, QMessageBox, QGroupBox,
|
|
25
|
-
QCheckBox, QAbstractItemView,QStyleFactory,QTableWidgetItem, QPlainTextEdit
|
|
27
|
+
QCheckBox, QAbstractItemView,QStyleFactory,QTableWidgetItem, QPlainTextEdit, QDialog
|
|
26
28
|
)
|
|
27
|
-
from PySide6.QtGui import QAction, QColor, QTextCharFormat, QFont, QSyntaxHighlighter
|
|
28
|
-
from PySide6.QtCore import Qt, QRegularExpression, QProcess
|
|
29
|
+
from PySide6.QtGui import QAction, QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QPainter, QPen
|
|
30
|
+
from PySide6.QtCore import Qt, QRegularExpression, QProcess, QRect
|
|
29
31
|
|
|
30
32
|
# we expect gds2palace in the same directory as this code, or installed as module
|
|
31
33
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'gds2palace')))
|
|
@@ -267,8 +269,16 @@ class FileInputTab(QWidget):
|
|
|
267
269
|
|
|
268
270
|
self.XML_file_layout.addWidget(self.XML_file_edit)
|
|
269
271
|
self.XML_file_layout.addWidget(self.browse_XML_btn)
|
|
270
|
-
|
|
271
272
|
self.XML_layout.addLayout(self.XML_file_layout)
|
|
273
|
+
|
|
274
|
+
self.XML_show_layout = QHBoxLayout()
|
|
275
|
+
self.XML_show_layout.setAlignment(Qt.AlignRight)
|
|
276
|
+
self.show_XML_btn = QPushButton("Show Stackup")
|
|
277
|
+
self.show_XML_btn.setFixedWidth(150)
|
|
278
|
+
self.show_XML_btn.clicked.connect(self.MainWindow.open_popup)
|
|
279
|
+
self.XML_show_layout.addWidget(self.show_XML_btn)
|
|
280
|
+
self.XML_layout.addLayout(self.XML_show_layout)
|
|
281
|
+
|
|
272
282
|
self.XML_group.setLayout(self.XML_layout)
|
|
273
283
|
|
|
274
284
|
self.main_layout.addWidget(self.gds_group)
|
|
@@ -312,6 +322,7 @@ class FileInputTab(QWidget):
|
|
|
312
322
|
def set_XML_file (self, filename):
|
|
313
323
|
self.XML_file_edit.setText(filename)
|
|
314
324
|
saved_values ["SubstrateFile"] = filename.replace('\\','/')
|
|
325
|
+
self.MainWindow.read_XML() # safe if invalid filename
|
|
315
326
|
# file is read when leaving the files tab
|
|
316
327
|
|
|
317
328
|
|
|
@@ -330,22 +341,11 @@ class FileInputTab(QWidget):
|
|
|
330
341
|
# read_XML(self.XML_file_edit.text()) # safe if invalid filename
|
|
331
342
|
|
|
332
343
|
|
|
333
|
-
# load technology stackup data
|
|
334
|
-
def read_XML(self, filename):
|
|
335
|
-
if pathlib.Path(filename).exists():
|
|
336
|
-
materials_list, dielectrics_list, metals_list = stackup_reader.read_substrate (filename)
|
|
337
|
-
self.MainWindow.ports_tab.update_layers(metals_list)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
344
|
def save_values(self):
|
|
342
345
|
saved_values ["GdsFile"] = self.gds_file_edit.text().replace('\\','/')
|
|
343
346
|
saved_values ["SubstrateFile"] = self.XML_file_edit.text().replace('\\','/')
|
|
344
347
|
saved_values ["preprocess_gds"] = self.preprocess_gds_checkbox.isChecked()
|
|
345
348
|
|
|
346
|
-
# read Substrate file, which also updates port target layer choices
|
|
347
|
-
self.read_XML(saved_values ["SubstrateFile"])
|
|
348
|
-
|
|
349
349
|
try:
|
|
350
350
|
merge_polygon_size = float(self.viamerge_edit.text())
|
|
351
351
|
except Exception:
|
|
@@ -365,6 +365,9 @@ class FileInputTab(QWidget):
|
|
|
365
365
|
# also trigger the load function of CreateModelTab, because that uses gds file info
|
|
366
366
|
self.MainWindow.create_model_tab.load_values()
|
|
367
367
|
|
|
368
|
+
# read Substrate file, which also updates port target layer choices
|
|
369
|
+
self.MainWindow.read_XML()
|
|
370
|
+
|
|
368
371
|
return True # Tab change only possible when returning True
|
|
369
372
|
|
|
370
373
|
|
|
@@ -1660,7 +1663,7 @@ class ModelEditorTab(QWidget):
|
|
|
1660
1663
|
"\n\tpurposelist=settings['purpose'], "
|
|
1661
1664
|
"\n\tmetals_list=metals_list, \n\tpreprocess=settings['preprocess_gds'], "
|
|
1662
1665
|
"\n\tmerge_polygon_size=settings['merge_polygon_size'],"
|
|
1663
|
-
"\n\tgds_boundary_layers=
|
|
1666
|
+
"\n\tgds_boundary_layers=dielectrics_list.get_boundary_layers(),"
|
|
1664
1667
|
"\n\tmirror=False, "
|
|
1665
1668
|
"\n\toffset_x=0, offset_y=0,"
|
|
1666
1669
|
"\n\tlayernumber_offset=0)")
|
|
@@ -1778,6 +1781,327 @@ class CodeEditor(QPlainTextEdit):
|
|
|
1778
1781
|
self.setLineWrapMode(QPlainTextEdit.NoWrap)
|
|
1779
1782
|
|
|
1780
1783
|
|
|
1784
|
+
# ---------- POP UP WINDOW TO SHOW STACKUP ------------------
|
|
1785
|
+
|
|
1786
|
+
def epsilon_to_color(erel, transparency):
|
|
1787
|
+
# Compute raw float components
|
|
1788
|
+
red = 250 - 30 * (erel - 1)
|
|
1789
|
+
green = 255 - 20 * (erel - 1) + (20 / erel) + 10 * erel
|
|
1790
|
+
blue = 100 + 15 * erel + (250 / erel)
|
|
1791
|
+
|
|
1792
|
+
# Extra adjustment
|
|
1793
|
+
if 3.8 < erel < 4.5:
|
|
1794
|
+
red += 50 * (erel - 3.8)
|
|
1795
|
+
green -= 100 * (erel - 3.8)
|
|
1796
|
+
|
|
1797
|
+
# Clamp to range 0–255
|
|
1798
|
+
red = min(max(red, 0), 255)
|
|
1799
|
+
green = min(max(green, 0), 255)
|
|
1800
|
+
blue = min(max(blue, 0), 255)
|
|
1801
|
+
|
|
1802
|
+
# Convert to integer RGB
|
|
1803
|
+
r = int(round(red))
|
|
1804
|
+
g = int(round(green))
|
|
1805
|
+
b = int(round(blue))
|
|
1806
|
+
|
|
1807
|
+
return QColor(r, g, b, transparency)
|
|
1808
|
+
|
|
1809
|
+
|
|
1810
|
+
class VectorWidget(QWidget):
|
|
1811
|
+
"""This widget actually draws the stackup preview"""
|
|
1812
|
+
|
|
1813
|
+
def __init__(self, materials_list,dielectrics_list,metals_list):
|
|
1814
|
+
super().__init__()
|
|
1815
|
+
self.materials_list = materials_list
|
|
1816
|
+
self.dielectrics_list = dielectrics_list
|
|
1817
|
+
self.metals_list = metals_list
|
|
1818
|
+
|
|
1819
|
+
|
|
1820
|
+
def paintEvent(self, event):
|
|
1821
|
+
|
|
1822
|
+
# utility: flip y to have y=0 at bottom
|
|
1823
|
+
def flipy(y):
|
|
1824
|
+
return self.height()-y
|
|
1825
|
+
|
|
1826
|
+
# utility to draw text with alignment on right side
|
|
1827
|
+
def drawText_right (x, y, w, h, text):
|
|
1828
|
+
rect = QRect(x, y-h, w, h)
|
|
1829
|
+
painter.drawText(rect, Qt.AlignVCenter | Qt.AlignRight, text)
|
|
1830
|
+
|
|
1831
|
+
def drawText_left (x, y, w, h, text):
|
|
1832
|
+
rect = QRect(x, y-h, w, h)
|
|
1833
|
+
painter.drawText(rect, Qt.AlignVCenter | Qt.AlignLeft, text)
|
|
1834
|
+
|
|
1835
|
+
painter = QPainter(self)
|
|
1836
|
+
painter.fillRect(self.rect(), Qt.white)
|
|
1837
|
+
painter.setRenderHint(QPainter.Antialiasing)
|
|
1838
|
+
|
|
1839
|
+
xmin = int(self.width()*0.02)
|
|
1840
|
+
xmax = int(self.width()*0.98)
|
|
1841
|
+
|
|
1842
|
+
ymin = int(self.height()*0.025)
|
|
1843
|
+
ymax = int(self.height()*0.975)
|
|
1844
|
+
|
|
1845
|
+
penBlack = QPen(Qt.black, 1)
|
|
1846
|
+
penGray = QPen(QColor(134, 132, 130))
|
|
1847
|
+
penDarkGray = QPen(QColor(53, 50, 47))
|
|
1848
|
+
|
|
1849
|
+
# get total dielectric parts, where each metal in a dielectric adds one part
|
|
1850
|
+
dielectric_shapes = []
|
|
1851
|
+
total_parts = 0
|
|
1852
|
+
dielectrics_bottom_up = self.dielectrics_list.dielectrics[::-1]
|
|
1853
|
+
for dielectric in dielectrics_bottom_up: # bottom up
|
|
1854
|
+
painter.setPen(penBlack)
|
|
1855
|
+
|
|
1856
|
+
metals_inside = dielectric.get_planar_metals_inside()
|
|
1857
|
+
# get number of unique zmin values in that list
|
|
1858
|
+
zmin_list = []
|
|
1859
|
+
for metal in metals_inside:
|
|
1860
|
+
if not metal.zmin in zmin_list:
|
|
1861
|
+
zmin_list.append(metal.zmin)
|
|
1862
|
+
metals_count = len(zmin_list)
|
|
1863
|
+
|
|
1864
|
+
# first metal not aligned with dielectric?
|
|
1865
|
+
if len(metals_inside)>0:
|
|
1866
|
+
if metals_inside[0].zmin > dielectric.zmin:
|
|
1867
|
+
metals_count = metals_count+ 0.5
|
|
1868
|
+
|
|
1869
|
+
parts = max (1,metals_count)
|
|
1870
|
+
dielectric_shape = {}
|
|
1871
|
+
dielectric_shape['name'] = dielectric.name
|
|
1872
|
+
dielectric_shape['dielectric'] = dielectric
|
|
1873
|
+
dielectric_shape['numparts'] = parts
|
|
1874
|
+
|
|
1875
|
+
# dielectric color is calculated from permittivity
|
|
1876
|
+
materialname = dielectric.material
|
|
1877
|
+
material = self.materials_list.get_by_name(materialname)
|
|
1878
|
+
eps = material.eps
|
|
1879
|
+
dielectric_shape['color'] = epsilon_to_color(eps, 95) # QColor(Qt.white)
|
|
1880
|
+
dielectric_shape['material'] = material
|
|
1881
|
+
|
|
1882
|
+
total_parts = total_parts + parts
|
|
1883
|
+
dielectric_shapes.append(dielectric_shape)
|
|
1884
|
+
|
|
1885
|
+
# calculate height of one dielectric shape
|
|
1886
|
+
total_parts = max(total_parts, 1)
|
|
1887
|
+
part_height = int((ymax-ymin)/(total_parts))
|
|
1888
|
+
|
|
1889
|
+
y = ymin
|
|
1890
|
+
w = xmax-ymin
|
|
1891
|
+
|
|
1892
|
+
# we need to store data for original z position and the displayed y position
|
|
1893
|
+
stored_z = np.array([0])
|
|
1894
|
+
stored_y = np.array([0])
|
|
1895
|
+
|
|
1896
|
+
|
|
1897
|
+
for dielectric_shape in dielectric_shapes:
|
|
1898
|
+
h = part_height * dielectric_shape['numparts']
|
|
1899
|
+
dielectric = dielectric_shape['dielectric']
|
|
1900
|
+
color = dielectric_shape['color']
|
|
1901
|
+
material = dielectric_shape['material']
|
|
1902
|
+
|
|
1903
|
+
material_string = f'εr={material.eps:.1f}'
|
|
1904
|
+
if material.sigma > 1e-3:
|
|
1905
|
+
material_string = material_string + f' σ={material.sigma:.1f}'
|
|
1906
|
+
material_string = material_string + f'\n{dielectric.thickness:.2f}µm'
|
|
1907
|
+
|
|
1908
|
+
painter.setPen(penBlack)
|
|
1909
|
+
painter.setBrush(color)
|
|
1910
|
+
painter.drawRect(xmin, flipy(y), w, -h)
|
|
1911
|
+
drawText_left (xmin+5, flipy(y), w, h, dielectric.name)
|
|
1912
|
+
drawText_right (xmin, flipy(y), w-5, h, material_string)
|
|
1913
|
+
|
|
1914
|
+
if not dielectric.zmax in stored_z:
|
|
1915
|
+
stored_z = np.append(stored_z, dielectric.zmax)
|
|
1916
|
+
stored_y = np.append(stored_y, y+h)
|
|
1917
|
+
|
|
1918
|
+
|
|
1919
|
+
# get metals inside this dielectric
|
|
1920
|
+
metals_inside = dielectric.get_planar_metals_inside()
|
|
1921
|
+
# height for one dielectric segment including one metal is part_height
|
|
1922
|
+
if len(metals_inside) > 0:
|
|
1923
|
+
|
|
1924
|
+
# there could be multiple metals starting at the same zmin
|
|
1925
|
+
|
|
1926
|
+
# draw planar metals, one after another
|
|
1927
|
+
ymetal = y
|
|
1928
|
+
for n, metal in enumerate(metals_inside):
|
|
1929
|
+
|
|
1930
|
+
painter.setPen(penBlack)
|
|
1931
|
+
|
|
1932
|
+
# check if metal is aligned with dielectric zmin
|
|
1933
|
+
elevation = metal.zmin-dielectric.zmin
|
|
1934
|
+
if n==0 and (abs(elevation) > 0.001):
|
|
1935
|
+
# draw some vertical offset, not aligned with dielectric
|
|
1936
|
+
ymetal = ymetal + part_height*0.5 # slight offset
|
|
1937
|
+
|
|
1938
|
+
# check if next metal is at same zmin
|
|
1939
|
+
next_at_same_zmin = False
|
|
1940
|
+
previous_at_same_zmin = False
|
|
1941
|
+
xmetal = xmin+120
|
|
1942
|
+
wmetal = w-200
|
|
1943
|
+
|
|
1944
|
+
if n < len(metals_inside)-1:
|
|
1945
|
+
next_metal = metals_inside[n+1]
|
|
1946
|
+
if abs(next_metal.zmin - metal.zmin) < 0.001:
|
|
1947
|
+
next_at_same_zmin = True
|
|
1948
|
+
xmetal = xmin+120
|
|
1949
|
+
wmetal = int(w/2)-100
|
|
1950
|
+
else:
|
|
1951
|
+
next_metal = None
|
|
1952
|
+
|
|
1953
|
+
if n > 0:
|
|
1954
|
+
previous_metal = metals_inside[n-1]
|
|
1955
|
+
if abs(previous_metal.zmin - metal.zmin) < 0.001:
|
|
1956
|
+
xmetal = xmin+int(w/2)+20
|
|
1957
|
+
wmetal = int(w/2)-100
|
|
1958
|
+
previous_at_same_zmin = True
|
|
1959
|
+
|
|
1960
|
+
material = self.materials_list.get_by_name(metal.material)
|
|
1961
|
+
|
|
1962
|
+
if metal.is_sheet:
|
|
1963
|
+
# sheet metal that is simulated with zero extrusion
|
|
1964
|
+
height = 3
|
|
1965
|
+
resistance_string = f'Rs={material.Rs*1e3:.1f}mΩ'
|
|
1966
|
+
else:
|
|
1967
|
+
# regular extruded metal
|
|
1968
|
+
height = part_height/2
|
|
1969
|
+
if (material.sigma > 0) and (metal.thickness > 0):
|
|
1970
|
+
Rs = 1 / (material.sigma*metal.thickness*1e-6)
|
|
1971
|
+
if Rs < 1:
|
|
1972
|
+
resistance_string = f'Rs={Rs*1e3:.1f} mΩ'
|
|
1973
|
+
else:
|
|
1974
|
+
resistance_string = f'Rs={Rs:.2f} Ω'
|
|
1975
|
+
else:
|
|
1976
|
+
resistance_string = '? ' + material.type + ' ?'
|
|
1977
|
+
|
|
1978
|
+
# the box for this metal
|
|
1979
|
+
if material.type.upper() == "CONDUCTOR":
|
|
1980
|
+
painter.setBrush(QColor(230,230,230, 90))
|
|
1981
|
+
painter.drawRect(xmetal, flipy(ymetal), wmetal, -int(height))
|
|
1982
|
+
else:
|
|
1983
|
+
painter.setBrush(QColor(230,130,130, 90))
|
|
1984
|
+
painter.drawRect(xmetal, flipy(ymetal), wmetal, -int(height))
|
|
1985
|
+
|
|
1986
|
+
painter.setPen(penBlack)
|
|
1987
|
+
drawText_left(xmetal+10, flipy(ymetal), wmetal, part_height/2, metal.name)
|
|
1988
|
+
painter.setPen(penGray)
|
|
1989
|
+
drawText_right(xmetal, flipy(ymetal), wmetal-10, part_height/2, resistance_string)
|
|
1990
|
+
# store the drawing position, because vias will refer to that
|
|
1991
|
+
if not metal.zmin in stored_z:
|
|
1992
|
+
stored_z = np.append(stored_z, metal.zmin)
|
|
1993
|
+
stored_y = np.append(stored_y, ymetal)
|
|
1994
|
+
if not metal.zmax in stored_z:
|
|
1995
|
+
stored_z = np.append(stored_z, metal.zmax)
|
|
1996
|
+
stored_y = np.append(stored_y, ymetal+height)
|
|
1997
|
+
|
|
1998
|
+
painter.setPen(penGray)
|
|
1999
|
+
painter.drawLine(xmetal-60, flipy(ymetal), xmetal-10, flipy(ymetal))
|
|
2000
|
+
# draw line at top side of metal
|
|
2001
|
+
if not metal.is_sheet:
|
|
2002
|
+
painter.drawLine(xmetal-60, flipy(ymetal+height), xmetal-10, flipy(ymetal+height))
|
|
2003
|
+
heightstring = f'{metal.thickness:.3f}µm'
|
|
2004
|
+
painter.setPen(penDarkGray)
|
|
2005
|
+
drawText_left(xmetal-60, flipy(ymetal), 50, height, heightstring)
|
|
2006
|
+
|
|
2007
|
+
|
|
2008
|
+
if not previous_at_same_zmin:
|
|
2009
|
+
# draw height to metal above
|
|
2010
|
+
if next_metal is not None:
|
|
2011
|
+
dz = abs(next_metal.zmin - metal.zmax)
|
|
2012
|
+
heightstring = f'{dz:.3f}µm'
|
|
2013
|
+
painter.setPen(penGray)
|
|
2014
|
+
drawText_left(xmetal-60, flipy(ymetal+height), 50, height, heightstring)
|
|
2015
|
+
|
|
2016
|
+
if n==len(metals_inside)-1:
|
|
2017
|
+
# last metal (top metal)
|
|
2018
|
+
# place text for distance to dielectric boundary
|
|
2019
|
+
|
|
2020
|
+
painter.setPen(penBlack)
|
|
2021
|
+
dz = dielectric.zmax - metal.zmax
|
|
2022
|
+
if dz > 10:
|
|
2023
|
+
heightstring = f'{dz:.1f}µm'
|
|
2024
|
+
else:
|
|
2025
|
+
heightstring = f'{dz:.3f}µm'
|
|
2026
|
+
painter.setPen(penGray)
|
|
2027
|
+
painter.drawText(xmetal-60, flipy(ymetal+height+5), heightstring)
|
|
2028
|
+
|
|
2029
|
+
if n==0 and elevation > 0.001:
|
|
2030
|
+
# metal not aligned with bottom of dielectric, add a label for offset value
|
|
2031
|
+
heightstring = f'{elevation:.3f}µm'
|
|
2032
|
+
painter.setPen(penGray)
|
|
2033
|
+
painter.drawText(xmetal-60, flipy(ymetal-10), heightstring)
|
|
2034
|
+
|
|
2035
|
+
|
|
2036
|
+
if not next_at_same_zmin:
|
|
2037
|
+
# increase screen y for next metal
|
|
2038
|
+
ymetal = ymetal + part_height
|
|
2039
|
+
|
|
2040
|
+
y = y + h
|
|
2041
|
+
|
|
2042
|
+
# sort stored positions
|
|
2043
|
+
if len(stored_z) > 2:
|
|
2044
|
+
idx = np.argsort(stored_z)
|
|
2045
|
+
y_sorted = stored_y[idx]
|
|
2046
|
+
z_sorted = stored_z[idx]
|
|
2047
|
+
z_to_y = interp1d(z_sorted, y_sorted, kind='cubic', fill_value='extrapolate')
|
|
2048
|
+
|
|
2049
|
+
# next we draw the vias, based on the screen position of metals that we have stored
|
|
2050
|
+
# via position alternates between 3 positions along x axis
|
|
2051
|
+
pos = 1
|
|
2052
|
+
w = (xmax-xmin)/10
|
|
2053
|
+
|
|
2054
|
+
painter.setBrush(QColor(136,192,200,80))
|
|
2055
|
+
for metal in self.metals_list.metals:
|
|
2056
|
+
if metal.is_via:
|
|
2057
|
+
y1 = z_to_y(metal.zmin)
|
|
2058
|
+
y2 = z_to_y(metal.zmax)
|
|
2059
|
+
h = abs(y2-y1)
|
|
2060
|
+
|
|
2061
|
+
if pos == 1:
|
|
2062
|
+
xvia = (xmax+xmin)/2-4*w/2
|
|
2063
|
+
pos = 2
|
|
2064
|
+
elif pos == 2:
|
|
2065
|
+
xvia = (xmax+xmin)/2-w/2
|
|
2066
|
+
pos = 3
|
|
2067
|
+
else:
|
|
2068
|
+
xvia = (xmax+xmin)/2+w
|
|
2069
|
+
pos = 1
|
|
2070
|
+
|
|
2071
|
+
painter.setPen(penBlack)
|
|
2072
|
+
painter.drawRect(xvia, flipy(y1), w, -h)
|
|
2073
|
+
painter.drawText(xvia+10, flipy(y1+5), metal.name)
|
|
2074
|
+
|
|
2075
|
+
painter.end()
|
|
2076
|
+
|
|
2077
|
+
|
|
2078
|
+
class PopUpWindow(QDialog):
|
|
2079
|
+
"""This window shows the substrate stackup preview"""
|
|
2080
|
+
def __init__(self, MainWindow):
|
|
2081
|
+
super().__init__()
|
|
2082
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
2083
|
+
self.setWindowTitle("Stackup Preview")
|
|
2084
|
+
self.resize(700, 800)
|
|
2085
|
+
self.MainWindow = MainWindow
|
|
2086
|
+
|
|
2087
|
+
layout = QVBoxLayout()
|
|
2088
|
+
|
|
2089
|
+
# Add the custom painting widget
|
|
2090
|
+
self.vector_widget = VectorWidget(self.MainWindow.materials_list,
|
|
2091
|
+
self.MainWindow.dielectrics_list,
|
|
2092
|
+
self.MainWindow.metals_list)
|
|
2093
|
+
layout.addWidget(self.vector_widget)
|
|
2094
|
+
|
|
2095
|
+
# Close button
|
|
2096
|
+
close_button = QPushButton("Close")
|
|
2097
|
+
close_button.clicked.connect(self.close)
|
|
2098
|
+
layout.addWidget(close_button)
|
|
2099
|
+
|
|
2100
|
+
self.setLayout(layout)
|
|
2101
|
+
self.setModal(True)
|
|
2102
|
+
|
|
2103
|
+
|
|
2104
|
+
|
|
1781
2105
|
# ---------- MAIN WINDOW ----------
|
|
1782
2106
|
|
|
1783
2107
|
|
|
@@ -1822,6 +2146,11 @@ class MainWindow(QMainWindow):
|
|
|
1822
2146
|
self.tabs_widget.currentChanged.connect(self.on_tab_change)
|
|
1823
2147
|
main_layout.addWidget(self.tabs_widget)
|
|
1824
2148
|
|
|
2149
|
+
# Stackup data from XML, used when showing stackup viewer
|
|
2150
|
+
self.materials_list = None
|
|
2151
|
+
self.dielectrics_list = None
|
|
2152
|
+
self.metals_list = None
|
|
2153
|
+
|
|
1825
2154
|
# Do not auto-load default values at this early startup stage,
|
|
1826
2155
|
# instead this is done from File menu
|
|
1827
2156
|
# self.user_inputs_file = DEFAULT_SETTINGS_FILE
|
|
@@ -1950,7 +2279,6 @@ class MainWindow(QMainWindow):
|
|
|
1950
2279
|
self.load_configuration_from_file(file_path)
|
|
1951
2280
|
|
|
1952
2281
|
|
|
1953
|
-
|
|
1954
2282
|
def load_configuration_from_file(self, file_path):
|
|
1955
2283
|
if file_path:
|
|
1956
2284
|
extension = pathlib.Path(file_path).suffix
|
|
@@ -2134,6 +2462,20 @@ class MainWindow(QMainWindow):
|
|
|
2134
2462
|
saved_values['sim_path']=''
|
|
2135
2463
|
|
|
2136
2464
|
|
|
2465
|
+
# load technology stackup data
|
|
2466
|
+
def read_XML(self):
|
|
2467
|
+
filename = saved_values ["SubstrateFile"]
|
|
2468
|
+
if pathlib.Path(filename).exists():
|
|
2469
|
+
self.materials_list, self.dielectrics_list, self.metals_list = stackup_reader.read_substrate (filename)
|
|
2470
|
+
self.ports_tab.update_layers(self.metals_list)
|
|
2471
|
+
|
|
2472
|
+
|
|
2473
|
+
def open_popup(self):
|
|
2474
|
+
# self.read_XML(saved_values ["SubstrateFile"])
|
|
2475
|
+
self.popup = PopUpWindow(self)
|
|
2476
|
+
self.popup.show()
|
|
2477
|
+
|
|
2478
|
+
|
|
2137
2479
|
|
|
2138
2480
|
def parse_assignments(file_path):
|
|
2139
2481
|
# parse lines from a Python model code for variable assigments
|
|
@@ -2206,6 +2548,8 @@ def main():
|
|
|
2206
2548
|
|
|
2207
2549
|
win = MainWindow()
|
|
2208
2550
|
win.show()
|
|
2551
|
+
|
|
2552
|
+
|
|
2209
2553
|
sys.exit(app.exec())
|
|
2210
2554
|
|
|
2211
2555
|
if __name__ == "__main__":
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: setupEM
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Python tool for configuration of gds2palace workflow with GUI.
|
|
5
5
|
Author-email: Volker Muehlhaus <volker@muehlhaus.com>
|
|
6
6
|
Requires-Python: >=3.9
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: gds2palace>=0.1.
|
|
8
|
+
Requires-Dist: gds2palace>=0.1.4
|
|
9
9
|
Requires-Dist: PySide6
|
|
10
|
+
Requires-Dist: scipy
|
|
10
11
|
|
|
11
12
|
# Create EM Simulation model from GDSII layout files
|
|
12
13
|
|
|
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
|