New toolbar button to draw then run code

edited February 2014 in Ruby Scripting

Hi,

I know how to add a new toolbar button, as detailed in many other forum questions.

I also know how to draw something (say, a box), select it, and then click Edit menu > Selection > Convert to PCell, then to choose a PCell (say, BasicLibrary's StrokedPolygon) to convert it to PCell.

However I'd like to combine the two above in one fluid process.

Basically, using the examples above, I envisage clicking a custom "Stroked Box" button on the toolbar, then drawing a box with my mouse, then as soon as I have drawn the box it instantly converts it to StrokedPolygon (without me having to select it or press menu items or shortcut keys etc). In other words you click "Stroked Box" button to draw a stroked box. Can this be done?

Thanks!

Comments

  • edited February 2014

    Hi David,

    well, it probably can be done, but the path is a stony one.

    You cannot directly catch mouse events. KLayout implements functionality based on components called "plugins". A component can register itself inside the system and add functionality to menus and the mouse event dispatcher of the application. A big part of KLayout's functionality is implemented as plugins. Plugins can also be provided through Ruby code. As a starting point you can use the documentation of PluginFactory http://klayout.de/doc/code/class_PluginFactory.html.

    Only by using that facility you will be able to implement the requested functionality. Implementing a stroked box will be easier than lets say, a stroked polygon. The latter means you'll have to control multiple mouse clicks, handle alignment options and so on.

    To make the functionality feel like a built-in function, undo/redo support needs to be added as well, although that is rather simple (the key are so called "transactions" - see http://klayout.de/doc/code/class_LayoutView.html#m_transaction).

    So overall that will be a fairly complex project. But it can be done, I'm sure.

    Matthias

  • edited March 2014

    Thanks Matthias,

    I took a look and played with the PluginFactory example - and yes it seems it can be done but may be nontrivial.

    Thinking more, an acceptable alternative would be to draw a shape (say, a regular box), then press a user-defined key (like "s") and it converts the just-drawn shape to Stroked Box. I know how to define keyboard shortcuts in Ruby, but am stuck on two things:

    1) How to convert the selected shape to PCell, in Ruby.

    For this of course I know how to get the selected shape(s) and iterate over them with some action, e.g.

    lv = RBA::Application.instance.main_window.current_view
    lv.each_object_selected do |obj|
      # Do something to each one
    end
    

    However I can't figure out a way to convert to PCell. I looked in PCellDeclaration but none of those methods do that. I could run

    RBA::Application.instance.main_window.menu.action("edit_menu.selection_menu.convert_to_pcell").trigger
    

    but then I get a dropdown box to choose the PCell that I have to click with my mouse -- instead I want to specify the PCell in code.

    2) How to select the just-drawn shape

    Say I draw a box. Rather than changing to the select tool and then selecting it, is there a way to get a handle to the just-drawn box in Ruby? I thought maybe in the list of shapes there is a way to find the one we just added. We can assume the box was literally just drawn and the user has not done any other input since.

    (Note: I'm actually not interested in Stroked Box - I'm interested in various other more complicated things, but just using this as a simple example.)

    Thanks!

  • edited November -1

    Hi David,

    I'm somewhat short of time right now and can't give advise immediately. But I'll come back to you. Please ping me in case I forget.

    Matthias

  • edited November -1

    Hi David,

    Regarding 1.), I just answered a similar request here: http://klayout.de/forum/comments.php?DiscussionID=439&page=1#Comment_1873. In your case, since you have a STROKED_POLYGON PCell, the parameters are

    name     type        unit    default value  description
    layer    TypeLayer                          Layer
    radius   TypeDouble  micron  0.0            Radius
    width    TypeDouble  micron  0.1            Width
    shape    TypeShape           (polygon)     
    npoints  TypeInt             64             Number of points / full circle
    

    In order to implement the "convert shape to polygon", you'll need to identify the shape you want to convert (that will give you a Shape object). You can then use Shape#polygon to convert it to a polygon which you can assign to the "shape" parameter. After placing a such a PCell instance into the same cell than the original shape you can remove the shape.

    Regarding 2.) I'm afraid there is no other solution rather than implementing the mouse actions yourself using the Plugin scheme. Here's a sketch:

    • On the first mouse click establish a new action: initialize the first point, set some state flag indicating you're drawing, and setup a Marker object to represent the box
    • On mouse movement (while the state flag indicates that drawing is in progress), update the second point and the marker object
    • On the second mouse click, finish the action: create a box object with both points, start a transaction, create a PCell using that box for the shape parameter, commit the transaction and clean up (destroy the marker, reset the state)
    • Implement "drag_cancel" to finish the action without producing a PCell

    I don't have a worked out example that would illustrate that. Maybe I find time to make one, but I can't promise.

    Matthias

  • edited November -1

    Thanks Matthias.

    Regarding 1.) I tried this, but while it does place a PCell on the layout, it does not appear to use the selected shape as the PCell input. Instead it just places the PCell at the default position, (0,0).

    Here is the code. I couldn't use Basic.TEXT because that doesn't allow a TypeShape input, so I changed it to generate a Basic.CIRCLE.

    lv = RBA::Application.instance.main_window.current_view
    layout = lv.active_cellview.layout
    
    lv.each_object_selected do |obj|
    
      top = layout.cell(obj.cell_index)
    
      # find the lib
      lib = RBA::Library.library_by_name("Basic")
      lib || raise("Unknown lib 'Basic'")
    
      # find the pcell
      pcell_decl = lib.layout.pcell_declaration("CIRCLE")
      pcell_decl || raise("Unknown PCell 'CIRCLE'")
    
      # set some sample parameters (uses GDS layer 10/0):
      r = 20.0
      param = {
        "layer" => RBA::LayerInfo::new(9, 0),
        "radius" => r, 
        "handle" => obj.shape,
        "actual_radius" => r,
        "npoints" => 64
      }
    
      # build a param array using the param hash as a source
      pv = pcell_decl.get_parameters.collect do |p|
        param[p.name] || p.default
      end
    
      # create a PCell variant cell
      pcell_var = layout.add_pcell_variant(lib, pcell_decl.id, pv)
    
      # instantiate that cell
      t = RBA::Trans::new(RBA::Trans::r90, 0, 0)
      pcell_inst = top.insert(RBA::CellInstArray::new(pcell_var, t))
    
    end
    

    Regarding 2.) Thanks for outlining, and I plan to give this a crack when I've figured out 1.

    Thanks!

  • edited November -1

    Ah - of course in the 3rd-to-last line I tell it to create it at (0,0), don't I? Oops.

    Well anyway - so I could in this code get the various parameters of the selected shape and from that figure out r, and the x,y location and rotation to place it. However then why do I even need to give it the shape in this line:

    param = { ... "handle" => obj.shape ... }
    

    in the first place? It seems like in general the calling script must figure out what to set parameters like "radius" and x,y location to --- therefore making the passing of "handle" => obj.shape irrelevant. Is there in fact some need for this?

    Thanks!

  • edited March 2014

    Hi David,

    CIRCLE is not a good example - the "handle" is basically an internal parameter which is not a shape, but a single-point box which represents the handle by which you can graphically edit the radius.

    A CIRCLE PCell does not need a shape for input - even though you can convert a shape to a circle. If you want to copy the behavior of the "convert to PCell" function, you'll have to derive the radius from the shape's bounding box (the implementation of Shape to CIRCLE takes the minimum of width and height for the diameter). You could also use PCellDeclaration#parameters_from_shape and PCellDeclaration#transformation_from_shape on the Basic.CIRCLE's PCellDeclarationHelper object to perform this.

    A PCell that truly requires a shape for input is ROUNDED_POLYGON for example. For such PCell's the input shape acts as the template and a shape parameter is required to specify that. In that case, I'd suggest to use "obj.shape.polygon" to convert the shape to a polygon and maybe check whether that is possible - the shape could be a text as well. But also for such PCell's the PCellDeclaration#parameters_from_shape and PCellDeclaration#transformation_from_shape works, so maybe that is the more generic approach:

    # Replace obj.layer by the layer index of the target layer if it shall be 
    # different from the original shape's layer.
    # The "param" helper hash is no longer required.
    pv = pcell_decl.parameters_from_shape(layout, obj.shape, obj.layer)
    
    ...
    # In addition we can derive the transformation
    t = pcell_decl.transformation_from_shape(layout, obj.shape, obj.layer)
    

    I have not tested that, but that's the way it should work in general, even for CIRCLE. I admit that solution is far more simple than the first suggestion and I should have come up with that earlier.

    Matthias

Sign In or Register to comment.