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 <doc>@class [db] PCellDeclarationHelper &lt; PCellDeclaration
@brief A helper class to simplify the declaration of a PCell (Python version) @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 This class provides adds some convenience to the PCell declaration based
on PCellDeclaration. PCellDeclaration is a C++ object which is less on PCellDeclaration. PCellDeclaration is a C++ object which is less
convenient to use than a Ruby-based approach. In particular this class 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 @code
import pya import klayout.db as kdb
# Derive your PCell from PCellDeclarationHelper # Derive your PCell from PCellDeclarationHelper
class MyPCell(pya.PCellDeclarationHelper): class MyPCell(kdb.PCellDeclarationHelper):
def __init__(self): def __init__(self):
@ -35,7 +31,7 @@ class MyPCell(pya.PCellDeclarationHelper):
# your initialization: add parameters with name, type, description and # your initialization: add parameters with name, type, description and
# optional other values # optional other values
self.param("p", self.TypeInt, "The parameter", default = 1) 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 .. # add other parameters ..
# reimplement display_text_impl # reimplement display_text_impl
@ -71,7 +67,7 @@ we can access the layer index with the "l_layer" method:
@code @code
def produce_impl(self): 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 @/code
Again in this sample, we used "p" to access the parameter "p". Again in this sample, we used "p" to access the parameter "p".

View File

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

View File

@ -9,16 +9,17 @@
<keyword name="Ruby scripts"/> <keyword name="Ruby scripts"/>
<keyword name="Python"/> <keyword name="Python"/>
<keyword name="pya"/> <keyword name="pya"/>
<keyword name="Python module"/>
<keyword name="Python scripts"/> <keyword name="Python scripts"/>
<p> <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>
<p> <p>
To use RBA scripts, KLayout must be compiled with the Ruby interpreter. Check under "Help/About" whether 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. 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>
<p> <p>

View File

@ -7,6 +7,7 @@
<keyword name="Programming"/> <keyword name="Programming"/>
<keyword name="Python"/> <keyword name="Python"/>
<keyword name="pya"/> <keyword name="pya"/>
<keyword name="Python module"/>
<keyword name="Python scripts"/> <keyword name="Python scripts"/>
@ -55,8 +56,11 @@
<p> <p>
Apart from a few specialities and the different language of course, Python macros do not look much different from Ruby 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 macros. Ruby's "RBA" namespace is "klayout" for Python (lowercase to conform with PEP-8).
class and methods names are the same with very few exceptions and the documentation can be 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. used for Python too. Where necessary, a special remark is made regarding the Python implementation.
</p> </p>
@ -68,12 +72,12 @@
<pre> <pre>
# Python version: # Python version:
import pya import klayout.db as kdb
layout = pya.Layout() layout = kdb.Layout()
top = layout.create_cell("TOP") top = layout.create_cell("TOP")
l1 = layout.layer(1, 0) 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") layout.write("t.gds")
</pre> </pre>
@ -139,7 +143,12 @@ layout.write("t.gds")
<ul> <ul>
<li><b>KLayout module:</b> <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>
<li><b>Reserved names:</b> <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 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: read accessor method too, the attribute can be read and written. For example:
<pre> <pre>import klayout.db as kdb
box = pya.Box()
box = kdb.Box()
box.left = 10 box.left = 10
box.right = box.right + 100 box.right = box.right + 100
</pre> </pre>
@ -200,8 +210,9 @@ box.right = box.right + 100
<li><b>Iterators:</b> <li><b>Iterators:</b>
Iterator binding: Iterator binding:
<pre> <pre>import klayout.db as kdb
edges = pya.Edges()
edges = kdb.Edges()
... ...
for edge in edges.each(): for edge in edges.each():
... ...
@ -221,8 +232,10 @@ for edge in edges:
<p>Most methods support keyword arguments, for example:</p> <p>Most methods support keyword arguments, for example:</p>
<pre># a 45 degree rotation <pre>import klayout.db as kdb
t = pya.CplxTrans(rot = 45)</pre>
# a 45 degree rotation
t = kdb.CplxTrans(rot = 45)</pre>
<p> <p>
Exceptions are some built-in methods like "assign". Keyword arguments can be used 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>
<li><b>Deep copies:</b> <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> <pre>import klayout.db as kdb
box = pya.Box(10, 20, 110, 220)
box = kdb.Box(10, 20, 110, 220)
copy_box = box.dup() copy_box = box.dup()
</pre> </pre>
</li> </li>

View File

@ -9,7 +9,8 @@
<group-name/> <group-name/>
<menu-path/> <menu-path/>
<interpreter>python</interpreter> <interpreter>python</interpreter>
<text>import pya <text>
import pya
def _qt_signal(s): 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. # 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. # Also install the qt_signal and qt_slot functions which go into the pya module.
if "QObject_Native" in pya.__dict__: if "QObject_Native" in pya.__dict__:
pya.QObject_Native.__getattr__ = _getattr_with_child_objects pya.QObject_Native.__getattr__ = _getattr_with_child_objects
pya.__dict__["qt_signal"] = _qt_signal pya.__dict__["qt_signal"] = _qt_signal
pya.__dict__["qt_slot"] = _qt_slot 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> </text>
</klayout-macro> </klayout-macro>

View File

@ -26,12 +26,16 @@
# Register this macro as "autorun" to enable the plugin # Register this macro as "autorun" to enable the plugin
# on startup. # 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_color = "drag-box-color"
cfg_width = "drag-box-width" cfg_width = "drag-box-width"
# The widget placed into the editor options dock # 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 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) super(DragBoxEditorOptionsPage, self).__init__("Options", 1)
layout2 = pya.QVBoxLayout(self) layout2 = QVBoxLayout(self)
layout = pya.QHBoxLayout(self) layout = QHBoxLayout(self)
layout2.addLayout(layout) layout2.addLayout(layout)
label = pya.QLabel("Line width", self) label = QLabel("Line width", self)
layout.addWidget(label) layout.addWidget(label)
self.spin_box = pya.QSpinBox(self) self.spin_box = QSpinBox(self)
self.spin_box.setMinimum(1) self.spin_box.setMinimum(1)
self.spin_box.setMaximum(16) self.spin_box.setMaximum(16)
layout.addWidget(self.spin_box) layout.addWidget(self.spin_box)
@ -87,7 +91,7 @@ class DragBoxEditorOptionsPage(pya.EditorOptionsPage):
# The modal dialog page that appears when "Tab" is pressed # 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 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.focus_page = True
self.modal_page = True self.modal_page = True
self.box = pya.DBox() self.box = DBox()
self.pfix = pya.DPoint() self.pfix = DPoint()
self.update_box = None self.update_box = None
layout = pya.QGridLayout(self) layout = QGridLayout(self)
layout.setColumnStretch(1, 1) layout.setColumnStretch(1, 1)
label = pya.QLabel("Width", self) label = QLabel("Width", self)
layout.addWidget(label, 0, 0, 1, 1) 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) 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) 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.addWidget(self.le_height, 1, 1, 1, 1)
layout.setRowStretch(2, 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 # issue the event (call the handler) to inform the plugin of this change
if self.update_box is not None: 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 # The widget placed into the configuration page
class DragBoxConfigPage(pya.ConfigPage): class DragBoxConfigPage(ConfigPage):
""" """
A configuration page with a single entry box to change 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") super(DragBoxConfigPage, self).__init__("Drag Box|Configure")
layout = pya.QHBoxLayout(self) layout = QHBoxLayout(self)
label = pya.QLabel("Color (hex, rrggbb)", self) label = QLabel("Color (hex, rrggbb)", self)
layout.addWidget(label) layout.addWidget(label)
self.line_edit = pya.QLineEdit(self) self.line_edit = QLineEdit(self)
layout.addWidget(self.line_edit) layout.addWidget(self.line_edit)
layout.addStretch(1) layout.addStretch(1)
@ -217,7 +221,7 @@ class DragBoxConfigPage(pya.ConfigPage):
""" """
dispatcher.set_config(cfg_color, self.line_edit.text) dispatcher.set_config(cfg_color, self.line_edit.text)
class DragBoxPlugin(pya.Plugin): class DragBoxPlugin(Plugin):
""" """
The custom plugin implementation. The custom plugin implementation.
@ -275,14 +279,14 @@ class DragBoxPlugin(pya.Plugin):
self.marker = None self.marker = None
# reset to idle # reset to idle
self.ungrab_mouse() 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): def _update_marker(self):
""" """
updates the marker with the current box updates the marker with the current box
""" """
if self.marker is None: if self.marker is None:
self.marker = pya.Marker(self.view) self.marker = Marker(self.view)
self._configure_marker() self._configure_marker()
self.marker.set(self.box) self.marker.set(self.box)
@ -339,7 +343,7 @@ class DragBoxPlugin(pya.Plugin):
plugin is activated - i.e. the mode is selected 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): def deactivated(self):
@ -349,7 +353,7 @@ class DragBoxPlugin(pya.Plugin):
""" """
self._clear_marker() self._clear_marker()
pya.MainWindow.instance().message("", 0) MainWindow.instance().message("", 0)
def mouse_click_event(self, p, buttons, prio): def mouse_click_event(self, p, buttons, prio):
@ -363,15 +367,15 @@ class DragBoxPlugin(pya.Plugin):
# stop dragging it and freeze the box # stop dragging it and freeze the box
if self.marker is None: if self.marker is None:
p = self.snap2(p) p = self.snap2(p)
self.box = pya.DBox(p, p) self.box = DBox(p, p)
self.start_point = p self.start_point = p
self._clear_marker() self._clear_marker()
self._update_marker() self._update_marker()
self.grab_mouse() 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: else:
p = self.snap2(p, self.start_point, True, self.ac_from_buttons(buttons)) 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() self._finish()
return True return True
return False return False
@ -395,13 +399,13 @@ class DragBoxPlugin(pya.Plugin):
self.clear_mouse_cursors() self.clear_mouse_cursors()
p = self.snap2(p, self.start_point, True, self.ac_from_buttons(buttons), visualize=True) p = self.snap2(p, self.start_point, True, self.ac_from_buttons(buttons), visualize=True)
self.add_mouse_cursor(p) self.add_mouse_cursor(p)
self.box = pya.DBox(self.start_point, p) self.box = DBox(self.start_point, p)
self._update_marker() self._update_marker()
# NOTE: we must not digest this event (i.e. return True) # NOTE: we must not digest this event (i.e. return True)
# to allow the mouse tracker to receive the events as well # to allow the mouse tracker to receive the events as well
return False return False
class DragBoxPluginFactory(pya.PluginFactory): class DragBoxPluginFactory(PluginFactory):
""" """
Implements a "plugin factory". Implements a "plugin factory".

View File

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

View File

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

View File

@ -78,6 +78,13 @@ module PCellLibModule
# In lazy mode, the user has to acknowledge parameter changes before # In lazy mode, the user has to acknowledge parameter changes before
# they are executed. # they are executed.
# end # 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 end

View File

@ -7,18 +7,15 @@
<show-in-menu>false</show-in-menu> <show-in-menu>false</show-in-menu>
<shortcut></shortcut> <shortcut></shortcut>
<interpreter>python</interpreter> <interpreter>python</interpreter>
<text>import pya <text>import klayout.db as kdb
# PCell template # PCell template
# This macro template provides the framework for a PCell library # 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 # The PCell declaration
# Each PCell must provide a declaration. It is recommended to use the PCell name as the class name. # Each PCell must provide a declaration. It is recommended to use the PCell name as the class name.
# TODO: change the class name # TODO: change the class name
class PCell(pya.PCellDeclarationHelper): class PCell(kdb.PCellDeclarationHelper):
def __init__(self): def __init__(self):
@ -26,8 +23,8 @@ class PCell(pya.PCellDeclarationHelper):
super(PCell, self).__init__() super(PCell, self).__init__()
# declare the parameters # declare the parameters
# i.e. self.param("l", self.TypeLayer, "Layer", default = pya.LayerInfo(1, 0)) # i.e. self.param("l", self.TypeLayer, "Layer", default = kdb.LayerInfo(1, 0))
# self.param("s", self.TypeShape, "", default = pya.DPoint(0, 0)) # self.param("s", self.TypeShape, "", default = kdb.DPoint(0, 0))
def display_text_impl(self): def display_text_impl(self):
# Provide a descriptive text for the cell # Provide a descriptive text for the cell
@ -35,10 +32,12 @@ class PCell(pya.PCellDeclarationHelper):
def coerce_parameters_impl(self): def coerce_parameters_impl(self):
# TODO: use x to access parameter x and set_x to modify its value # TODO: use x to access parameter x and set_x to modify its value
pass
def produce_impl(self): def produce_impl(self):
# TODO: produce the cell content # 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: # optional:
# def can_create_from_shape_impl(self): # 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. # TODO: return "True" here if the PCell takes a long time to compute.
# In lazy mode, the user has to acknowledge parameter changes before # In lazy mode, the user has to acknowledge parameter changes before
# they are executed. # 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 .. # 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 # The main purpose of this class is to provide the PCell declarations and to register itself
# with a proper name. # with a proper name.
# TODO: change the class name # TODO: change the class name
class PCellLib(pya.Library): class PCellLib(kdb.Library):
def __init__(self): def __init__(self):

View File

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

View File

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

View File

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

View File

@ -14,10 +14,12 @@
<menu-path/> <menu-path/>
<interpreter>python</interpreter> <interpreter>python</interpreter>
<dsl-interpreter-name/> <dsl-interpreter-name/>
<text>import pya <text>import re
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 Implements a TCP server listening on port 8082
This server will accept HTTP requests and deliver a HTML page containing an image 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() connection = self.nextPendingConnection()
# Read the request header # Read the request header
while connection.isOpen() and connection.state() == pya.QTcpSocket.ConnectedState: while connection.isOpen() and connection.state() == QTcpSocket.ConnectedState:
if connection.canReadLine(): if connection.canReadLine():
line = connection.readLine().decode("utf-8") line = connection.readLine().decode("utf-8")
if line.rstrip('\n').rstrip('\r') == "": if line.rstrip('\n').rstrip('\r') == "":
@ -47,19 +49,19 @@ class MyServer(pya.QTcpServer):
else: else:
m = re.match("GET\\s+(.*)\\s+HTTP", line) m = re.match("GET\\s+(.*)\\s+HTTP", line)
if not m is None: if not m is None:
url = pya.QUrl(m.groups()[0]) url = QUrl(m.groups()[0])
else: else:
connection.waitForReadyRead(100) connection.waitForReadyRead(100)
view = pya.Application.instance().main_window().current_view() view = Application.instance().main_window().current_view()
if url is None: if url is None:
pass pass
elif url.path == "/screenshot.png": elif url.path() == "/screenshot.png":
# Deliver the image # Deliver the image
if not view is None: if not view is None:
b = pya.QBuffer() b = QBuffer()
b.open(pya.QIODevice.WriteOnly) b.open(QIODevice.WriteOnly)
view.get_image(800, 800).save(b, "PNG") view.get_image(800, 800).save(b, "PNG")
b.close() b.close()
connection.write(b.buffer()) 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') 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 # 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. # deleted before the C++ object is. Hence we do it explicitly.
connection.disconnectFromHost() connection.disconnectFromHost()
#connection.destroy() #connection.destroy()
signal = pya.qt_signal("disconnected()") signal = qt_signal("disconnected()")
slot = pya.qt_slot("deleteLater()") slot = qt_slot("deleteLater()")
pya.QObject.connect(connection, signal, connection, slot) QObject.connect(connection, signal, connection, slot)
except Exception as ex: except Exception as ex:
print("ERROR " + str(ex)) print("ERROR " + str(ex))
@ -89,7 +91,7 @@ class MyServer(pya.QTcpServer):
super(MyServer, self).__init__(parent) super(MyServer, self).__init__(parent)
ha = pya.QHostAddress("0.0.0.0") ha = QHostAddress("0.0.0.0")
self.listen(ha, 8082) self.listen(ha, 8082)
# install an event on a new connection # install an event on a new connection

View File

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