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:

An example showing less segments being generated due to the change in logic in TaperedWaveguide.

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:

../_images/luceda_2025030_directionalcoupler.png