Rewriting Python samples to klayout.* modules, fixing some bugs and flaws in the samples

This commit is contained in:
Matthias Koefferlein 2026-08-09 16:43:11 +02:00
parent 22042a0b10
commit d35e21be29
15 changed files with 177 additions and 136 deletions

View File

@ -8,10 +8,6 @@
<doc>@class [db] PCellDeclarationHelper &lt; PCellDeclaration
@brief A helper class to simplify the declaration of a PCell (Python version)
NOTE: in the following, "pya" can be replaced by "klayout.db" which is
the canonical module and the preferred way of addressing the
external Python library.
This class provides adds some convenience to the PCell declaration based
on PCellDeclaration. PCellDeclaration is a C++ object which is less
convenient to use than a Ruby-based approach. In particular this class
@ -22,10 +18,10 @@ The basic usage of this class is the following:
@code
import pya
import klayout.db as kdb
# Derive your PCell from PCellDeclarationHelper
class MyPCell(pya.PCellDeclarationHelper):
class MyPCell(kdb.PCellDeclarationHelper):
def __init__(self):
@ -35,7 +31,7 @@ class MyPCell(pya.PCellDeclarationHelper):
# your initialization: add parameters with name, type, description and
# optional other values
self.param("p", self.TypeInt, "The parameter", default = 1)
self.param("l", self.TypeLayer, "The layer", default = pya.LayerInfo(1, 0))
self.param("l", self.TypeLayer, "The layer", default = kdb.LayerInfo(1, 0))
# add other parameters ..
# reimplement display_text_impl
@ -71,7 +67,7 @@ we can access the layer index with the "l_layer" method:
@code
def produce_impl(self):
cell.shapes(self.l_layer).insert(pya.Box(0, 0, self.p*100, self.p*200))
cell.shapes(self.l_layer).insert(kdb.Box(0, 0, self.p*100, self.p*200))
@/code
Again in this sample, we used "p" to access the parameter "p".

View File

@ -85,8 +85,7 @@ end
The Python version is this:
<pre>
from pya import BrowserSource, BrowserDialog
<pre>from klayout.lay import BrowserSource, BrowserDialog
class MyBrowserSource(BrowserSource):
def get(self, url):
@ -157,8 +156,7 @@ end
The Python version is:
</p>
<pre>
from pya import Action, MessageBox, Application
<pre>from klayout.lay import Action, MessageBox, Application
def on_triggered():
MessageBox.info("A message", "The action was triggered", MessageBox.Ok)
@ -235,8 +233,8 @@ end
The Python version is:
</p>
<pre>
from pya import QDialog, QVBoxLayout, QLineEdit, QLabel, Application
<pre>from klayout.lay import Application
from klayout.QtWidgets import QDialog, QVBoxLayout, QLineEdit, QLabel
dialog = QDialog(Application.instance().main_window())
layout = QVBoxLayout(dialog)
@ -285,8 +283,8 @@ end
with the Python version:
</p>
<pre>
from pya import QDialog, QVBoxLayout, QLineEdit, QLabel, Application
<pre>from klayout.lay import Application
from klayout.QtWidgets import QDialog, QVBoxLayout, QLineEdit, QLabel
dialog = QDialog(Application.instance().main_window())
layout = QVBoxLayout(dialog)

View File

@ -9,16 +9,17 @@
<keyword name="Ruby scripts"/>
<keyword name="Python"/>
<keyword name="pya"/>
<keyword name="Python module"/>
<keyword name="Python scripts"/>
<p>
This chapter is about programming extensions for KLayout using the integrated Ruby API (RBA) or Python API (pya).
This chapter is about programming extensions for KLayout using the integrated Ruby API (RBA) or Python API (klayout.db, klayout.layer etc.).
</p>
<p>
To use RBA scripts, KLayout must be compiled with the Ruby interpreter. Check under "Help/About" whether
support is available. If there is, the "Build options" will include a "Ruby" or "Python" interpreter or both.
RBA scripts require a Ruby interpreter. To use pya scripts, Python support must be included.
RBA scripts require a Ruby interpreter. To use Python scripts, Python support must be compiled in.
</p>
<p>

View File

@ -7,6 +7,7 @@
<keyword name="Programming"/>
<keyword name="Python"/>
<keyword name="pya"/>
<keyword name="Python module"/>
<keyword name="Python scripts"/>
@ -55,8 +56,11 @@
<p>
Apart from a few specialities and the different language of course, Python macros do not look much different from Ruby
macros. Ruby's "RBA" namespace is "pya" for Python (lowercase to conform with PEP-8). The
class and methods names are the same with very few exceptions and the documentation can be
macros. Ruby's "RBA" namespace is "klayout" for Python (lowercase to conform with PEP-8).
The "klayout" module has a number of submodules like "klayout.db", "klayout.lay" etc.
The old combined Python module "pya" is still available, but deprecated in favor of
aligning code with the standalone "klayout" Python module.
The class and methods names are the same with very few exceptions and the documentation can be
used for Python too. Where necessary, a special remark is made regarding the Python implementation.
</p>
@ -68,12 +72,12 @@
<pre>
# Python version:
import pya
import klayout.db as kdb
layout = pya.Layout()
layout = kdb.Layout()
top = layout.create_cell("TOP")
l1 = layout.layer(1, 0)
top.shapes(l1).insert(pya.Box(0, 0, 1000, 2000))
top.shapes(l1).insert(kdb.Box(0, 0, 1000, 2000))
layout.write("t.gds")
</pre>
@ -139,7 +143,12 @@ layout.write("t.gds")
<ul>
<li><b>KLayout module:</b>
KLayout's module is "pya" (lowercase conforming to PEP 8).
KLayout's module is "klayout" (lowercase conforming to PEP 8).
It has a number of submodules: "db", "lay", "pex", "rdb", "tl" and optionally
"QtCore", "QtGui" etc.
The built-in documentation mentiones the module containing a specific class
at the top of the class documentation. For example, "Box" is present in the
"db" submodule - i.e. it is present in "klayout.db".
</li>
<li><b>Reserved names:</b>
@ -154,8 +163,9 @@ layout.write("t.gds")
Assignment methods (i.e. "Box#left=" are available as attributes. If there is a
read accessor method too, the attribute can be read and written. For example:
<pre>
box = pya.Box()
<pre>import klayout.db as kdb
box = kdb.Box()
box.left = 10
box.right = box.right + 100
</pre>
@ -200,8 +210,9 @@ box.right = box.right + 100
<li><b>Iterators:</b>
Iterator binding:
<pre>
edges = pya.Edges()
<pre>import klayout.db as kdb
edges = kdb.Edges()
...
for edge in edges.each():
...
@ -221,8 +232,10 @@ for edge in edges:
<p>Most methods support keyword arguments, for example:</p>
<pre># a 45 degree rotation
t = pya.CplxTrans(rot = 45)</pre>
<pre>import klayout.db as kdb
# a 45 degree rotation
t = kdb.CplxTrans(rot = 45)</pre>
<p>
Exceptions are some built-in methods like "assign". Keyword arguments can be used
@ -257,10 +270,11 @@ t = pya.CplxTrans(rot = 45)</pre>
</li>
<li><b>Deep copies:</b>
Deep copies of pya objects can be made with dup()
Deep copies of klayout objects can be made with dup()
<pre>
box = pya.Box(10, 20, 110, 220)
<pre>import klayout.db as kdb
box = kdb.Box(10, 20, 110, 220)
copy_box = box.dup()
</pre>
</li>

View File

@ -9,7 +9,8 @@
<group-name/>
<menu-path/>
<interpreter>python</interpreter>
<text>import pya
<text>
import pya
def _qt_signal(s):
"""
@ -39,9 +40,16 @@ def _getattr_with_child_objects(s, n):
# If Qt binding is enabled, install the new getattr function.
# Also install the qt_signal and qt_slot functions which go into the pya module.
if "QObject_Native" in pya.__dict__:
pya.QObject_Native.__getattr__ = _getattr_with_child_objects
pya.__dict__["qt_signal"] = _qt_signal
pya.__dict__["qt_slot"] = _qt_slot
# Do the same for klayout.QtCore
import klayout.QtCore
klayout.QtCore.QObject_Native.__getattr__ = _getattr_with_child_objects
klayout.QtCore.__dict__["qt_signal"] = _qt_signal
klayout.QtCore.__dict__["qt_slot"] = _qt_slot
</text>
</klayout-macro>

View File

@ -26,12 +26,16 @@
# Register this macro as "autorun" to enable the plugin
# on startup.
from klayout.lay import EditorOptionsPage, ConfigPage, Plugin, PluginFactory, MainWindow, Marker
from klayout.db import DBox, DPoint
from klayout.QtWidgets import QGridLayout, QVBoxLayout, QHBoxLayout, QLabel, QSpinBox, QLineEdit
cfg_color = "drag-box-color"
cfg_width = "drag-box-width"
# The widget placed into the editor options dock
class DragBoxEditorOptionsPage(pya.EditorOptionsPage):
class DragBoxEditorOptionsPage(EditorOptionsPage):
"""
An option page providing a single entry box for configuring the line width
@ -47,12 +51,12 @@ class DragBoxEditorOptionsPage(pya.EditorOptionsPage):
super(DragBoxEditorOptionsPage, self).__init__("Options", 1)
layout2 = pya.QVBoxLayout(self)
layout = pya.QHBoxLayout(self)
layout2 = QVBoxLayout(self)
layout = QHBoxLayout(self)
layout2.addLayout(layout)
label = pya.QLabel("Line width", self)
label = QLabel("Line width", self)
layout.addWidget(label)
self.spin_box = pya.QSpinBox(self)
self.spin_box = QSpinBox(self)
self.spin_box.setMinimum(1)
self.spin_box.setMaximum(16)
layout.addWidget(self.spin_box)
@ -87,7 +91,7 @@ class DragBoxEditorOptionsPage(pya.EditorOptionsPage):
# The modal dialog page that appears when "Tab" is pressed
class DragBoxFocusPage(pya.EditorOptionsPage):
class DragBoxFocusPage(EditorOptionsPage):
"""
A (modal) option page, also called a "focus page". This page is
@ -115,21 +119,21 @@ class DragBoxFocusPage(pya.EditorOptionsPage):
self.focus_page = True
self.modal_page = True
self.box = pya.DBox()
self.pfix = pya.DPoint()
self.box = DBox()
self.pfix = DPoint()
self.update_box = None
layout = pya.QGridLayout(self)
layout = QGridLayout(self)
layout.setColumnStretch(1, 1)
label = pya.QLabel("Width", self)
label = QLabel("Width", self)
layout.addWidget(label, 0, 0, 1, 1)
self.le_width = pya.QLineEdit(self)
self.le_width = QLineEdit(self)
layout.addWidget(self.le_width, 0, 1, 1, 1)
label = pya.QLabel("Height", self)
label = QLabel("Height", self)
layout.addWidget(label, 1, 0, 1, 1)
self.le_height = pya.QLineEdit(self)
self.le_height = QLineEdit(self)
layout.addWidget(self.le_height, 1, 1, 1, 1)
layout.setRowStretch(2, 1)
@ -175,11 +179,11 @@ class DragBoxFocusPage(pya.EditorOptionsPage):
# issue the event (call the handler) to inform the plugin of this change
if self.update_box is not None:
self.update_box(pya.DBox(l, b, r, t))
self.update_box(DBox(l, b, r, t))
# The widget placed into the configuration page
class DragBoxConfigPage(pya.ConfigPage):
class DragBoxConfigPage(ConfigPage):
"""
A configuration page with a single entry box to change
@ -197,10 +201,10 @@ class DragBoxConfigPage(pya.ConfigPage):
super(DragBoxConfigPage, self).__init__("Drag Box|Configure")
layout = pya.QHBoxLayout(self)
label = pya.QLabel("Color (hex, rrggbb)", self)
layout = QHBoxLayout(self)
label = QLabel("Color (hex, rrggbb)", self)
layout.addWidget(label)
self.line_edit = pya.QLineEdit(self)
self.line_edit = QLineEdit(self)
layout.addWidget(self.line_edit)
layout.addStretch(1)
@ -217,7 +221,7 @@ class DragBoxConfigPage(pya.ConfigPage):
"""
dispatcher.set_config(cfg_color, self.line_edit.text)
class DragBoxPlugin(pya.Plugin):
class DragBoxPlugin(Plugin):
"""
The custom plugin implementation.
@ -275,14 +279,14 @@ class DragBoxPlugin(pya.Plugin):
self.marker = None
# reset to idle
self.ungrab_mouse()
pya.MainWindow.instance().message("Box finished: " + str(self.box), 10000)
MainWindow.instance().message("Box finished: " + str(self.box), 10000)
def _update_marker(self):
"""
updates the marker with the current box
"""
if self.marker is None:
self.marker = pya.Marker(self.view)
self.marker = Marker(self.view)
self._configure_marker()
self.marker.set(self.box)
@ -339,7 +343,7 @@ class DragBoxPlugin(pya.Plugin):
plugin is activated - i.e. the mode is selected
"""
pya.MainWindow.instance().message("Click on point to start dragging a box", 10000)
MainWindow.instance().message("Click on point to start dragging a box", 10000)
def deactivated(self):
@ -349,7 +353,7 @@ class DragBoxPlugin(pya.Plugin):
"""
self._clear_marker()
pya.MainWindow.instance().message("", 0)
MainWindow.instance().message("", 0)
def mouse_click_event(self, p, buttons, prio):
@ -363,15 +367,15 @@ class DragBoxPlugin(pya.Plugin):
# stop dragging it and freeze the box
if self.marker is None:
p = self.snap2(p)
self.box = pya.DBox(p, p)
self.box = DBox(p, p)
self.start_point = p
self._clear_marker()
self._update_marker()
self.grab_mouse()
pya.MainWindow.instance().message("Drag the box and click again", 10000)
MainWindow.instance().message("Drag the box and click again", 10000)
else:
p = self.snap2(p, self.start_point, True, self.ac_from_buttons(buttons))
self._update_box(pya.DBox(self.start_point, p))
self._update_box(DBox(self.start_point, p))
self._finish()
return True
return False
@ -395,13 +399,13 @@ class DragBoxPlugin(pya.Plugin):
self.clear_mouse_cursors()
p = self.snap2(p, self.start_point, True, self.ac_from_buttons(buttons), visualize=True)
self.add_mouse_cursor(p)
self.box = pya.DBox(self.start_point, p)
self.box = DBox(self.start_point, p)
self._update_marker()
# NOTE: we must not digest this event (i.e. return True)
# to allow the mouse tracker to receive the events as well
return False
class DragBoxPluginFactory(pya.PluginFactory):
class DragBoxPluginFactory(PluginFactory):
"""
Implements a "plugin factory".

View File

@ -7,7 +7,8 @@
<show-in-menu>false</show-in-menu>
<category>pymacros</category>
<interpreter>python</interpreter>
<text>import pya
<text>import klayout.db as kdb
import klayout.lay as klay
"""
This demo installs editor hooks to indicate the forbidding regions around a shape
@ -24,7 +25,7 @@ are drawn.
Space between instances: 0.4
"""
class MyEditorHooks(pya.EditorHooks):
class MyEditorHooks(klay.EditorHooks):
def __init__(self):
@ -36,9 +37,9 @@ class MyEditorHooks(pya.EditorHooks):
self.space = 0.0
self.spaces = {
pya.LayerInfo(1, 0): ( 0.2, pya.Region.Euclidian ),
pya.LayerInfo(2, 0): ( 0.5, pya.Region.Projection ),
pya.LayerInfo(3, 0): ( 1.0, pya.Region.Projection )
kdb.LayerInfo(1, 0): ( 0.2, kdb.Region.Euclidian ),
kdb.LayerInfo(2, 0): ( 0.5, kdb.Region.Projection ),
kdb.LayerInfo(3, 0): ( 1.0, kdb.Region.Projection )
}
self.instance_space = 0.4
@ -61,12 +62,12 @@ class MyEditorHooks(pya.EditorHooks):
p = shape.polygon
if p is not None and p.num_points() &lt; 100:
r = pya.Region()
r = kdb.Region()
r.merged_semantics = (p.num_points() != 2)
r.insert(p)
r = r.drc_hull(self.metrics, self.space)
for pp in r.each():
m = pya.Marker(self.view)
m = klay.Marker(self.view)
m.line_style = 2
m.vertex_size = 0
m.set_polygon(trans * pp)
@ -74,7 +75,7 @@ class MyEditorHooks(pya.EditorHooks):
t = shape.text
if t is not None:
m = pya.Marker(self.view)
m = klay.Marker(self.view)
m.set_box(trans * t.bbox().enlarged(self.space))
self.markers.append(m)
@ -117,7 +118,7 @@ class MyEditorHooks(pya.EditorHooks):
self.markers = []
def create_instance(self, instance, trans):
m = pya.Marker(self.view)
m = klay.Marker(self.view)
m.set_box(trans * instance.bbox().enlarged(self.instance_space / self.layout.dbu))
self.markers.append(m)
@ -143,7 +144,7 @@ class MyEditorHooks(pya.EditorHooks):
if path.shape is None:
m = pya.Marker(self.view)
m = klay.Marker(self.view)
m.set_box(trans * (applied * path.inst().bbox()).enlarged(self.instance_space / self.layout.dbu))
self.markers.append(m)

View File

@ -7,7 +7,8 @@
<shortcut/>
<interpreter>python</interpreter>
<category>pymacro</category>
<text>import pya
<text>import klayout.db as kdb
import klayout.lay as klay
# Enter your Python code here ..

View File

@ -78,6 +78,13 @@ module PCellLibModule
# In lazy mode, the user has to acknowledge parameter changes before
# they are executed.
# end
#
# optional:
# def callback_impl(name)
# TODO: implement functionality to change user interface element
# states for PCell parameter editors based on certain events
# like value changes.
# end
end

View File

@ -7,18 +7,15 @@
<show-in-menu>false</show-in-menu>
<shortcut></shortcut>
<interpreter>python</interpreter>
<text>import pya
<text>import klayout.db as kdb
# PCell template
# This macro template provides the framework for a PCell library
# It is recommended to put PCell code into namespaces.
# TODO: change the module name
# The PCell declaration
# Each PCell must provide a declaration. It is recommended to use the PCell name as the class name.
# TODO: change the class name
class PCell(pya.PCellDeclarationHelper):
class PCell(kdb.PCellDeclarationHelper):
def __init__(self):
@ -26,8 +23,8 @@ class PCell(pya.PCellDeclarationHelper):
super(PCell, self).__init__()
# declare the parameters
# i.e. self.param("l", self.TypeLayer, "Layer", default = pya.LayerInfo(1, 0))
# self.param("s", self.TypeShape, "", default = pya.DPoint(0, 0))
# i.e. self.param("l", self.TypeLayer, "Layer", default = kdb.LayerInfo(1, 0))
# self.param("s", self.TypeShape, "", default = kdb.DPoint(0, 0))
def display_text_impl(self):
# Provide a descriptive text for the cell
@ -35,10 +32,12 @@ class PCell(pya.PCellDeclarationHelper):
def coerce_parameters_impl(self):
# TODO: use x to access parameter x and set_x to modify its value
pass
def produce_impl(self):
# TODO: produce the cell content
# i.e. self.cell.shapes(self.l_layer).insert(pya.Polygon(...))
# i.e. self.cell.shapes(self.l_layer).insert(kdb.Polygon(...))
pass
# optional:
# def can_create_from_shape_impl(self):
@ -60,6 +59,12 @@ class PCell(pya.PCellDeclarationHelper):
# TODO: return "True" here if the PCell takes a long time to compute.
# In lazy mode, the user has to acknowledge parameter changes before
# they are executed.
#
# optional:
# def callback_impl(self, name):
# TODO: implement functionality to change user interface element
# states for PCell parameter editors based on certain events
# like value changes.
# TODO: add more PCell classes ..
@ -68,7 +73,7 @@ class PCell(pya.PCellDeclarationHelper):
# The main purpose of this class is to provide the PCell declarations and to register itself
# with a proper name.
# TODO: change the class name
class PCellLib(pya.Library):
class PCellLib(kdb.Library):
def __init__(self):

View File

@ -8,7 +8,7 @@
<shortcut></shortcut>
<category>pymacros</category>
<interpreter>python</interpreter>
<text>import pya
<text>import klayout.db as kdb
import math
"""
@ -22,7 +22,7 @@ implementation. The macro is also set to "auto run" to install the PCell
when KLayout is run.
"""
class Circle(pya.PCellDeclarationHelper):
class Circle(kdb.PCellDeclarationHelper):
"""
The PCell declaration for the circle
"""
@ -34,7 +34,7 @@ class Circle(pya.PCellDeclarationHelper):
# declare the parameters
self.param("l", self.TypeLayer, "Layer")
self.param("s", self.TypeShape, "", default = pya.DPoint(0, 0))
self.param("s", self.TypeShape, "", default = kdb.DPoint(0, 0))
self.param("r", self.TypeDouble, "Radius", default = 0.1)
self.param("n", self.TypeInt, "Number of points", default = 64)
# this hidden parameter is used to determine whether the radius has changed
@ -53,15 +53,15 @@ class Circle(pya.PCellDeclarationHelper):
# radius ru) and set ru to the effective radius. We also update the
# numerical value or the shape, depending on which on has not changed.
rs = None
if isinstance(self.s, pya.DPoint):
if isinstance(self.s, kdb.DPoint):
# compute distance in micron
rs = self.s.distance(pya.DPoint(0, 0))
rs = self.s.distance(kdb.DPoint(0, 0))
if rs != None and abs(self.r-self.ru) &lt; 1e-6:
self.ru = rs
self.r = rs
else:
self.ru = self.r
self.s = pya.DPoint(-self.r, 0)
self.s = kdb.DPoint(-self.r, 0)
self.rd = 2*self.r
@ -83,7 +83,7 @@ class Circle(pya.PCellDeclarationHelper):
def transformation_from_shape_impl(self):
# Implement the "Create PCell from shape" protocol: we use the center of the shape's
# bounding box to determine the transformation
return pya.Trans(self.shape.bbox().center())
return kdb.Trans(self.shape.bbox().center())
def produce_impl(self):
@ -91,13 +91,13 @@ class Circle(pya.PCellDeclarationHelper):
# compute the circle
da = math.pi * 2 / self.n
pts = [ pya.DPoint(self.ru * math.cos(i * da), self.ru * math.sin(i * da)) for i in range(0, self.n) ]
pts = [ kdb.DPoint(self.ru * math.cos(i * da), self.ru * math.sin(i * da)) for i in range(0, self.n) ]
# create the shape
self.cell.shapes(self.l_layer).insert(pya.DPolygon(pts))
self.cell.shapes(self.l_layer).insert(kdb.DPolygon(pts))
class MyLib(pya.Library):
class MyLib(kdb.Library):
"""
The library where we will put the PCell into
"""

View File

@ -10,14 +10,16 @@
<group-name/>
<menu-path/>
<interpreter>python</interpreter>
<text>import pya
<text>from klayout.QtUiTools import QUiLoader
from klayout.QtCore import QFile, QIODevice
from klayout.lay import Application
import types
# Create the form object from the uic file:
ui_file = pya.QFile(":/macro-templates/qt_designer.ui")
ui_file.open(pya.QIODevice.ReadOnly)
form = pya.QUiLoader().load(ui_file, pya.Application.instance().main_window())
ui_file = QFile(":/macro-templates/qt_designer.ui")
ui_file.open(QIODevice.ReadOnly)
form = QUiLoader().load(ui_file, Application.instance().main_window())
ui_file.close()
# Install an event handler for the button clicked event

View File

@ -14,9 +14,11 @@
<menu-path/>
<interpreter>python</interpreter>
<dsl-interpreter-name/>
<text>import pya
<text>from klayout.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton
from klayout.QtGui import QPixmap, QFont
from klayout.lay import Application
class ScreenshotDialog(pya.QDialog):
class ScreenshotDialog(QDialog):
"""
This class implements a dialog with a screenshot display area and a
screenshot button
@ -25,11 +27,11 @@ class ScreenshotDialog(pya.QDialog):
def button_clicked(self, checked):
""" Event handler: "Screenshot" button clicked """
view = pya.Application.instance().main_window().current_view()
view = Application.instance().main_window().current_view()
# get the screenshot and place it in the image label
if not view is None:
self.image.setPixmap(pya.QPixmap.fromImage(view.get_image(400, 400)))
self.image.setPixmap(QPixmap.fromImage(view.get_image(400, 400)))
else:
self.image.setText("No layout opened to take screenshot from")
@ -42,14 +44,14 @@ class ScreenshotDialog(pya.QDialog):
self.resize(400, 120)
layout = pya.QVBoxLayout(self)
layout = QVBoxLayout(self)
self.setLayout(layout)
self.image = pya.QLabel("Press the button to fetch a screenshot", self)
self.image = QLabel("Press the button to fetch a screenshot", self)
layout.addWidget(self.image)
button = pya.QPushButton('Screenshot', self)
button.setFont(pya.QFont('Times', 18, int(pya.QFont.Bold)))
button = QPushButton('Screenshot', self)
button.setFont(QFont('Times', 18, int(QFont.Bold)))
layout.addWidget(button)
# attach the event handler
@ -57,7 +59,7 @@ class ScreenshotDialog(pya.QDialog):
# Instantiate the dialog and make it visible initially.
# Passing the main_window will make it stay on top of the main window.
dialog = ScreenshotDialog(pya.Application.instance().main_window())
dialog = ScreenshotDialog(Application.instance().main_window())
dialog.show()
</text>
</klayout-macro>

View File

@ -14,10 +14,12 @@
<menu-path/>
<interpreter>python</interpreter>
<dsl-interpreter-name/>
<text>import pya
import re
<text>import re
from klayout.QtNetwork import QTcpServer, QTcpSocket, QHostAddress
from klayout.QtCore import QUrl, QBuffer, QIODevice, QObject, qt_signal, qt_slot
from klayout.lay import Application
class MyServer(pya.QTcpServer):
class MyServer(QTcpServer):
"""
Implements a TCP server listening on port 8082
This server will accept HTTP requests and deliver a HTML page containing an image
@ -39,7 +41,7 @@ class MyServer(pya.QTcpServer):
connection = self.nextPendingConnection()
# Read the request header
while connection.isOpen() and connection.state() == pya.QTcpSocket.ConnectedState:
while connection.isOpen() and connection.state() == QTcpSocket.ConnectedState:
if connection.canReadLine():
line = connection.readLine().decode("utf-8")
if line.rstrip('\n').rstrip('\r') == "":
@ -47,19 +49,19 @@ class MyServer(pya.QTcpServer):
else:
m = re.match("GET\\s+(.*)\\s+HTTP", line)
if not m is None:
url = pya.QUrl(m.groups()[0])
url = QUrl(m.groups()[0])
else:
connection.waitForReadyRead(100)
view = pya.Application.instance().main_window().current_view()
view = Application.instance().main_window().current_view()
if url is None:
pass
elif url.path == "/screenshot.png":
elif url.path() == "/screenshot.png":
# Deliver the image
if not view is None:
b = pya.QBuffer()
b.open(pya.QIODevice.WriteOnly)
b = QBuffer()
b.open(QIODevice.WriteOnly)
view.get_image(800, 800).save(b, "PNG")
b.close()
connection.write(b.buffer())
@ -70,13 +72,13 @@ class MyServer(pya.QTcpServer):
connection.write("HTTP/1.0 200 OK\nContent-Type: text/html\n\n" + '&lt;html&gt;&lt;body&gt;No view open to take screenshot from&lt;/body&gt;&lt;/html&gt;\n\n')
# automatically delete when disconnected
# Note: we cannot use RBA signal bindings for that purpose because the Ruby object might get
# Note: we cannot use native Python signal bindings for that purpose because the Python object might get
# deleted before the C++ object is. Hence we do it explicitly.
connection.disconnectFromHost()
#connection.destroy()
signal = pya.qt_signal("disconnected()")
slot = pya.qt_slot("deleteLater()")
pya.QObject.connect(connection, signal, connection, slot)
signal = qt_signal("disconnected()")
slot = qt_slot("deleteLater()")
QObject.connect(connection, signal, connection, slot)
except Exception as ex:
print("ERROR " + str(ex))
@ -89,7 +91,7 @@ class MyServer(pya.QTcpServer):
super(MyServer, self).__init__(parent)
ha = pya.QHostAddress("0.0.0.0")
ha = QHostAddress("0.0.0.0")
self.listen(ha, 8082)
# install an event on a new connection

View File

@ -7,7 +7,7 @@
<show-in-menu>false</show-in-menu>
<category>pymacros</category>
<interpreter>python</interpreter>
<text>import pya
<text>import klayout.db as kdb
import math
"""
@ -33,7 +33,7 @@ implementation. The macro is also set to "auto run" to install the PCell
when KLayout is run.
"""
class ViaPCell(pya.PCellDeclarationHelper):
class ViaPCell(kdb.PCellDeclarationHelper):
def __init__(self):
@ -73,18 +73,18 @@ class ViaPCell(pya.PCellDeclarationHelper):
self.via_type_list = []
# Via1
vt = pya.ViaType("V1", "Via1 (0.2x0.2)")
vt = kdb.ViaType("V1", "Via1 (0.2x0.2)")
vt.wbmin = 0.4
vt.hbmin = 0.4
vt.wtmin = 0.5
vt.htmin = 0.5
vt.bottom = pya.LayerInfo(1, 0)
vt.cut = pya.LayerInfo(2, 0)
vt.top = pya.LayerInfo(3, 0)
vt.bottom = kdb.LayerInfo(1, 0)
vt.cut = kdb.LayerInfo(2, 0)
vt.top = kdb.LayerInfo(3, 0)
vt.bottom_grid = 0.01
vt.top_grid = 0.01
# foreign attributes (not used by pya, but handy for
# foreign attributes (not used by KLayout, but handy for
# internal use):
vt.enc_bottom = 0.1
vt.enc_top = 0.15
@ -94,18 +94,18 @@ class ViaPCell(pya.PCellDeclarationHelper):
self.via_type_list.append(vt)
# Via2
vt = pya.ViaType("V2", "Via2 (0.3x0.3)")
vt = kdb.ViaType("V2", "Via2 (0.3x0.3)")
vt.wbmin = 0.5
vt.hbmin = 0.5
vt.wtmin = 0.5
vt.htmin = 0.5
vt.bottom = pya.LayerInfo(3, 0)
vt.cut = pya.LayerInfo(4, 0)
vt.top = pya.LayerInfo(5, 0)
vt.bottom = kdb.LayerInfo(3, 0)
vt.cut = kdb.LayerInfo(4, 0)
vt.top = kdb.LayerInfo(5, 0)
vt.bottom_grid = 0.01
vt.top_grid = 0.01
# foreign attributes (not used by pya, but handy for
# foreign attributes (not used by kdb, but handy for
# internal use):
vt.enc_bottom = 0.1
vt.enc_top = 0.1
@ -159,7 +159,7 @@ class ViaPCell(pya.PCellDeclarationHelper):
"""
PCell interface implementation
"""
return pya.Trans()
return kdb.Trans()
def min_dim(self, a, b, da, db):
"""
@ -248,8 +248,8 @@ class ViaPCell(pya.PCellDeclarationHelper):
ltop = self.layout.layer(vt.top)
lcut = self.layout.layer(vt.cut)
self.cell.shapes(lbottom).insert(pya.DBox(wbottom, hbottom))
self.cell.shapes(ltop).insert(pya.DBox(wtop, htop))
self.cell.shapes(lbottom).insert(kdb.DBox(wbottom, hbottom))
self.cell.shapes(ltop).insert(kdb.DBox(wtop, htop))
scut = self.cell.shapes(lcut)
@ -257,10 +257,10 @@ class ViaPCell(pya.PCellDeclarationHelper):
x = (ix - 0.5 * (nx - 1)) * (vt.vwidth + vt.vspace)
for iy in range(0, ny):
y = (iy - 0.5 * (ny - 1)) * (vt.vwidth + vt.vspace)
scut.insert(pya.DBox(vt.vwidth, vt.vwidth).moved(x, y))
scut.insert(kdb.DBox(vt.vwidth, vt.vwidth).moved(x, y))
class MyViaLib(pya.Library):
class MyViaLib(kdb.Library):
"""
A declaration for a test library