From 068849a634f1c70348ed10160aebd332d44233c1 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 10 Mar 2023 23:22:30 +0100 Subject: [PATCH] Refactoring, primary goal is to centralize the definition of PCellDeclarationHelper in Python --- .../pcell_declaration_helper.lym | 315 +---------- .../distutils_src/klayout/db/__init__.py | 41 +- .../klayout/db/pcell_declaration_helper.py | 85 ++- .../distutils_src/klayout/lay/__init__.py | 8 +- .../distutils_src/klayout/lib/__init__.py | 6 +- .../distutils_src/klayout/rdb/__init__.py | 6 +- .../distutils_src/klayout/tl/__init__.py | 6 +- src/pymod/distutils_src/pya/__init__.py | 17 + src/pymod/unit_tests/pymod_tests.cc | 1 + testdata/pymod/klayout_db_tests.py | 502 ++++++++++++++++++ 10 files changed, 621 insertions(+), 366 deletions(-) create mode 100644 testdata/pymod/klayout_db_tests.py diff --git a/src/db/db/built-in-pymacros/pcell_declaration_helper.lym b/src/db/db/built-in-pymacros/pcell_declaration_helper.lym index e3cdf7f62..462883154 100644 --- a/src/db/db/built-in-pymacros/pcell_declaration_helper.lym +++ b/src/db/db/built-in-pymacros/pcell_declaration_helper.lym @@ -8,6 +8,10 @@ @class [db] PCellDeclarationHelper < 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 @@ -252,314 +256,7 @@ This method must return a \\Trans object. The default implementation returns a u python -import pya - -class _PCellDeclarationHelperLayerDescriptor(object): - """ - A descriptor object which translates the PCell parameters into class attributes - """ - - def __init__(self, param_index): - self.param_index = param_index - - def __get__(self, obj, type = None): - return obj._layers[self.param_index] - - def __set__(self, obj, value): - raise AttributeError("can't change layer attribute") - -class _PCellDeclarationHelperParameterDescriptor(object): - """ - A descriptor object which translates the PCell parameters into class attributes - - In some cases (i.e. can_convert_from_shape), these placeholders are not - connected to real parameters (obj._param_values is None). In this case, - the descriptor acts as a value holder (self.value) - """ - - def __init__(self, param_index, param_name): - self.param_index = param_index - self.param_name = param_name - self.value = None - - def __get__(self, obj, type = None): - if obj._param_values: - return obj._param_values[self.param_index] - elif obj._param_states: - return obj._param_states.parameter(self.param_name) - else: - return self.value - - def __set__(self, obj, value): - if obj._param_values: - obj._param_values[self.param_index] = value - else: - self.value = value - -class _PCellDeclarationHelper(pya.PCellDeclaration): - """ - A helper class that somewhat simplifies the implementation - of a PCell - """ - - def __init__(self): - """ - initialize this instance - """ - # "private" attributes - self._param_decls = [] - self._param_values = None - self._param_states = None - self._layer_param_index = [] - self._layers = [] - # public attributes - self.layout = None - self.shape = None - self.layer = None - self.cell = None - - def param(self, name, value_type, description, hidden = False, readonly = False, unit = None, default = None, choices = None): - """ - Defines a parameter - name -> the short name of the parameter - type -> the type of the parameter - description -> the description text - named parameters - hidden -> (boolean) true, if the parameter is not shown in the dialog - readonly -> (boolean) true, if the parameter cannot be edited - unit -> the unit string - default -> the default value - choices -> ([ [ d, v ], ...) choice descriptions/value for choice type - this method defines accessor methods for the parameters - {name} -> read accessor - set_{name} -> write accessor ({name}= does not work because the - Ruby confuses that method with variables) - {name}_layer -> read accessor for the layer index for TypeLayer parameters - """ - - # create accessor methods for the parameters - param_index = len(self._param_decls) - setattr(type(self), name, _PCellDeclarationHelperParameterDescriptor(param_index, name)) - - if value_type == type(self).TypeLayer: - setattr(type(self), name + "_layer", _PCellDeclarationHelperLayerDescriptor(len(self._layer_param_index))) - self._layer_param_index.append(param_index) - - # store the parameter declarations - pdecl = pya.PCellParameterDeclaration(name, value_type, description) - self._param_decls.append(pdecl) - - # set additional attributes of the parameters - pdecl.hidden = hidden - pdecl.readonly = readonly - if not (default is None): - pdecl.default = default - if not (unit is None): - pdecl.unit = unit - if not (choices is None): - if not isinstance(choices, list) and not isinstance(choices, tuple): - raise "choices value must be an list/tuple of two-element arrays (description, value)" - for c in choices: - if (not isinstance(choices, list) and not isinstance(choices, tuple)) or len(c) != 2: - raise "choices value must be an list/tuple of two-element arrays (description, value)" - pdecl.add_choice(c[0],c[1]) - - # return the declaration object for further operations - return pdecl - - def display_text(self, parameters): - """ - implementation of display_text - """ - self._param_values = parameters - try: - text = self.display_text_impl() - finally: - self._param_values = None - return text - - def get_parameters(self): - """ - gets the parameters - """ - return self._param_decls - - def get_values(self): - """ - gets the temporary parameter values - """ - v = self._param_values - self._param_values = None - return v - - def init_values(self, values = None, layers = None, states = None): - """ - initializes the temporary parameter values - "values" are the original values. If "None" is given, the - default values will be used. - "layers" are the layer indexes corresponding to the layer - parameters. - """ - self._param_values = None - self._param_states = None - if states: - self._param_states = states - elif not values: - self._param_values = [] - for pd in self._param_decls: - self._param_values.append(pd.default) - else: - self._param_values = values - self._layers = layers - - def finish(self): - """ - Needs to be called at the end of an implementation - """ - self._param_values = None - self._param_states = None - self._layers = None - self._cell = None - self._layout = None - self._layer = None - self._shape = None - - def get_layers(self, parameters): - """ - gets the layer definitions - """ - layers = [] - for i in self._layer_param_index: - layers.append(parameters[i]) - return layers - - def callback(self, layout, name, states): - """ - callback (change state on parameter change) - """ - self.init_values(states = states) - self.layout = layout - try: - self.callback_impl(name) - finally: - self.finish() - - def coerce_parameters(self, layout, parameters): - """ - coerce parameters (make consistent) - """ - self.init_values(parameters) - self.layout = layout - try: - self.coerce_parameters_impl() - parameters = self.get_values() - finally: - self.finish() - return parameters - - def produce(self, layout, layers, parameters, cell): - """ - produces the layout - """ - self.init_values(parameters, layers) - self.cell = cell - self.layout = layout - try: - self.produce_impl() - finally: - self.finish() - - def can_create_from_shape(self, layout, shape, layer): - """ - produce a helper for can_create_from_shape - """ - self.layout = layout - self.shape = shape - self.layer = layer - try: - ret = self.can_create_from_shape_impl() - finally: - self.finish() - return ret - - def transformation_from_shape(self, layout, shape, layer): - """ - produce a helper for parameters_from_shape - """ - self.layout = layout - self.shape = shape - self.layer = layer - try: - t = self.transformation_from_shape_impl() - finally: - self.finish() - return t - - def parameters_from_shape(self, layout, shape, layer): - """ - produce a helper for parameters_from_shape - with this helper, the implementation can use the parameter setters - """ - self.init_values() - self.layout = layout - self.shape = shape - self.layer = layer - try: - self.parameters_from_shape_impl() - param = self.get_values() - finally: - self.finish() - return param - - def display_text_impl(self): - """ - default implementation - """ - return "" - - def coerce_parameters_impl(self): - """ - default implementation - """ - pass - - def callback_impl(self, name): - """ - default implementation - """ - pass - - def produce_impl(self): - """ - default implementation - """ - pass - - def can_create_from_shape_impl(self): - """ - default implementation - """ - return False - - def parameters_from_shape_impl(self): - """ - default implementation - """ - pass - - def transformation_from_shape_impl(self): - """ - default implementation - """ - return pya.Trans() - -# import the Type... constants from PCellParameterDeclaration -for k in dir(pya.PCellParameterDeclaration): - if k.startswith("Type"): - setattr(_PCellDeclarationHelper, k, getattr(pya.PCellParameterDeclaration, k)) - -# Inject the PCellDeclarationHelper into pya module for consistency: -setattr(pya, "PCellDeclarationHelper", _PCellDeclarationHelper) - +# No code provided here. This macro is supplied to provide the documentation. +# The basic code is located in klayout.db.pcell_declaration_helper now. diff --git a/src/pymod/distutils_src/klayout/db/__init__.py b/src/pymod/distutils_src/klayout/db/__init__.py index fee5eb8c9..55498829f 100644 --- a/src/pymod/distutils_src/klayout/db/__init__.py +++ b/src/pymod/distutils_src/klayout/db/__init__.py @@ -1,20 +1,31 @@ -import functools -from typing import Type -import klayout.dbcore -from klayout.dbcore import * -from klayout.db.pcell_declaration_helper import PCellDeclarationHelper +import sys +from ..dbcore import __all__ +from ..dbcore import * -__all__ = klayout.dbcore.__all__ + ["PCellDeclarationHelper"] # type: ignore +from .pcell_declaration_helper import * + +# establish the PCellDeclarationHelper using the mixin provided by _pcell_declaration_helper +class PCellDeclarationHelper(_PCellDeclarationHelperMixin, PCellDeclaration): + def __init__(self): + super().__init__() + def _make_parameter_declaration(self, name, value_type, description): + return PCellParameterDeclaration(name, value_type, description) + def _make_default_trans(self): + return Trans() + +# import the Type... constants from PCellParameterDeclaration +for k in dir(PCellParameterDeclaration): + if k.startswith("Type"): + setattr(PCellDeclarationHelper, k, getattr(PCellParameterDeclaration, k)) # If class has from_s, to_s, and assign, use them to # enable serialization. -for name, cls in klayout.dbcore.__dict__.items(): - if not isinstance(cls, type): - continue - if hasattr(cls, 'from_s') and hasattr(cls, 'to_s') and hasattr(cls, 'assign'): - cls.__getstate__ = cls.to_s # type: ignore - def _setstate(self, str): - cls = self.__class__ - self.assign(cls.from_s(str)) - cls.__setstate__ = _setstate # type: ignore +for name in __all__: + cls = globals()[name] + if hasattr(cls, 'from_s') and hasattr(cls, 'to_s') and hasattr(cls, 'assign'): + cls.__getstate__ = cls.to_s # type: ignore + def _setstate(self, str): + cls = self.__class__ + self.assign(cls.from_s(str)) + cls.__setstate__ = _setstate # type: ignore diff --git a/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py b/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py index f8e5f2c7e..4731b074f 100644 --- a/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py +++ b/src/pymod/distutils_src/klayout/db/pcell_declaration_helper.py @@ -1,5 +1,3 @@ -from klayout.db import Trans, PCellDeclaration, PCellParameterDeclaration - class _PCellDeclarationHelperLayerDescriptor(object): """ @@ -43,16 +41,17 @@ class _PCellDeclarationHelperParameterDescriptor(object): else: self.value = value -class _PCellDeclarationHelper(PCellDeclaration): +class _PCellDeclarationHelperMixin: """ - A helper class that somewhat simplifies the implementation - of a PCell + A mixin class that somewhat simplifies the implementation of a PCell + Needed to build PCellDeclarationHelper """ - def __init__(self): + def __init__(self, *args, **kwargs): """ - initialize this instance + initializes this instance """ + super().__init__(*args, **kwargs) # "private" attributes self._param_decls = [] self._param_values = None @@ -93,7 +92,7 @@ class _PCellDeclarationHelper(PCellDeclaration): self._layer_param_index.append(param_index) # store the parameter declarations - pdecl = PCellParameterDeclaration(name, value_type, description) + pdecl = self._make_parameter_declaration(name, value_type, description) self._param_decls.append(pdecl) # set additional attributes of the parameters @@ -116,24 +115,33 @@ class _PCellDeclarationHelper(PCellDeclaration): def display_text(self, parameters): """ - implementation of display_text + Reimplementation of PCellDeclaration.display_text + + This function delegates the implementation to self.display_text_impl + after configuring the PCellDeclaration object. """ self._param_values = parameters try: text = self.display_text_impl() finally: - self._param_values = None + self.finish() return text def get_parameters(self): """ - gets the parameters + Reimplementation of PCellDeclaration.get_parameters + + This function uses the collected parameters to feed the + PCell declaration. """ return self._param_decls def get_values(self): """ - gets the temporary parameter values + Gets the temporary parameter values used for the current evaluation + + Call this function to get the a current parameter values. This + is an array of variants in the order the parameters are declared. """ v = self._param_values self._param_values = None @@ -141,7 +149,8 @@ class _PCellDeclarationHelper(PCellDeclaration): def init_values(self, values = None, layers = None, states = None): """ - initializes the temporary parameter values + initializes the temporary parameter values for the current evaluation + "values" are the original values. If "None" is given, the default values will be used. "layers" are the layer indexes corresponding to the layer @@ -161,7 +170,7 @@ class _PCellDeclarationHelper(PCellDeclaration): def finish(self): """ - Needs to be called at the end of an implementation + Is called at the end of an implementation of a PCellDeclaration method """ self._param_values = None self._param_states = None @@ -173,7 +182,9 @@ class _PCellDeclarationHelper(PCellDeclaration): def get_layers(self, parameters): """ - gets the layer definitions + Reimplements PCellDeclaration.get_layers. + + Gets the layer definitions from all layer parameters. """ layers = [] for i in self._layer_param_index: @@ -182,7 +193,10 @@ class _PCellDeclarationHelper(PCellDeclaration): def callback(self, layout, name, states): """ - callback (change state on parameter change) + Reimplements PCellDeclaration.callback (change state on parameter change) + + The function delegates the implementation to callback_impl + after updating the state of this object with the current parameters. """ self.init_values(states = states) self.layout = layout @@ -193,7 +207,10 @@ class _PCellDeclarationHelper(PCellDeclaration): def coerce_parameters(self, layout, parameters): """ - coerce parameters (make consistent) + Reimplements PCellDeclaration.coerce parameters (make consistent) + + The function delegates the implementation to coerce_parameters_impl + after updating the state of this object with the current parameters. """ self.init_values(parameters) self.layout = layout @@ -206,7 +223,10 @@ class _PCellDeclarationHelper(PCellDeclaration): def produce(self, layout, layers, parameters, cell): """ - produces the layout + Reimplements PCellDeclaration.produce (produces the layout) + + The function delegates the implementation to produce_impl + after updating the state of this object with the current parameters. """ self.init_values(parameters, layers) self.cell = cell @@ -218,7 +238,10 @@ class _PCellDeclarationHelper(PCellDeclaration): def can_create_from_shape(self, layout, shape, layer): """ - produce a helper for can_create_from_shape + Reimplements PCellDeclaration.can_create_from_shape + + The function delegates the implementation to can_create_from_shape_impl + after updating the state of this object with the current parameters. """ self.layout = layout self.shape = shape @@ -231,21 +254,28 @@ class _PCellDeclarationHelper(PCellDeclaration): def transformation_from_shape(self, layout, shape, layer): """ - produce a helper for parameters_from_shape + Reimplements PCellDeclaration.transformation_from_shape + + The function delegates the implementation to transformation_from_shape_impl + after updating the state of this object with the current parameters. """ self.layout = layout self.shape = shape self.layer = layer try: t = self.transformation_from_shape_impl() + if t is None: + t = self._make_default_trans() finally: self.finish() return t def parameters_from_shape(self, layout, shape, layer): """ - produce a helper for parameters_from_shape - with this helper, the implementation can use the parameter setters + Reimplements PCellDeclaration.parameters_from_shape + + The function delegates the implementation to parameters_from_shape_impl + after updating the state of this object with the current parameters. """ self.init_values() self.layout = layout @@ -298,13 +328,10 @@ class _PCellDeclarationHelper(PCellDeclaration): """ default implementation """ - return Trans() + return None -# import the Type... constants from PCellParameterDeclaration -for k in dir(PCellParameterDeclaration): - if k.startswith("Type"): - setattr(_PCellDeclarationHelper, k, getattr(PCellParameterDeclaration, k)) -# Inject the PCellDeclarationHelper into module for consistency: -PCellDeclarationHelper = _PCellDeclarationHelper +__all__ = [ "_PCellDeclarationHelperLayerDescriptor", + "_PCellDeclarationHelperParameterDescriptor", + "_PCellDeclarationHelperMixin" ] diff --git a/src/pymod/distutils_src/klayout/lay/__init__.py b/src/pymod/distutils_src/klayout/lay/__init__.py index e744d3dea..08443bfdc 100644 --- a/src/pymod/distutils_src/klayout/lay/__init__.py +++ b/src/pymod/distutils_src/klayout/lay/__init__.py @@ -1,6 +1,6 @@ -import klayout.dbcore # enables stream reader plugins -import klayout.laycore -from klayout.laycore import * +from ..dbcore import Layout # enables stream reader plugins + +from ..laycore import __all__ +from ..laycore import * -__all__ = klayout.laycore.__all__ diff --git a/src/pymod/distutils_src/klayout/lib/__init__.py b/src/pymod/distutils_src/klayout/lib/__init__.py index 90fb8d3de..63172d56e 100644 --- a/src/pymod/distutils_src/klayout/lib/__init__.py +++ b/src/pymod/distutils_src/klayout/lib/__init__.py @@ -1,4 +1,4 @@ -import klayout.libcore -from klayout.libcore import * -__all__ = klayout.libcore.__all__ +from ..libcore import __all__ +from ..libcore import * + diff --git a/src/pymod/distutils_src/klayout/rdb/__init__.py b/src/pymod/distutils_src/klayout/rdb/__init__.py index eb48ea888..26ed03eb8 100644 --- a/src/pymod/distutils_src/klayout/rdb/__init__.py +++ b/src/pymod/distutils_src/klayout/rdb/__init__.py @@ -1,4 +1,4 @@ -import klayout.rdbcore -from klayout.rdbcore import * -__all__ = klayout.rdbcore.__all__ +from ..rdbcore import __all__ +from ..rdbcore import * + diff --git a/src/pymod/distutils_src/klayout/tl/__init__.py b/src/pymod/distutils_src/klayout/tl/__init__.py index a4c4287e9..84698bc50 100644 --- a/src/pymod/distutils_src/klayout/tl/__init__.py +++ b/src/pymod/distutils_src/klayout/tl/__init__.py @@ -1,4 +1,4 @@ -import klayout.tlcore -from klayout.tlcore import * -__all__ = klayout.tlcore.__all__ +from ..tlcore import __all__ +from ..tlcore import * + diff --git a/src/pymod/distutils_src/pya/__init__.py b/src/pymod/distutils_src/pya/__init__.py index df2cefac7..a822c6177 100644 --- a/src/pymod/distutils_src/pya/__init__.py +++ b/src/pymod/distutils_src/pya/__init__.py @@ -2,3 +2,20 @@ # pull everything from the generic klayout.pya package into pya from klayout.pya import __all__ from klayout.pya import * + +from klayout.db.pcell_declaration_helper import * + +# establish the PCellDeclarationHelper using the mixin provided by _pcell_declaration_helper +class PCellDeclarationHelper(_PCellDeclarationHelperMixin, PCellDeclaration): + def __init__(self): + super().__init__() + def _make_parameter_declaration(self, name, value_type, description): + return PCellParameterDeclaration(name, value_type, description) + def _make_default_trans(self): + return Trans() + +# import the Type... constants from PCellParameterDeclaration +for k in dir(PCellParameterDeclaration): + if k.startswith("Type"): + setattr(PCellDeclarationHelper, k, getattr(PCellParameterDeclaration, k)) + diff --git a/src/pymod/unit_tests/pymod_tests.cc b/src/pymod/unit_tests/pymod_tests.cc index 693b81cb4..88cc5d7f4 100644 --- a/src/pymod/unit_tests/pymod_tests.cc +++ b/src/pymod/unit_tests/pymod_tests.cc @@ -90,6 +90,7 @@ PYMODTEST (bridge, "bridge.py") PYMODTEST (import_tl, "import_tl.py") PYMODTEST (import_db, "import_db.py") +PYMODTEST (klayout_db_tests, "klayout_db_tests.py") PYMODTEST (import_rdb, "import_rdb.py") PYMODTEST (import_lay, "import_lay.py") PYMODTEST (import_lib, "import_lib.py") diff --git a/testdata/pymod/klayout_db_tests.py b/testdata/pymod/klayout_db_tests.py new file mode 100644 index 000000000..725cde541 --- /dev/null +++ b/testdata/pymod/klayout_db_tests.py @@ -0,0 +1,502 @@ +# KLayout Layout Viewer +# Copyright (C) 2006-2023 Matthias Koefferlein +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import klayout.db +import unittest +import sys + +class BoxPCell(klayout.db.PCellDeclaration): + + def display_text(self, parameters): + # provide a descriptive text for the cell + return "Box(L=" + str(parameters[0]) + ",W=" + ('%.3f' % parameters[1]) + ",H=" + ('%.3f' % parameters[2]) + ")" + + def get_parameters(self): + + # prepare a set of parameter declarations + param = [] + + param.append(klayout.db.PCellParameterDeclaration("l", klayout.db.PCellParameterDeclaration.TypeLayer, "Layer", klayout.db.LayerInfo(0, 0))) + param.append(klayout.db.PCellParameterDeclaration("w", klayout.db.PCellParameterDeclaration.TypeDouble, "Width", 1.0)) + param.append(klayout.db.PCellParameterDeclaration("h", klayout.db.PCellParameterDeclaration.TypeDouble, "Height", 1.0)) + + return param + + + def get_layers(self, parameters): + return [ parameters[0] ] + + def produce(self, layout, layers, parameters, cell): + + dbu = layout.dbu + + # fetch the parameters + l = parameters[0] + w = parameters[1] / layout.dbu + h = parameters[2] / layout.dbu + + # create the shape + cell.shapes(layers[0]).insert(klayout.db.Box(-w / 2, -h / 2, w / 2, h / 2)) + + def can_create_from_shape(self, layout, shape, layer): + return shape.is_box() + + def transformation_from_shape(self, layout, shape, layer): + return klayout.db.Trans(shape.box.center() - klayout.db.Point()) + + def parameters_from_shape(self, layout, shape, layer): + return [ layout.get_info(layer), shape.box.width() * layout.dbu, shape.box.height() * layout.dbu ] + +class PCellTestLib(klayout.db.Library): + + def __init__(self): + + # set the description + self.description = "PCell test lib" + + # create the PCell declarations + self.layout().register_pcell("Box", BoxPCell()) + + sb_index = self.layout().add_cell("StaticBox") + l10 = self.layout().insert_layer(klayout.db.LayerInfo(10, 0)) + sb_cell = self.layout().cell(sb_index) + sb_cell.shapes(l10).insert(klayout.db.Box(0, 0, 100, 200)) + + # register us with the name "MyLib" + self.register("PCellTestLib") + + +# A PCell based on the declaration helper + +class BoxPCell2(klayout.db.PCellDeclarationHelper): + + def __init__(self): + + super(BoxPCell2, self).__init__() + + self.param("layer", self.TypeLayer, "Layer", default = klayout.db.LayerInfo(0, 0)) + self.param("width", self.TypeDouble, "Width", default = 1.0) + self.param("height", self.TypeDouble, "Height", default = 1.0) + + def display_text_impl(self): + # provide a descriptive text for the cell + return "Box2(L=" + str(self.layer) + ",W=" + ('%.3f' % self.width) + ",H=" + ('%.3f' % self.height) + ")" + + def wants_lazy_evaluation(self): + return True + + def produce_impl(self): + + dbu = self.layout.dbu + + # fetch the parameters + l = self.layer_layer + w = self.width / self.layout.dbu + h = self.height / self.layout.dbu + + # create the shape + self.cell.shapes(l).insert(klayout.db.Box(-w / 2, -h / 2, w / 2, h / 2)) + + def can_create_from_shape_impl(self): + return self.shape.is_box() + + def transformation_from_shape_impl(self): + return klayout.db.Trans(self.shape.box.center() - klayout.db.Point()) + + def parameters_from_shape_impl(self): + self.layer = self.layout.get_info(self.layer) + self.width = self.shape.box.width() * self.layout.dbu + self.height = self.shape.box.height() * self.layout.dbu + +class PCellTestLib2(klayout.db.Library): + + def __init__(self): + + # set the description + self.description = "PCell test lib2" + + # create the PCell declarations + self.layout().register_pcell("Box2", BoxPCell2()) + + # register us with the name "MyLib" + self.register("PCellTestLib2") + + +def inspect_LayerInfo(self): + return "<" + str(self) + ">" + +klayout.db.LayerInfo.__repr__ = inspect_LayerInfo + +def find_layer(ly, lp): + + for li in ly.layer_indices(): + if str(ly.get_info(li)) == lp: + return li + return None + + +def nh(h): + """ + Returns a normalized hash representation + """ + v = [] + for k in sorted(h): + v.append(repr(k) + ": " + repr(h[k])) + return "{" + (", ".join(v)) + "}" + +class DBPCellTests(unittest.TestCase): + + def test_1(self): + + # instantiate and register the library + tl = PCellTestLib() + + ly = klayout.db.Layout(True) + ly.dbu = 0.01 + + li1 = find_layer(ly, "1/0") + self.assertEqual(li1 == None, True) + + ci1 = ly.add_cell("c1") + c1 = ly.cell(ci1) + + lib = klayout.db.Library.library_by_name("NoLib") + self.assertEqual(lib == None, True) + lib = klayout.db.Library.library_by_name("PCellTestLib") + self.assertEqual(lib != None, True) + pcell_decl = lib.layout().pcell_declaration("x") + self.assertEqual(pcell_decl == None, True) + pcell_decl = lib.layout().pcell_declaration("Box") + self.assertEqual(pcell_decl != None, True) + pcell_decl_id = lib.layout().pcell_id("Box") + self.assertEqual(pcell_decl.id(), pcell_decl_id) + self.assertEqual(":".join(lib.layout().pcell_names()), "Box") + self.assertEqual(lib.layout().pcell_ids(), [ pcell_decl_id ]) + self.assertEqual(lib.layout().pcell_declaration(pcell_decl_id).id(), pcell_decl_id) + + param = [ klayout.db.LayerInfo(1, 0) ] # rest is filled with defaults + pcell_var_id = ly.add_pcell_variant(lib, pcell_decl_id, param) + pcell_var = ly.cell(pcell_var_id) + pcell_inst = c1.insert(klayout.db.CellInstArray(pcell_var_id, klayout.db.Trans())) + self.assertEqual(pcell_var.layout().__repr__(), ly.__repr__()) + self.assertEqual(pcell_var.library().__repr__(), lib.__repr__()) + self.assertEqual(pcell_var.is_pcell_variant(), True) + self.assertEqual(pcell_var.display_title(), "PCellTestLib.Box(L=1/0,W=1.000,H=1.000)") + self.assertEqual(pcell_var.basic_name(), "Box") + self.assertEqual(pcell_var.pcell_declaration().wants_lazy_evaluation(), False) + self.assertEqual(c1.is_pcell_variant(), False) + self.assertEqual(c1.is_pcell_variant(pcell_inst), True) + self.assertEqual(pcell_var.pcell_id(), pcell_decl_id) + self.assertEqual(pcell_var.pcell_library().__repr__(), lib.__repr__()) + self.assertEqual(pcell_var.pcell_parameters().__repr__(), "[<1/0>, 1.0, 1.0]") + self.assertEqual(nh(pcell_var.pcell_parameters_by_name()), "{'h': 1.0, 'l': <1/0>, 'w': 1.0}") + self.assertEqual(pcell_var.pcell_parameter("h").__repr__(), "1.0") + self.assertEqual(c1.pcell_parameters(pcell_inst).__repr__(), "[<1/0>, 1.0, 1.0]") + self.assertEqual(nh(c1.pcell_parameters_by_name(pcell_inst)), "{'h': 1.0, 'l': <1/0>, 'w': 1.0}") + self.assertEqual(c1.pcell_parameter(pcell_inst, "h").__repr__(), "1.0") + self.assertEqual(nh(pcell_inst.pcell_parameters_by_name()), "{'h': 1.0, 'l': <1/0>, 'w': 1.0}") + self.assertEqual(pcell_inst["h"].__repr__(), "1.0") + self.assertEqual(pcell_inst["i"].__repr__(), "None") + self.assertEqual(pcell_inst.pcell_parameter("h").__repr__(), "1.0") + self.assertEqual(pcell_var.pcell_declaration().__repr__(), pcell_decl.__repr__()) + self.assertEqual(c1.pcell_declaration(pcell_inst).__repr__(), pcell_decl.__repr__()) + self.assertEqual(pcell_inst.pcell_declaration().__repr__(), pcell_decl.__repr__()) + + pcell_inst.change_pcell_parameter("h", 2.0) + self.assertEqual(nh(pcell_inst.pcell_parameters_by_name()), "{'h': 2.0, 'l': <1/0>, 'w': 1.0}") + pcell_inst.set_property("abc", "a property") + self.assertEqual(pcell_inst.property("abc").__repr__(), "'a property'") + + c1.clear() + + param = [ klayout.db.LayerInfo(1, 0), 5.0, 10.0 ] + pcell_var_id = ly.add_pcell_variant(lib, pcell_decl_id, param) + pcell_var = ly.cell(pcell_var_id) + pcell_inst = c1.insert(klayout.db.CellInstArray(pcell_var_id, klayout.db.Trans())) + self.assertEqual(pcell_var.layout().__repr__(), ly.__repr__()) + self.assertEqual(pcell_var.library().__repr__(), lib.__repr__()) + self.assertEqual(pcell_var.is_pcell_variant(), True) + self.assertEqual(pcell_var.display_title(), "PCellTestLib.Box(L=1/0,W=5.000,H=10.000)") + self.assertEqual(pcell_var.basic_name(), "Box") + self.assertEqual(c1.is_pcell_variant(), False) + self.assertEqual(c1.is_pcell_variant(pcell_inst), True) + self.assertEqual(pcell_var.pcell_id(), pcell_decl_id) + self.assertEqual(pcell_var.pcell_library().__repr__(), lib.__repr__()) + self.assertEqual(pcell_var.pcell_parameters().__repr__(), "[<1/0>, 5.0, 10.0]") + self.assertEqual(c1.pcell_parameters(pcell_inst).__repr__(), "[<1/0>, 5.0, 10.0]") + self.assertEqual(pcell_inst.pcell_parameters().__repr__(), "[<1/0>, 5.0, 10.0]") + self.assertEqual(pcell_var.pcell_declaration().__repr__(), pcell_decl.__repr__()) + self.assertEqual(c1.pcell_declaration(pcell_inst).__repr__(), pcell_decl.__repr__()) + + li1 = find_layer(ly, "1/0") + self.assertEqual(li1 != None, True) + self.assertEqual(ly.is_valid_layer(li1), True) + self.assertEqual(str(ly.get_info(li1)), "1/0") + + lib_proxy_id = ly.add_lib_cell(lib, lib.layout().cell_by_name("StaticBox")) + lib_proxy = ly.cell(lib_proxy_id) + self.assertEqual(lib_proxy.display_title(), "PCellTestLib.StaticBox") + self.assertEqual(lib_proxy.basic_name(), "StaticBox") + self.assertEqual(lib_proxy.layout().__repr__(), ly.__repr__()) + self.assertEqual(lib_proxy.library().__repr__(), lib.__repr__()) + self.assertEqual(lib_proxy.is_pcell_variant(), False) + self.assertEqual(lib.layout().cell(lib.layout().cell_by_name("StaticBox")).library().__repr__(), "None") + + li2 = find_layer(ly, "10/0") + self.assertEqual(li2 != None, True) + + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-250,-500;250,500)") + self.assertEqual(ly.begin_shapes(lib_proxy.cell_index(), li2).shape().__str__(), "box (0,0;10,20)") + + param = { "w": 1, "h": 2 } + c1.change_pcell_parameters(pcell_inst, param) + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-50,-100;50,100)") + + param = [ klayout.db.LayerInfo(1, 0), 5.0, 5.0 ] + c1.change_pcell_parameters(pcell_inst, param) + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-250,-250;250,250)") + + pcell_inst.change_pcell_parameters({ "w": 2.0, "h": 10.0 }) + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-100,-500;100,500)") + + pcell_inst.change_pcell_parameters([ klayout.db.LayerInfo(1, 0), 5.0, 5.0 ]) + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-250,-250;250,250)") + + pcell_inst.change_pcell_parameter("w", 5.0) + pcell_inst.change_pcell_parameter("h", 1.0) + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-250,-50;250,50)") + + c1.change_pcell_parameter(pcell_inst, "w", 10.0) + c1.change_pcell_parameter(pcell_inst, "h", 2.0) + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-500,-100;500,100)") + + self.assertEqual(ly.cell(pcell_inst.cell_index).is_pcell_variant(), True) + self.assertEqual(pcell_inst.is_pcell(), True) + new_id = ly.convert_cell_to_static(pcell_inst.cell_index) + self.assertEqual(new_id == pcell_inst.cell_index, False) + self.assertEqual(ly.cell(new_id).is_pcell_variant(), False) + param = [ klayout.db.LayerInfo(1, 0), 5.0, 5.0 ] + c1.change_pcell_parameters(pcell_inst, param) + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-250,-250;250,250)") + pcell_inst.cell_index = new_id + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-500,-100;500,100)") + + l10 = ly.layer(10, 0) + c1.shapes(l10).insert(klayout.db.Box(0, 10, 100, 210)) + l11 = ly.layer(11, 0) + c1.shapes(l11).insert(klayout.db.Text("hello", klayout.db.Trans())) + self.assertEqual(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l11).shape(), l10), False) + self.assertEqual(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10), True) + self.assertEqual(repr(pcell_decl.parameters_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10)), "[<10/0>, 1.0, 2.0]") + self.assertEqual(str(pcell_decl.transformation_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10)), "r0 50,110") + + + def test_1a(self): + + if not "PCellDeclarationHelper" in klayout.db.__dict__: + return + + # instantiate and register the library + tl = PCellTestLib2() + + ly = klayout.db.Layout(True) + ly.dbu = 0.01 + + li1 = find_layer(ly, "1/0") + self.assertEqual(li1 == None, True) + + ci1 = ly.add_cell("c1") + c1 = ly.cell(ci1) + + lib = klayout.db.Library.library_by_name("PCellTestLib2") + self.assertEqual(lib != None, True) + pcell_decl = lib.layout().pcell_declaration("Box2") + + param = [ klayout.db.LayerInfo(1, 0) ] # rest is filled with defaults + pcell_var_id = ly.add_pcell_variant(lib, pcell_decl.id(), param) + pcell_var = ly.cell(pcell_var_id) + pcell_inst = c1.insert(klayout.db.CellInstArray(pcell_var_id, klayout.db.Trans())) + self.assertEqual(pcell_var.basic_name(), "Box2") + self.assertEqual(pcell_var.pcell_parameters().__repr__(), "[<1/0>, 1.0, 1.0]") + self.assertEqual(pcell_var.display_title(), "PCellTestLib2.Box2(L=1/0,W=1.000,H=1.000)") + self.assertEqual(nh(pcell_var.pcell_parameters_by_name()), "{'height': 1.0, 'layer': <1/0>, 'width': 1.0}") + self.assertEqual(pcell_var.pcell_parameter("height").__repr__(), "1.0") + self.assertEqual(c1.pcell_parameters(pcell_inst).__repr__(), "[<1/0>, 1.0, 1.0]") + self.assertEqual(nh(c1.pcell_parameters_by_name(pcell_inst)), "{'height': 1.0, 'layer': <1/0>, 'width': 1.0}") + self.assertEqual(c1.pcell_parameter(pcell_inst, "height").__repr__(), "1.0") + self.assertEqual(nh(pcell_inst.pcell_parameters_by_name()), "{'height': 1.0, 'layer': <1/0>, 'width': 1.0}") + self.assertEqual(pcell_inst["height"].__repr__(), "1.0") + self.assertEqual(pcell_inst.pcell_parameter("height").__repr__(), "1.0") + self.assertEqual(pcell_var.pcell_declaration().__repr__(), pcell_decl.__repr__()) + self.assertEqual(c1.pcell_declaration(pcell_inst).__repr__(), pcell_decl.__repr__()) + self.assertEqual(pcell_inst.pcell_declaration().__repr__(), pcell_decl.__repr__()) + self.assertEqual(pcell_decl.wants_lazy_evaluation(), True) + + li1 = find_layer(ly, "1/0") + self.assertEqual(li1 == None, False) + pcell_inst.change_pcell_parameter("height", 2.0) + self.assertEqual(nh(pcell_inst.pcell_parameters_by_name()), "{'height': 2.0, 'layer': <1/0>, 'width': 1.0}") + + self.assertEqual(ly.begin_shapes(c1.cell_index(), li1).shape().__str__(), "box (-50,-100;50,100)") + + param = { "layer": klayout.db.LayerInfo(2, 0), "width": 2, "height": 1 } + li2 = ly.layer(2, 0) + c1.change_pcell_parameters(pcell_inst, param) + self.assertEqual(ly.begin_shapes(c1.cell_index(), li2).shape().__str__(), "box (-100,-50;100,50)") + + l10 = ly.layer(10, 0) + c1.shapes(l10).insert(klayout.db.Box(0, 10, 100, 210)) + l11 = ly.layer(11, 0) + c1.shapes(l11).insert(klayout.db.Text("hello", klayout.db.Trans())) + self.assertEqual(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l11).shape(), l10), False) + self.assertEqual(pcell_decl.can_create_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10), True) + self.assertEqual(repr(pcell_decl.parameters_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10)), "[<10/0>, 1.0, 2.0]") + self.assertEqual(str(pcell_decl.transformation_from_shape(ly, ly.begin_shapes(c1.cell_index(), l10).shape(), l10)), "r0 50,110") + + + def test_2(self): + + # instantiate and register the library + tl = PCellTestLib() + + ly = klayout.db.Layout(True) + ly.dbu = 0.01 + + ci1 = ly.add_cell("c1") + c1 = ly.cell(ci1) + + lib = klayout.db.Library.library_by_name("PCellTestLib") + pcell_decl_id = lib.layout().pcell_id("Box") + + param = [ klayout.db.LayerInfo(1, 0), 10.0, 2.0 ] + pcell_var_id = ly.add_pcell_variant(lib, pcell_decl_id, param) + pcell_var = ly.cell(pcell_var_id) + pcell_inst = c1.insert(klayout.db.CellInstArray(pcell_var_id, klayout.db.Trans())) + + li1 = find_layer(ly, "1/0") + self.assertEqual(li1 != None, True) + self.assertEqual(ly.is_valid_layer(li1), True) + self.assertEqual(str(ly.get_info(li1)), "1/0") + + self.assertEqual(pcell_inst.is_pcell(), True) + + self.assertEqual(ly.begin_shapes(pcell_inst.cell_index, li1).shape().__str__(), "box (-500,-100;500,100)") + pcell_inst.convert_to_static() + self.assertEqual(pcell_inst.is_pcell(), False) + self.assertEqual(ly.begin_shapes(pcell_inst.cell_index, li1).shape().__str__(), "box (-500,-100;500,100)") + pcell_inst.convert_to_static() + self.assertEqual(pcell_inst.is_pcell(), False) + self.assertEqual(ly.begin_shapes(pcell_inst.cell_index, li1).shape().__str__(), "box (-500,-100;500,100)") + + def test_3(self): + + # instantiate and register the library + tl = PCellTestLib() + + ly = klayout.db.Layout(True) + ly.dbu = 0.01 + + c1 = ly.create_cell("c1") + + lib = klayout.db.Library.library_by_name("PCellTestLib") + pcell_decl_id = lib.layout().pcell_id("Box") + + param = { "w": 4.0, "h": 8.0, "l": klayout.db.LayerInfo(1, 0) } + pcell_var_id = ly.add_pcell_variant(lib, pcell_decl_id, param) + pcell_var = ly.cell(pcell_var_id) + pcell_inst = c1.insert(klayout.db.CellInstArray(pcell_var_id, klayout.db.Trans())) + + self.assertEqual(ly.begin_shapes(c1.cell_index(), ly.layer(1, 0)).shape().__str__(), "box (-200,-400;200,400)") + + def test_4(self): + + # instantiate and register the library + tl = PCellTestLib() + + lib = klayout.db.Library.library_by_name("PCellTestLib") + pcell_decl_id = lib.layout().pcell_id("Box") + + param = { "w": 4.0, "h": 8.0, "l": klayout.db.LayerInfo(1, 0) } + pcell_var_id = lib.layout().add_pcell_variant(pcell_decl_id, param) + + self.assertEqual(lib.layout().begin_shapes(pcell_var_id, lib.layout().layer(1, 0)).shape().__str__(), "box (-2000,-4000;2000,4000)") + + def test_5(self): + + # instantiate and register the library + tl = PCellTestLib() + + lib = klayout.db.Library.library_by_name("PCellTestLib") + pcell_decl_id = lib.layout().pcell_id("Box") + + param = { "w": 3.0, "h": 7.0, "l": klayout.db.LayerInfo(2, 0) } + pcell_var_id = lib.layout().add_pcell_variant(pcell_decl_id, param) + + self.assertEqual(lib.layout().begin_shapes(pcell_var_id, lib.layout().layer(2, 0)).shape().__str__(), "box (-1500,-3500;1500,3500)") + + + def test_6(self): + + # instantiate and register the library + tl = PCellTestLib() + + lib = klayout.db.Library.library_by_name("PCellTestLib") + + param = { "w": 3.0, "h": 8.0, "l": klayout.db.LayerInfo(3, 0) } + pcell_var = lib.layout().create_cell("Box", param) + + self.assertEqual(lib.layout().begin_shapes(pcell_var.cell_index(), lib.layout().layer(3, 0)).shape().__str__(), "box (-1500,-4000;1500,4000)") + + + def test_7(self): + + # instantiate and register the library + tl = PCellTestLib() + + ly = klayout.db.Layout(True) + ly.dbu = 0.01 + + param = { "w": 4.0, "h": 8.0, "l": klayout.db.LayerInfo(4, 0) } + cell = ly.create_cell("Box", "PCellTestLib", param) + + self.assertEqual(ly.begin_shapes(cell, ly.layer(4, 0)).shape().__str__(), "box (-200,-400;200,400)") + + def test_8(self): + + # instantiate and register the library + tl = PCellTestLib() + + lib = klayout.db.Library.library_by_name("PCellTestLib") + ly = klayout.db.Layout(True) + ly.dbu = 0.01 + + param = { "w": 2.0, "h": 6.0, "l": klayout.db.LayerInfo(5, 0) } + pcell_var = lib.layout().create_cell("Box", param) + pcell_var.name = "BOXVAR" + + cell = ly.create_cell("BOXVAR", "PCellTestLib") + + self.assertEqual(cell.begin_shapes_rec(ly.layer(5, 0)).shape().__str__(), "box (-100,-300;100,300)") + +# run unit tests +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(DBPCellTests) + + if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful(): + sys.exit(1) +