Trigger a re-draw of PCell

Hi Guys,

I am making a PCell library where the PCells have external dependencies and I need some way to trigger a re-draw of the placed PCells in a layout. How can I do this from Python?

Thanks,
Robert

Comments

  • The method to do this is "Cell#refresh". But you will need to call it in the "proxy cell".

    If you have a layout which instantiates a PCell, KLayout generates a "proxy cell" which is basically linked to the PCell but provides a mirror of the current layout. So if the Library goes away, KLayout still has a copy of the layout.

    This is the proxy cell. It lives inside the layout you place the PCell and this is the cell you need to call "refresh" on. As a simple solution, you can simply call "refresh" on all cells of your layout:

    for c in layout.each_cell():
       c.refresh()
    

    Matthias

  • Thanks Matthias,

    I tried this, but seems my hackish-solution cannot work with this approach. I get this error message:

    Object has been destroyed already in Layout.each_cell
      C:/.../src/pymacros/QTQC/QTQC Python parametric cells.lym:71
    

    I guess this has to do with this context-dependent thing with the proxy cell you try to explain to me, but I cannot quite figure it out. What I did was I use the self.layout() object in my pya.Library's __init__-function and when I trigger an external change (which comes from a pya.QFileSystemWatcher() and an external file) I run

    for c in self.layout.each_cell():
          c.refresh()
    

    And get above error. What am I not getting?

  • "layout" was mean to be the client layout which is using the library, not the layout inside that library.

    You cannot trigger a refresh from inside the library currently. The only thing that triggers a refresh currently is the re-registration of a library. However registering the same library again will probably crash the application. You can only create a new library with the new layout and register it under the same name than the existing library. This will replace the old library and trigger a refresh.

    Matthias

  • Thanks Mattias,

    Can you point me to some documentation for me to understand where to put this code then? I have not understood I think where to execute that code.

    Thanks,
    Robert

  • @ravn The code must be some on your side. You have somewhere declared a library class with the PCell code. There must be something like this example of mine:

    # -------------------------------------------------------------------------------  
    
       # The library where we will put the PCell into 
      class TestLib2 < Library
    
        def initialize  
    
          # Set the description
          self.description = "Test Library 2"
    
          # initialize the database unit (line added ~ default is 0.001):
          layout.dbu = 0.0001
    
          # Create the PCell declarations
          layout.register_pcell("Rectangle", Rectangle::new)
          layout.register_pcell("RectangleArray", RectangleArray::new)
    
          # Register the library
          register("TestLib2")
    
        end
    
      end 
    
      # Instantiate and register the library
      TestLib2::new
    

    Eventuall all you need to do is to execute the "TestLib2::new" again to create a new library object and re-register it.

    Or you wait until I release 0.27.8, because it gets this: https://github.com/KLayout/klayout/issues/996

    Matthias

  • This is super! I will wait for 0.27.8.

  • 0.27.8 is released. Let's see whether it works for you :)

    Matthias

  • edited April 2022

    Hi @Matthias,

    I tried this feature now. Unfortunately, it does not seem to work as I expected (I'm sure I misunderstood something). I am not sure what refresh does under the hood, but in my case, I could not see any change in the layout in the used PCells. As described above, my PCells have external dependency on a file and I am using a file-watcher from QT to trigger when that file has changed. That part works and I get a notification. However, when running refresh in the library, I cannot see the reflected in the rendered shapes.

    I would have expected the library refresh-function to call all PCells produce_impl, but perhaps I am mistaking?

    Thanks,
    Robert

  • You need to call "refresh" on the Library. As explained above, you cannot call it on the layout inside the library, because that is not the client layout which uses the PCell.

    Please paste some code - I don't want to guess what you're actually doing.

    Matthias

  • Here is the code from the example Python PCell. Only changed where it is marked by <===========

    import pya
    import math
    from numpy.random import rand # <===========
    
    """
    This sample PCell implements a library called "MyLib" with a single PCell that
    draws a circle. It demonstrates the basic implementation techniques for a PCell 
    and how to use the "guiding shape" feature to implement a handle for the circle
    radius.
    
    NOTE: after changing the code, the macro needs to be rerun to install the new
    implementation. The macro is also set to "auto run" to install the PCell 
    when KLayout is run.
    """
    
    class Circle(pya.PCellDeclarationHelper):
      """
      The PCell declaration for the circle
      """
    
      def __init__(self):
    
        # Important: initialize the super class
        super(Circle, self).__init__()
    
        # declare the parameters
        self.param("l", self.TypeLayer, "Layer", default = pya.LayerInfo(1, 0))
        self.param("s", self.TypeShape, "", default = pya.DPoint(0, 0))
        self.param("r", self.TypeDouble, "Radius", default = 0.1)
        self.param("n", self.TypeInt, "Number of points", default = 64)     
        # this hidden parameter is used to determine whether the radius has changed
        # or the "s" handle has been moved
        self.param("ru", self.TypeDouble, "Radius", default = 0.0, hidden = True)
        self.param("rd", self.TypeDouble, "Double radius", readonly = True)
    
      def display_text_impl(self):
        # Provide a descriptive text for the cell
        return "Circle(L=" + str(self.l) + ",R=" + ('%.3f' % self.r) + ")"
    
      def coerce_parameters_impl(self):
    
        # We employ coerce_parameters_impl to decide whether the handle or the 
        # numeric parameter has changed (by comparing against the effective 
        # 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.
        rs = None
        if isinstance(self.s, pya.DPoint): 
          # compute distance in micron
          rs = self.s.distance(pya.DPoint(0, 0))
        if rs != None and abs(self.r-self.ru) < 1e-6:
          self.ru = rs
          self.r = rs 
        else:
          self.ru = self.r
          self.s = pya.DPoint(-self.r, 0)
    
        self.rd = 2*self.r
    
        # n must be larger or equal than 4
        if self.n <= 4:
          self.n = 4
    
      def can_create_from_shape_impl(self):
        # Implement the "Create PCell from shape" protocol: we can use any shape which 
        # has a finite bounding box
        return self.shape.is_box() or self.shape.is_polygon() or self.shape.is_path()
    
      def parameters_from_shape_impl(self):
        # Implement the "Create PCell from shape" protocol: we set r and l from the shape's 
        # bounding box width and layer
        self.r = self.shape.bbox().width() * self.layout.dbu / 2
        self.l = self.layout.get_info(self.layer)
    
      def transformation_from_shape_impl(self):
        # Implement the "Create PCell from shape" protocol: we use the center of the shape's
        # bounding box to determine the transformation
        return pya.Trans(self.shape.bbox().center())
    
      def produce_impl(self):
    
        # This is the main part of the implementation: create the layout
    
        # fetch the parameters
        ru_dbu = rand()*self.ru / self.layout.dbu # <===========
    
        # compute the circle
        pts = []
        da = math.pi * 2 / self.n
        for i in range(0, self.n):
          pts.append(pya.Point.from_dpoint(pya.DPoint(ru_dbu * math.cos(i * da), ru_dbu * math.sin(i * da))))
    
        # create the shape
        self.cell.shapes(self.l_layer).insert(pya.Polygon(pts))
    
    
    class MyLib(pya.Library):
      """
      The library where we will put the PCell into 
      """
    
      def __init__(self):
    
        # Set the description
        self.description = "My First Library"
    
        # Create the PCell declarations
        self.layout().register_pcell("Circle", Circle())
        # That would be the place to put in more PCells ...
    
        # Register us with the name "MyLib".
        # If a library with that name already existed, it will be replaced then.
        self.register("MyLib")
    
    
    # Instantiate and register the library
    ml = MyLib() # <===========
    

    Then I make a small script to refresh:

    import pya
    
    lv = pya.Application.instance().main_window().current_view()
    ly = lv.cellview(0).layout()
    
    for c in ly.each_cell():
      print(c.name)
      c.refresh()
    
    ml.refresh()
    

    Here I try to refresh in two ways, using the cells refresh and using the library. Non of them show any change in the circle radius. If I just re-run the MyLib-code, I can clearly see that the circle radius is changing from run to run due to the rand() call.

    Hope this illustrates my issue.

    Kr,
    Robert

  • edited April 2022

    Hi Robert,

    Thanks a lot. Such code is very helpful indeed.

    So I see. You want to force a "re-computation". Sorry for the nit-picking, but that is something slightly different. Well, I admit it's also a bit confusing.

    So refresh just restores the connection between library and clients. But it does not force a re-computation of the PCells (well, that can be added later). PCells are cached inside the library and the implementation assumes that the PCells do not change geometry spontaneously and as long as neither the library object nor the parameters change there is no need for an update.

    But you can force an update by calling refresh also on the library cells. The library cells represent incarnations of the PCells and calling refresh on them triggers the recomputation. So the code to trigger a recomputation is:

    def do_refresh(ml):
      for cell in ml.layout().each_cell():
        cell.refresh()
      ml.refresh()
    

    The first loop will trigger the re-computation, the final refresh will transfer those recomputed cells into the client layouts.

    As this is probably a common use model, I will consider incorporating the per-cell refresh into the library refresh (https://github.com/KLayout/klayout/issues/1057), so finally a single "ml.refresh()" call is sufficient.

    Matthias

  • Thanks for all support @Matthias!

    Now it works almost as expected :) I have some issue when creating a new library object, that is when I re-run the -lym-file. Not sure exactly how to describe the problem - it looks like the shapes generated from my PCell in some cases loose it's connection to the library. I'll look around and perhaps file another post here if I understand it better. One thing I noted is that the python __del__-method of the library does not seem to be run, so I'm guessing the python library object is not deleted. Maybe this is a memory leak?

    The library I have been working on is starting to show some functionality and if you would be interested, I could give you a demonstration of the features. It might be of interest for others, despite still being under heavy development.

    Kr,
    Robert

  • @ravn Very good :)

    BTW: Are you planning to make the library available to others as open source? In that case you may want to put it on a public repository and let others have a look at it.

    Regarding the __del__ I could not readily reproduce it. I used the Python PCell sample and added __del__ like this:

    class PCellLib(pya.Library):
    
      def __del__(self):
        print("@@@ del on "+repr(self))
    
      def __init__(self):
    ...
    

    In my case, __del__ was called whenever I reran the .lym file.

    Kind regards,

    Matthias

  • @Matthias : Thanks. Our code is probably not ready for to public yet, there is still too much that needs to be polished, but we are aiming at FOSS'ing it once it has matured a bit more.

    It uses this feature that I explained in an earlier post (and you were skeptical to :) where parameters are actually not numbers but rather Python code. This has some clear advantages, I think especially for researchers where you often have to test stuff and need to change things in a flexible manner over time.

  • I see. Do worry if I am sceptical - but I am curious about your application. And thanks for considering FOSS!

    Matthias

  • If you want a short demonstration, send me and email and we can book a Zoom call. The library is for designing quantum processors.

    R.

  • That may sound somewhat funny, but I do not see your mail adress :)

    But I'm curious. Maybe you can send me a mail to contact "at" klayout.de ? I will get back to you then.

    Matthias

Sign In or Register to comment.