Known changes and backwards incompatibilities in 2025.03
Names of cells in GDSII in case of multiple views
Cells that have multiple views will now export to GDSII with slightly different names. This takes advantage of the newer name collision detection in the GDSII export which replaces the old (slower) behavior.
from ipkiss3 import all as i3
class MyCell(i3.PCell):
class Layout(i3.LayoutView):
def _generate_elements(self, elems):
elems += i3.Rectangle(layer=i3.Layer(0))
return elems
class Layout2(i3.LayoutView):
def _generate_elements(self, elems):
elems += i3.Circle(layer=i3.Layer(1))
return elems
class SuperCell(i3.PCell):
sub = i3.PCellProperty()
class Layout(i3.LayoutView):
def _generate_elements(self, elems):
elems += i3.SRef(self.sub.Layout.view)
elems += i3.SRef(self.sub.Layout2.view)
return elems
# test export of multiple views in the dependency tree, exported to one database
sub = MyCell(name="submv")
sup = SuperCell(name="topmv", sub=sub)
sup_lo = sup.Layout()
sup_lo.write_gdsii("multiple_views_same_cell.gds")
This will now generate a GDSII in which there are two cells called submv and submv_1. Previously, they would have called submv and submv_Layout2.
Rounding of the number of grid points per user unit
On GDSII export, the number of GDSII grid points per user unit is rounded to the nearest integer to avoid very small numerical differences due to computer floating point accuracy limits. This might result in different grid snapping behavior compared to previous IPKISS versions. The old behavior is still available via the legacy_grids_per_unit flag (a deprecation warning will be raised):
my_layout.write_gdsii("my_layout.gds", legacy_grids_per_unit=True)
TaperedWaveguide implementation change
The taper logic inside the i3.TaperedWaveguide
method has been improved, and is now more aware of when tapers are needed.
The TaperedWaveguide will try to maximize the generation of continuous segments, and will only generate taper sections when needed.
Due to this change some previously generated small waveguide sections that were not needed are now removed.
As a result, some GDS files may differ due to the absence of these small sections. Users are advised to review their designs to ensure compatibility with this update.
Example of a waveguide that will now generate less segments:

Directly affected methods:
Special transformations
i3.IdentityTransform
, i3.Magnification
, i3.VMirror
, i3.HMirror
, i3.CMirror
, i3.Rotation
, and i3.Translation
are not classes anymore, but functions returning i3.NoDistortTransform
objects.
The transformations behave in the same way, except that isinstance checks against those will not be possible anymore:
from ipkiss3 import all as i3
tr = i3.Translation()
print(isinstance(tr, i3.NoDistortTransform))
print(isinstance(tr, i3.Translation))
The first print statement will print True (as before).
The second one will give a TypeError in 2025.03, since i3.Translation
is not a class anymore (before it was False, since all the special transformations are reduced to i3.NoDistortTransform
).
Instead of isinstance(tform, i3.IdentityTransform)
, tform.is_identity()
can be used to check if a transformation is identity.
Those function no longer accept keyword arguments that are not relevant for the particular special case of transformation, i.e. all of the following will now raise an error:
from ipkiss3 import all as i3
id = i3.IdentityTransform(magnification=1.5)
tr = i3.Translation(rotation=180.0)
rot = i3.Rotation(translation=(2.0, 1.0))
Adding None or IdentityTransform to a NoDistortTransform
The __add__ operator of i3.NoDistortTransform
is now faster when the second operand is None or i3.IdentityTransform.
It will discard the center arguments (rotation_center, magnification_center, mirror_center, mirror_plane_x, and mirror_plane_y), regardless of the second operand.
Note that center arguments do not affect transformations: if they are non-zero, they incur a translation (which is always preserved).
The following code illustrates the change:
from ipkiss3 import all as i3
rot = i3.Rotation(rotation_center=(2.0, 1.0), rotation=15.0)
print(rot.rotation_center) # (2.0,1.0) on both 2024.12 and 2025.03
# adding i3.IdentityTransform or None
tform = rot + i3.IdentityTransform()
print(tform.rotation_center) # (0.0,0.0) on 2025.03 and (2.0,1.0) on 2024.12
# other cases
tform = rot + i3.Translation(translation=(3.0, 5.0))
print(tform.rotation_center) # (0.0,0.0) on both 2024.12 and 2025.03
Configuring the license through an environment variable on linux
When configuring the license through the luceda_LICENSE
environment variable,
this variable now has to be set in ~/.profile
.
Setting this variable in ~/.bashrc
will no longer work when launching Canvas or Luceda Control Center
without using a terminal.
IPKISS Canvas: parameters of the directional coupler in generic_devices
The parameters of the DirectionalCoupler symbol in generic_devices have been modified to make more sense in the context of design exploration:
