Action class not triggering outside of lym

edited August 2017 in Python scripting
Hello,

I am trying to write a python module that when ran, extends some of the menu for KLayout. However, I am having an issue I do not understand. Within this module I create a new menu item, attach an action to it, and attach a method (from a submodule) to the action. When I do this and try clicking the new item nothing happens, but if I move this code to a KLayout macro and import the module appropriately everything works fine. This might be strictly a python referencing issue with modules, I am not sure, but I seem to only be able to use the Action class properly when run as a KLayout macro.

To try and test if it was an issue with referencing, I tried the same code (ran in the module) but triggering a lambda function that prints "test" without success.

I am not sure if this is well explained, but any help would be appreciated.

To provide some additional information to this, I have been able to successfully use the Observer class within this module, and QT signals, slots and event bindings. The Action class is the only one not 'responding'.

Comments

  • edited August 2017

    Here is an example of what is in the python module:

    def registerToolbarItems():
      import os
      from .lumerical import interconnect
      path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files", "INTERCONNECT_icon.png")
      menu = pya.Application.instance().main_window().menu()
    
      act = pya.Action()
      act.title = "Circuit \nSimulation"
      menu.insert_item("@toolbar.end", "cir_sim", act)
      act.on_triggered(interconnect.launch)
      act.icon = path
    
      act = pya.Action()
      act.title = "INTERCONNECT Monte Carlo Simulations"
      menu.insert_item("@toolbar.cir_sim.end", "mc_sim", act)
      act.on_triggered(MC_GUI.show)
      act.icon = path
    
      act = pya.Action()
      act.title = "INTERCONNECT Circuit Simulation"
      menu.insert_item("@toolbar.cir_sim.end", "launch_lumerical", act)
      act.on_triggered(interconnect.launch)
      act.icon = path
    
      act = pya.Action()
      act.title = "INTERCONNECT Update Netlist"
      menu.insert_item("@toolbar.cir_sim.end", "update_netlist", act)
      act.on_triggered(interconnect.update_netlist)
      act.icon = path
    
  • edited August 2017

    Hi,

    Not having the full code, I can just guess ... but my main suspect is this: AbstractMenu#insert_item does not take over the ownership over the action object. When the Python interpreter decides to remove the object, it's basically lost for KLayout. In this case, the menu item is still there but the Action object serving as a connector between the Python function and the menu item is lost. Python will destroy objects whenever they go out of scope, so this will happen when the function returns.

    When you do this in a .lym macro, the Action object will persist, because the variable holding the object is created on global level already.

    Please try whether it helps to keep the action objects somewhere on a global heap. Like this:

    actions = []
    
    def registerToolbarItems():
    
      global actions
    
      ...
      act = pya.Action()
      actions.append(act)
      menu.insert_item("..", "..", act)
      ...
    

    A more stylish way of doing this is a singleton which holds the action objects.

    BTW: the same is true for Ruby where the problem is more difficult to debug, because destruction happens later and more or less randomly.

    The Observer is actually a legacy concept and does not solve this issue. It's just a different way to connect code and menu items.

    Regards,

    Matthias

  • edited November -1
    Hi Matthias,

    Thank you for your answer, I will proceed with the global variables then.

    I thought maybe using the _unmanage method on these objects would preserve them, but I guess the python reference is still deleted and only the C++ object is retained so it didn't end up working.

    Brett
  • edited September 2017

    Hi Brett,

    "_unmanage" is not entirely wrong - this method actually detaches the C++ object from Python and the C++ object won't be deleted when the Python object is. (A warning: be somewhat careful with this: basically this bears the risk of memory leaks because then C++ is responsible to clean up the memory and C++ now always knows about this ...)

    In your case however this is no solution, because it is required to keep the Python object, not the C++ object. The Python object is the one that forms the bridge between the Action (in C++) and the Python function to be executed. If this object gets lost, this bridge is lost and the function is not executed. Any reference (global variable, singleton object attribute, array member ...) will make Python retain the object and everything is fine.

    Technically it's possible in such a case to tell C++ to hold a Python reference (increment Python's reference counter), but in this particular case that's not possible currently. Specifically: because C++'s Action is a proxy object itself with a unidirectional link. Similar other cases I have tried to guard against such issues by using the Python reference counter increment trick (and Ruby GC marking too), so Action is an exception.

    Regards,

    Matthias

Sign In or Register to comment.