# This file is part of KLayoutPhotonicPCells, an extension for Photonic Layouts in KLayout.
# Copyright (c) 2018, Sebastian Goeldi
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Photonic PCell-Extension Module
.. warning ::
Before using this module for the first time, make sure the `kppc.drc.slcleaner` submodule is compiled and importable, as this module
relies on the drc package for DR-Cleaning. See :py:mod:`drc` for further details.
A Module which provides extensions for standard KLyaout-PCells. This extension mainly provides functionalities for
photonics. One main feature of photonics are so-called ports. These define a position and a direction on a Cell.
They indicate where multiple Cells/Devices should interact with each other. For example, one can connect a waveguide
with a linear taper. This module provides the classes and functions for this functionality. Additionally, this module
provides a lot of convenience functions for interactions with the KLayout-API.
The main functionality for this module is in the class :class:`~kppc.photonics.PhotDevice`.
.. warning :: When using this module to extend a PCell-Library any PCell class has to assign valid values to the
parameters ``layermap`` , ``dataprep_config`` , ``clean_rules`` . These are accessed by :py:class:`~kppc.photonics.PhotDevice`. If they
aren't declared, a runtime error will occur.
"""
# make sure the setup script is compiled
import pya
import kppc.drc
import kppc.photonics.dataprep
from time import clock
import kppc
import re
# Class with port information.
[docs]class PortCreation:
"""Custom Class similar to a namedtuple
This will hold informations for creating ports.
:param x: x Coordinate [microns]
:type x: int
:param y: y Coordinate [microns]
:type y: int
:param rot: Rotation in degrees
:type int: int
:param length: Port length [microns]
:type length: float
"""
def __init__(self,x, y, rot, length, name=''):
self.x = x
self.y = y
self.rot = rot
self.length = length
self.name = name
[docs]def is_named_tuple_instance(x):
"""Test if something is a named tuple
This allows to test if `x` is a port (PortCreation object) or just a list of instance descriptions
"""
typ = type(x)
base = typ.__bases__
if len(base) != 1 or base[0] != tuple: return False
fields = getattr(typ, '_fields', None)
if not isinstance(fields, tuple): return False
return all(type(n) == str for n in fields)
[docs]class InstanceHolder:
"""Class to keep track and hold the information of a pcell instance. The information will be processed to a PCell in
:func:`~kppc.photonics.PhotDevice.produce_impl`
"""
def __init__(self, cell_name, lib, pcell_decl, params=None, params_mod=None, id=0):
"""
Initialize the class
:param cell_name: The name of the cell which will be instantiated
:param lib: Library from which the cell will be instantiated
:param pcell_decl: the pya.PCellDeclaration object reference
:param params: The unmodified parameters of the object
:param params_mod: The coerced parameter set
:param id: id of the object. Ids determine the order in which the instances are created
"""
self.id = id
self.params = params
self.params_mod = params_mod
self.movement = None
self.connection = None
self.connection_port = None
self.port_to_connect = None
self.cell_name = cell_name
self.lib = lib
self.pcell_decl = pcell_decl
self.placed = False
[docs] def port_to_port(self, port: int, inst_holder: tuple):
"""Attach one of this instance's ports to another instance's port.
:param port: port of this instance
:param inst_holder: Tuple of the the reference to the other instance and the port to connect to.
This is a tuple returned from <InstanceHolder object>.port(<portnumber>).
"""
if self.movement:
raise ValueError('Cannot move an instance and connect to a port as these operations are mutually exlusive')
self.connection = inst_holder[0]
self.connection_port = inst_holder[1]
self.port_to_connect = port
[docs] def port(self, port: int):
"""
Returns a reference to itself and the port number. No checks are made whether this port is valid or not!
Available ports can be seen if such an object is instantiated.
:param port: index of the port
:return: self, port
"""
return self, port
[docs] def move(self, x: float = 0, y: float = 0, rot: int = 0, mirrx: bool = False, mag: float = 1):
"""Moves an instance. Units of microns relative to origin.
:param x: x position where to move
:type x: float
:param y: y position where to move
:type y: float
:param rot: Rotation of the object in degrees
:type rot: int
:param mirrx: Mirror at x-axis if True
:type mirrx: bool
:param mag: Magnification of the Cell. This feature is not tested well.
:type mag: float
"""
if self.connection:
raise ValueError(
'Cannot connect to a port of another instance and move the same instance. These operations are mutually exlusive')
self.movement = pya.DCplxTrans(mag, rot, mirrx, x, y)
[docs]class PhotDevice(pya.PCellDeclarationHelper):
"""Wrapper for calls to the Klayout API.
:ivar dict layermap: The layermap dictionary. This value has to be written by a child class. If undefined this
class won't work and crash.
:ivar str dataprep_config: String with the path to the file containing the dataprep instructions.
If left empty, dataprep will do nothing.
:ivar list clean_rules: String with the path to the file containing the DR-Cleaning rules.
If left empty, DR-Cleaning will do nothing. If the cells are built similar to the FreePDK45-SampleCells example,
DR-Cleaning will not work without dataprep, or will be without any effect.
:ivar bool keep: Parameter created during :func:`__init__` via pya.DeclarationHelper. If set to True in the PCell,
all child-cells will be preserved at the end. If set to False only the Dataprep Sub-Cell will be preserved.
:ivar bool dataprep: If this flag is set, :func:`kppc.photonics.dataprep.dataprep` will be performed on the cell. The variable
:attr:`dataprep_config` holds the path to the instructions for dataprep.
:ivar bool clean: If this flag is set, :func:`kppc.drc.clean` will be performed on the cell. Rules for the DR-Cleaning
are pulled from :attr:`clean_rules`.
:ivar bool top: Hidden parameter that indicates whether this cell is a top_cell. Default is yes. When an instance is
added through :meth:`.add_pcell_variant` these cells will not be set to top_cells as they are instantiated from
another cell.
:ivar only_top_ports: GUI parameter. If set to true, only ports of the top most hierarchy level (top_cell) will be
annotated by text.
"""
def __init__(self):
"""
Initialization of the class. This will add automatically several PCell parameters to the class as documented in
the class.
"""
pya.PCellDeclarationHelper.__init__(self)
self.param("portlist", self.TypeString, "Portlist", hidden=not kppc.settings.General.Debug, default="", readonly=not kppc.settings.General.Debug)
self.param("transformations", self.TypeString, "Child PCell Transformations", hidden=not kppc.settings.General.Debug, default="",
readonly=not kppc.settings.General.Debug)
# Disable the option to flatten cell for the moment
self.param("keep", self.TypeBoolean, "Keep original Cell?", hidden=True, default=True, readonly=True)
self.param('dataprep', self.TypeBoolean, "Data Prep?", hidden=False, default=False, readonly=False)
self.param('drc_clean', self.TypeBoolean, 'Clean DRC violations?', hidden=False, default=False,
readonly=False)
self.param('top', self.TypeBoolean, 'Is top cell?', hidden=True, default=True, readonly=True)
self.param('only_top_ports', self.TypeBoolean, 'Show only Ports of Top Cell?', hidden=False, default=True,
readonly=False)
self.layermap = None
self.dataprep_config = None
self.clean_rules = None
self.transformations = None
[docs] def get_layer(self, name: str, purpose: str = ''):
"""Creates LayerInfo object
Creates a pya.LayerInfo object to find layer indexes in the current layout.
:param name: name of the layer
:param purpose: if not empty then layer and purpose are separate
:return: pya.LayerInfo about the layer
"""
if purpose:
layer = self.layermap[name][purpose]
else:
layer = self.layermap[name.split('.')[0]][name.split('.')[1]]
return pya.LayerInfo(int(layer[0]), int(layer[1]), name)
[docs] def connect_port_to_port(self, port1: tuple, port2: tuple):
""" Connect Ports from two InstanceHolder instances.
Connect two `InstanceHolders` together. Attach <InstanceHolder instance1>.port(<port1>) to <InstanceHolder instance2>.port(<port2>).
This will apply a transformation to Instance2. There can only be either a transformation through connect_port_to_port or through InstanceHolder.move
:param port1: <InstanceHolder instance1>.port(<port1>)
:param port2: <InstanceHolder instance2>.port(<port2>)
"""
port2[0].port_to_port(port2[1], port1)
[docs] def flip_shape_xaxis(self, shape: 'pya.Shape'):
"""Flip a polygon (or any shape) at the x-axis
:param shape: pya.Shape object (e.g. through photonicpcell.create_polygon obtained)
"""
shape.transform(pya.ICplxTrans.M0)
[docs] def flip_shape_yaxis(self, shape: 'pya.Shape'):
"""Flip a polygon (or any shape) at the y-axis
:param shape: pya.Shape object (e.g. through photonicpcell.create_polygon obtained)
"""
shape.transform(pya.ICplxTrans.M90)
[docs] def create_port(self, x: float, y: float, rot: int = 0, length: int = 0, name: str = None):
"""Creates a Port at the specified coordinates.
This function will be used when a port is created through the PortCreation tuple.
:param x: x Coordinate in microns
:param y: y Coordinate in microns
:param rot: Rotation in degrees
:param length: length of the port in microns
"""
# Append a serialized transformation to the code.
if self.portlist != "":
self.portlist += ";"
trans = pya.CplxTrans(1, int(rot), False, int(x / self.layout.dbu), int(y / self.layout.dbu))
self.portlist += trans.to_s()
self.portlist += ":"
self.portlist += str(int(length / self.layout.dbu))
if name:
self.portlist += f":name={name}"
else:
ori = 'E'
if 45<=rot<135:
ori = 'N'
elif 125<=rot<225:
ori = 'W'
elif 215<=rot<315:
ori = 'S'
self.portlist += f":name={ori}{{n}}"
[docs] def clear_ports(self):
"""Clears self.portlist and by that delete all ports. This is used when updating the Ports
"""
# Delete all ports in the string
self.portlist = ""
[docs] def connect_port(self, pos1: int, portlist1: str, port1: int, pos2: int, portlist2: str, port2: int) -> int:
""" Connect ports of two instances. The second instance will be transformed to attach to the first instance.
:param pos1: index of instance1
:param portlist1: portlist of instance1
:param port1: port number of instance1
:param pos2: index of instance2
:param portlist2: portlist of instance2
:param port2: port number of instance2
"""
p1, l1 = portlist1.split(';')[port1].split(':')[:2]
p2, l2 = portlist2.split(';')[port2].split(':')[:2]
if l1 != l2:
return -1
child_trans = self.transformations.split(';')
trans1 = pya.ICplxTrans.from_s(child_trans[pos1]) * pya.ICplxTrans.from_s(p1)
if trans1.is_mirror():
trans1.mirror = False
trans2 = pya.ICplxTrans.R180 * pya.ICplxTrans.from_s(p2).inverted()
child_trans[pos2] = pya.ICplxTrans.to_s(trans1 * trans2)
transformation = ''
for i, string in enumerate(child_trans):
transformation += string
if i != len(child_trans) - 1:
transformation += ';'
self.transformations = transformation
return 0
[docs] def create_polygon(self, points: 'list or pya.DPolygon', layer: int):
"""Creates a Polygon and adjusts from microns to database units. Format: [[x1,y1],[x2,y2],...] in microns
:param points: Points defining the corners of the polygon.
:param layer: layer_index of the target layer
:return: reference to polygon object
"""
if isinstance(points, pya.DPolygon) or isinstance(points, pya.Polygon):
self.cell.shapes(self.layout.find_layer(layer)).insert(points)
else:
pts = []
for p in points:
pts.append(pya.DPoint(p[0], p[1]))
return self.cell.shapes(self.layout.find_layer(layer)).insert(pya.DPolygon(pts))
[docs] def create_path(self, points: list, width: float, layer: 'pya.LayerInfo'):
"""Creates a pya.Path object and inserts it into the Library-PCell.
:param points: The points describing the path [[x1,y1],[x2,y2],...] in microns
:param width: Path width
:param layer: layer on which the path should be made
"""
pts = []
for p in points:
pts.append(pya.Point(pya.DPoint(p[0] / self.layout.dbu, p[1] / self.layout.dbu)))
w = int(width / self.layout.dbu)
self.cell.shapes(self.layout.find_layer(layer)).insert(pya.Path(pts, w))
[docs] def insert_shape(self, shape: pya.Shape, layer: 'pya.LayerInfo'):
"""Any other Klayout shape can be added to the PCell through this function.
:param shape: pya.Shape object
:param layer: layer where to write to
:return: reference to shape
"""
return self.cell.shapes(self.layout.find_layer(layer)).insert(shape)
[docs] def add_layer(self, var_name: str, name: str = '', layer: int = 0, datatype: int = 0, ld=tuple(),
field_name: str = '',
hidden=False):
"""Add a layer to the layer list of the pcell by name.
:param var_name: name of the variable
:param name: name in the pcell window
:param layer: layernumber
:param datatype: layerdatatype
:param field_name:
:param hidden: hide in the GUI
:Examples:
self.add_layer('lpp','rx1phot.drawing')
"""
if ld:
layer, datatype = ld
linfo = pya.LayerInfo(layer, datatype)
elif name:
# ln, dn = name.split('.')
linfo = self.get_layer(name) # self.layermap[ln][dn]
# layer = int(layer)
# datatype = int(datatype)
else:
raise ValueError("ld or name, layer and datatype must be defined")
self.param(var_name, self.TypeLayer, field_name, hidden=hidden, default=linfo)
[docs] def add_params(self, params: dict):
"""Create the PCell conform dictionary from a parameter list
:param params: Dictionary of parameters
"""
for key in sorted(params):
if '_choices' in key:
continue
df = params[key]
if type(df) == float:
t = self.TypeDouble
elif type(df) == int:
t = self.TypeInt
elif type(df) == bool:
t = self.TypeBoolean
elif type(df) == list:
t = self.TypeList
elif type(df) == str:
t = self.TypeString
elif type(df) == pya.Shape:
t = self.TypeShape
elif type(df) is None:
t = self.TypeNone
else:
raise NotImplementedError("Type {} not found".format(type(df)))
if key + '_choices' in params:
self.param(key, t, key, default=params[key], choices=params[key + '_choices'])
else:
self.param(key, t, key, default=params[key])
[docs] def decl(self, libname: str, cellname: str):
"""Get pya.PCellDeclaration of a cell in a library
:param libname: Name of the library
:param cellname: Name of the cell
:return: pya.PCellDeclaration reference of PCell
"""
lib = pya.Library.library_by_name(libname)
if not lib:
raise Exception("Unknown lib '{}'".format(libname))
pcell_decl = lib.layout().pcell_declaration(cellname)
if not pcell_decl:
raise Exception("Unkown PCell '{}'".format(cellname))
return pcell_decl
[docs] def update_parameter_list(self, params: dict, decl: 'pya.PCellDeclaration'):
"""Coerces parameter list. This is necessary to calculate port locations and update parameters in general.
:param params: dict of parameters
:param decl: pya.PCellDeclaration reference
:return: list of updated parameters
"""
param_list = [params.get(p.name, p.default) for p in decl.get_parameters()]
param_list = decl.coerce_parameters(self.layout, param_list)
return param_list
[docs] def calculate_ports(self, instances: list):
"""Calculates port locations in the cell layout. This is to propagate the port locations upwards
:param instances: list containing :class:`kppc.photonics.InstanceHolder`
"""
porttrans = []
if self.transformations != '':
# Check child cells ports. If there are ports which are not populated by other ports, add them as own ports
trans = [pya.ICplxTrans.from_s(i) for i in self.transformations.split(';')]
for i, inst in enumerate(instances):
if not inst.params_mod[0]:
continue
ports = [pya.ICplxTrans.from_s(k.split(':')[0]) for k in inst.params_mod[0].split(';')]
lengths = [k.split(':')[1] for k in inst.params_mod[0].split(';')]
names = [k.split(':')[2] for k in inst.params_mod[0].split(';')]
for q,w,n in zip(ports, lengths, names):
porttrans.append([trans[i] * q, w, n])
if self.portlist != '':
# Add manually created Ports
porttrans.extend(
[[pya.ICplxTrans.from_s(p.split(':')[0]), p.split(':')[1], p.split(':')[2]] for p in self.portlist.split(';')])
porttrans = sorted(porttrans, key=lambda x: (x[0].disp.x, x[0].disp.y))
# Add the calculated ports as own ports
self.portlist = ''
M180 = pya.ICplxTrans(1, 180, True, 0, 0)
nn,ne,ns,nw = 0,0,0,0
def regrep(match,nn,ne,ns,nw):
if match.group(1) == 'N':
ret = f'{match.group(1)}{nn}'
nn += 1
return ret
if match.group(1) == 'E':
ret = f'{match.group(1)}{ne}'
ne += 1
return ret
if match.group(1) == 'S':
ret = f'{match.group(1)}{ns}'
ns += 1
return ret
if match.group(1) == 'W':
ret = f'{match.group(1)}{nw}'
nw += 1
return ret
for p in porttrans:
if not ([p[0] * pya.ICplxTrans.R180, p[1]] in porttrans or [p[0] * M180, p[1]] in porttrans):
if self.portlist != '':
self.portlist += ';'
rawname = p[2]
regex = r"([NSEW])(?:\d+|{n})$"
name = re.sub(regex, lambda x: regrep(x,nn,ne,ns,nw), rawname)
if name == rawname:
if 'N{n}' in rawname:
name = rawname.replace('{n}',str(nn))
nn += 1
elif 'E{n}' in rawname:
name = rawname.replace('{n}',str(ne))
ne += 1
elif 'W{n}' in rawname:
name = rawname.replace('{n}',str(nw))
nw += 1
elif 'S{n}' in rawname:
name = rawname.replace('{n}',str(ns))
ns += 1
self.portlist += str(p[0]) + ':' + p[1] + ':' + name
[docs] def add_pcell_variant(self, params: dict, number: int = 1):
"""Add variants of PCells. Creates a list of InstanceHolders and modifies their parameters accordingly.
:param params: parameter list from which to create pcells
:param number: Number of instances to create
:return: list of :class:`kppc.photonics.InstanceHolder`
"""
params_copy = params.copy()
cellname = params_copy.pop('cellname')
libname = params_copy.pop('lib')
params_copy['top'] = False
params_copy['only_top_ports'] = self.only_top_ports
pcell_decl = self.decl(libname, cellname)
params_mod = self.update_parameter_list(params_copy, pcell_decl)
instance_list = []
for i in range(number):
instance_list.append(
InstanceHolder(cellname, libname, pcell_decl, params=params, params_mod=params_mod, id=i))
if number == 1:
return instance_list[0]
else:
return instance_list
[docs] def add_pcells(self, instance_list: list):
"""Creates list of instances of PCells. These are the effective Klayout cell instances.
:param instance_list: list of :class:`kppc.photonics.InstanceHolder`
:return: list of instantiated pya.CellInstArray
"""
id = 0
insts = []
for inst_port in instance_list:
if type(inst_port) is InstanceHolder:
inst_port.id = id
id += 1
insts.append(inst_port)
elif type(inst_port) is list:
for iinst_port in inst_port:
if type(iinst_port) is InstanceHolder:
iinst_port.id = id
id += 1
insts.append(iinst_port)
else:
raise ValueError(
"Expected type instances (InstanceHolder), ports (PortCreation) or a list of instances,"
"ports. Instead got {}".format(str(type(inst_port))))
trans = self.get_transformations()
instances = []
for i, inst in enumerate(insts):
pcell_var = self.layout.add_pcell_variant(inst.pcell_decl.id(), inst.params_mod)
instances.append(self.cell.insert(pya.CellInstArray(pcell_var, trans[i])))
return instances
[docs] def move_instance(self, ind: int, trans: 'pya.ICplxTrans', mirror: bool = False):
"""Moves an InstanceHolder object
:param ind: id of the InstanceHolder
:param trans: list of transformations
:param mirror: bool whether to mirror the object
"""
trans = pya.ICplxTrans(1, trans[2], mirror, trans[0], trans[1])
self.add_transformation(trans, ind)
[docs] def coerce_parameters_impl(self):
"""Method called by Klayout to update a PCell. For photonic PCells the ports are updated/calculated in the
parameter of the PCell. And desired movement transformations are performed.
Because the calculated ports of our own PCell are used by parent cells and are needed before
`~produce_impl`, we must calculate them twice. First to calculate where our own ports are and then again to
instantiate the child cells. This is unfortunate but not a big problem, since dataprep and DR-cleaning take
the majority of computation time.
:return:
"""
self.clear_ports()
instances_and_ports = self.create_param_inst()
if not instances_and_ports:
return
if isinstance(instances_and_ports, PortCreation) or isinstance(instances_and_ports, InstanceHolder):
instances_and_ports = [instances_and_ports]
instance_id = 0
instances = []
for inst_port in instances_and_ports:
# For each object test if it is a list of InstanceHolders/Port or plain objects and then separate them
# accordingly
if isinstance(inst_port, InstanceHolder):
inst_port.id = instance_id
instance_id += 1
instances.append(inst_port)
elif isinstance(inst_port, PortCreation):
self.create_port(inst_port.x, inst_port.y, inst_port.rot, inst_port.length, name=inst_port.name)
elif type(inst_port) is list:
for iinst_port in inst_port:
if isinstance(iinst_port, InstanceHolder):
iinst_port.id = instance_id
instance_id += 1
instances.append(iinst_port)
elif isinstance(iinst_port, PortCreation):
self.create_port(iinst_port.x, iinst_port.y, iinst_port.rot, iinst_port.length, name=iinst_port.name)
else:
raise ValueError(
"Expected type instances (InstanceHolder), ports (PortCreation) or a list of instances,"
"ports. Instead got {}".format(str(type(inst_port))))
self.transformations = ''
# If child instances have requested movements in their InstanceHolder move them now
for i, inst in enumerate(instances):
if self.transformations:
self.transformations += ';'
if inst.movement:
# Adjust movement for database
inst.movement.disp = pya.Vector(int(inst.movement.disp.x / self.layout.dbu),
int(inst.movement.disp.y / self.layout.dbu))
self.transformations += inst.movement.to_s()
inst.placed = True
elif inst.connection:
self.transformations += pya.ICplxTrans.R0.to_s()
else:
self.transformations += pya.ICplxTrans.R0.to_s()
inst.placed = True
# If the InstanceHolder object has a Port to connect to, calculate the transformation
all_placed = False
count = 0
while (not all_placed) and count < 50:
all_placed = True
count += 1
for i, inst in enumerate(instances):
if inst.connection:
if not inst.connection.placed:
all_placed = False
continue
retcode = self.connect_port(inst.connection.id, inst.connection.params_mod[0], inst.connection_port,
inst.id,
inst.params_mod[0], inst.port_to_connect)
if retcode < 0:
msg = pya.QMessageBox(pya.Application.instance().main_window())
msg.text = 'Port {} of {} cannot be connected to Port {} of {}'.format(inst.port_to_connect,
inst.connection.params[
'cellname'],
inst.port_to_connect,
inst.params['cellname'])
msg.windowTitle = 'ImportError'
msg.exec_()
return False
inst.placed = True
# Update the transformations and self.portlist with the calculated transformations of the children
self.calculate_ports(instances)
[docs] def produce_impl(self):
"""Create the effective Klayout shapes. For this all the InstanceHolders are cycled through and all the child
instances are created. Furthermore, if desired, dataprep is performed, which copies and sizes the shapes as
desired. Dataprep will only create shapes on the topmost cell. Finally, if desired DR-cleaning is performed and
in the process the shapes will be manhattanized.
"""
if self.layermap is None:
raise NotImplementedError(
'self.layermap has to be defined by the PCell. You can either do this in the PCell initialization or '
'in a Class that all PCells inherit from. See the documentation for more details')
if self.dataprep_config is None:
raise NotImplementedError(
'self.dataprep_config has to be defined by the PCell. You can either do this in the PCell '
'initialization or in a Class that all PCells inherit from. See the documentation for more details')
if self.clean_rules is None:
raise NotImplementedError(
'self.clean_rules has to be defined by the PCell. You can either do this in the PCell initialization '
'or in a Class that all PCells inherit from. See the documentation for more details')
instances_and_ports = self.create_param_inst()
instance_id = 0
instances = []
# Because we cannot safe anything between coerce_parameters and here, we must calculate this again.
if isinstance(instances_and_ports, PortCreation) or isinstance(instances_and_ports, InstanceHolder):
instances_and_ports = [instances_and_ports]
for inst_port in instances_and_ports:
if isinstance(inst_port, InstanceHolder):
inst_port.id = instance_id
instance_id += 1
instances.append(inst_port)
elif isinstance(inst_port, PortCreation):
continue
elif isinstance(inst_port, list):
for iinst_port in inst_port:
if isinstance(iinst_port, InstanceHolder):
iinst_port.id = instance_id
instance_id += 1
instances.append(iinst_port)
elif isinstance(iinst_port, PortCreation):
continue
else:
raise ValueError(
"Expected type instances (InstanceHolder), ports (PortCreation) or a list of instances,"
"ports. Instead got {}".format(str(type(inst_port))))
# If we have child cells to create, create them now
if instances:
self.add_pcells(instances)
# Create the shapes created in this PCell
self.shapes()
if self.top or not self.only_top_ports:
# If this is a top cell or we want to draw all ports, draw the texts. To make sure that texts of ports
# get drawn correctly set the boolean to true in File-> Setup -> Display -> Settings -> Cells ->
# 'Transform text with cell instance' and make sure it's not set to Default font
if self.portlist:
for i, p in enumerate(self.portlist.split(';')):
trans = pya.ICplxTrans.from_s(p.split(':')[0]).s_trans()
trans.rot = (trans.rot + 1 % 4)
if trans.is_mirror():
trans.rot = (trans.rot + 2 % 4)
valign = halign = 1
if trans.rot % 2:
valign = 2
else:
halign = 2
name = [n.lstrip('name=') for n in p.split(':') if 'name=' in n][0]
if name:
text = pya.Text(name, trans)
else:
text = pya.Text(f'Port {i}',trans)
text.halign = halign
text.valign = valign
self.cell.shapes(self._layers[0]).insert(text)
# For the dataprep, do we want to keep the original shapes and child-cells?
cl1 = clock()
if kppc.settings.Multithreading.Enabled:
if self.keep:
# Yes, so we create a new child cell called 'DataPrep' to create the dataprep shapes in
if self.dataprep:
prep_cell = self.layout.create_cell('DataPrep')
prep_cell._create()
kppc.photonics.dataprep.dataprep(self.cell, self.layout, prep_cell, config=self.dataprep_config,
layers_org=self.layermap)
self.cell.insert(pya.CellInstArray(prep_cell.cell_index(), pya.Trans.R0))
if self.drc_clean:
rules = self.clean_rules
# Convert Micrometers to database units
for cr in rules:
cr[1] = int(cr[1] / self.layout.dbu)
cr[2] = int(cr[2] / self.layout.dbu)
kppc.drc.multiprocessing_clean(prep_cell, rules)
else:
# the dataprep will clean all children and shapes and then insert cleaned ones
if self.dataprep:
temp_cell = self.layout.create_cell('DataPrep_del')
temp_cell._create()
kppc.photonics.dataprep.dataprep(self.cell, self.layout, temp_cell, config=self.dataprep_config,
layers_org=self.layermap)
if self.drc_clean:
rules = self.clean_rules
# vonvert Micrometers to database units
for cr in rules:
cr[1] = int(cr[1] / self.layout.dbu)
cr[2] = int(cr[2] / self.layout.dbu)
kppc.drc.multiprocessing_clean(temp_cell, rules)
# felete all child cells
self.cell.clear()
self.cell.insert(pya.CellInstArray(temp_cell.cell_index(), pya.Trans.R0))
self.cell.flatten(True)
else:
if self.keep:
# create a new child cell called 'DataPrep' to create the dataprep shapes in
if self.dataprep:
prep_cell = self.layout.create_cell('DataPrep')
prep_cell._create()
kppc.photonics.dataprep.dataprep(self.cell, self.layout, prep_cell, config=self.dataprep_config,
layers_org=self.layermap)
self.cell.insert(pya.CellInstArray(prep_cell.cell_index(), pya.Trans.R0))
if self.drc_clean:
rules = self.clean_rules
# Convert Micrometers to database units
for cr in rules:
cr[1] = int(cr[1] / self.layout.dbu)
cr[2] = int(cr[2] / self.layout.dbu)
kppc.drc.clean(prep_cell, rules)
else:
# the dataprep will clean all children and shapes and then insert cleaned ones
if self.dataprep:
temp_cell = self.layout.create_cell('DataPrep_del')
temp_cell._create()
kppc.photonics.dataprep.dataprep(self.cell, self.layout, temp_cell, config=self.dataprep_config,
layers_org=self.layermap)
if self.drc_clean:
rules = self.clean_rules
# Convert Micrometers to database units
for cr in rules:
cr[1] = int(cr[1] / self.layout.dbu)
cr[2] = int(cr[2] / self.layout.dbu)
kppc.drc.clean(temp_cell, rules)
# Delete all child cells
self.cell.clear()
self.cell.insert(pya.CellInstArray(temp_cell.cell_index(), pya.Trans.R0))
self.cell.flatten(True)
[docs] def create_param_inst(self):
"""To be overwritten by the effective PCell
:return: Iterable with the declarations of the child PCells.
"""
return []
[docs] def shapes(self):
"""To be overwritten by effective PCell if shapes should be desired.
:rtype: None
"""
return