CELL selection highlighter

edited January 2021 in Ruby Scripting

Hi Matthias (and/or others),
similar to the layer highlighter in this discussion (https://klayout.de/forum/discussion/1697/animation-mod#latest), I would like to construct a routine that does the same for selected cells. I wish to be able to highlight (select) a cell in the cell list/tree, then run a code that

  • determine which cells are selected in the tree
  • saves the default cell selection border width
  • sets the selection border with to something huge, like 5
  • selects the cell in the layout

Similar to what is in discussion 1697, I would then cycle through cells if more than 1 were selected.
This will allow us to see where in the layout a selected cell is exactly, even if we're in the top cell and all the way zoomed out. The usual way is to Hide/Show cycle quickly and hope that it flashes somewhere. We don't want the selection border width to be always huge either. The other way is to do this hide/show flashing along the hierarchy tree and work yourself up to the top cell. Neither way is liked much by our users.

Does anyone know how to

  • determine which cells are selected in the tree
  • programmatically select a cell in the layout ?
  • programmatically change the selection border with ?

Thank you !!!

Thomas

Comments

  • Hi Thomas

    have you considered the instance browser for this case? Select a cell in the tree and use "Tools/Browse Instances" to get a list of cell placements and walk through that.

    The highlight style follows the selection, but if that is all you're missing, maybe it's easier to change the C++ code rather then coding the functionality again in Python.

    Anyway, if you really want to here are some hints:

    • determine which cells are selected in the tree: LayoutView#selected_cells_paths. Note that a cell can be selected inside the tree in a particular inheritance chain - hence a "path". You're probably just interested in the last element of the path.
    • you can only select instances, not cells. Use LayoutView#object_selection=(path_to_object). "path_to_object" is one ObjectInstPath object. See there for more details.
    • the selection line width can be changed with setting the "sel-line-width" configuration property with Application#set_config. Pass the desired line width as a string.

    Matthias

  • Hi Matthias,
    I have to admit I don't quite get the path assignment thing. I think I am close, but I just don't know how to set up the Path objects for lv.object_selection. Can you give me a hint on how that's done ?

    Thanks.

    Thomas.

        def highlight(parent, useOriginalLprops)
            clearCellSelections()
            getSelectedCellIndex()
            puts "#{@selectedCellIndex.length} Selected Cells"
            selectCells()
        end
    
        def clearCellSelections()
            lv  = @mw.current_view
            lv.each_object_selected do |s|
                if !s.is_cell_inst?
                    lv.unselect_object(s)
                end
            end
        end
    
        def selectCells()
            lv       = @mw.current_view
            cv       = lv.cellview(lv.active_cellview_index)
            cv_index = cv.cell_index
            ly       = cv.layout 
            oiPaths  = []
    
            if @selectedCellIndex!=nil
                @selectedCellIndex.each do |cell_index|
                    cell   = ly.cell(cell_index)
    
                    ######### I need help here #########
                    iE     = RBA:InstElement.new(cell)
                    oiPath = RBA::ObjectInstPath.new
                    oiPath.path = iE
                    oiPaths << oiPath
                end
                lv.object_selection=oiPaths
            end
        end
    
        def getSelectedCellIndex()
            @selectedCellIndex = []
            lv  = @mw.current_view
            if lv!=nil
                cv  = lv.cellview(lv.active_cellview_index)
                if cv.is_valid?
                    cv_index = cv.cell_index
                    ly = cv.layout
                    if lv!=nil
                        lvi = @mw.current_view_index
                        ly.each_top_cell do |index_topcell|
                            top_cell = ly.cell(index_topcell)
                            top_cell_view_index = cv.index
    
                            selected_cells = lv.selected_cells_paths(top_cell_view_index )
                            if selected_cells!=nil
                                if selected_cells.length>0
                                    selected_cells.each do |x|
                                        if x!=nil
                                            if x.length>0
                                                @selectedCellIndex << x[x.length-1]
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    
  • Hi Thomas,

    if you don't care about where a cell is selected in the cell tree, you don't need to analyze the fill selected cell path. The last item is sufficient:

    lv = RBA::LayoutView::current
    cv_index = lv.active_cellview_index
    ly = lv.cellview(cv_index).layout
    
    puts "Selected cell(s):"
    lv.selected_cells_paths(cv_index).each do |cp|
      puts ly.cell(cp[1]).name
    end
    

    But when it comes to displaying the cells in the layout, you'll nee to generate a full instantiation path from each instance of the cell you want to highlight. The easiest way is to recursively walk the "parent" tree upwards from the cell you want to hightlight until you reach the current top cell (or skip that path if it ends somewhere else). The selected instance path is basically the reverse of the parent path. You also need to consider that some instances are arrays and you need to decide if you want to highlight all the expanded instances of those.

    You're sure you don't want to use the instance browser?

    Matthias

  • Hi Matthias,
    I spent the whole day on this and I got to the same conclusion as you: I need to find all the instances of the selected cells, then build paths that describe TopCell-->ThatCell, and hand that to lv.object_selection. I came up with a good way of finding the instances recursively in getInstanceElements(), but I am having a hard time piecing together the paths to the top cell. The application crashes if lv.object_selection = objectInstancePaths has only the target cell instanceElement in it.
    getInstanceElementsToTop() is what I am struggling with. I suppose the actual pathway to the top is not so important but since the path needs instanceElements, I don't really know how to convert what I encounter to instances, as I walk up the ladder. And you're right, there could be paths that dont end up at the top cell. Therefore, I need to look at all paths and pick the first that gets all the way up there. Also, if the topcell needs to be an instanceElement as well, I don't know how to get that info from the top cell either :-(
    I saw that there is RecursiveShapeIterator.path that gives me a path of a shape, but there is no Cell.path. Does the code for RecursiveShapeIterator.path have the construct for Cell.path in it ? Maybe I could make use of that ?
    Also, I was contemplating drawing a shape on a dummy layer into the cell instance, and using RecursiveShapeIterator.path to get the path that I so desperately need, and the just delete the drawn shape. If there are shapes already in the cell instance, then I could also just grab the first one.
    Is the path of a shape in a cell instance just +1 longer than that of the cell instance ?

    Thanks.
    (this is confusing)

    Thomas.

        ############################################################################################################        
        def highlight(parent, useOriginalLprops)
            clearCellSelections()
            getSelectedCellIndex()
            puts "#{@selectedCellIndex.length} Selected Cells"
            selectCells()
        end
        ############################################################################################################
        def clearCellSelections()
            lv  = @mw.current_view
            lv.each_object_selected do |s|
                if !s.is_cell_inst?
                    lv.unselect_object(s)
                end
            end
            lv.clear_object_selection
        end
        ############################################################################################################        
        def getInstanceElementsToTop(instElement, cellindex, lv, cv)
            instElements = []
            instElements << instElement
            inst        = instElement.inst
            cell        = inst.cell
            ly          = cv.layout
            foundParent = false
            while !foundParent
                foundParent = true
                cell.each_parent_inst do |parent_inst_array|
                    parentCellInstArr = parent_inst_array.inst
    
                    parent_cell.each_inst do |cell_inst_in_parent|
    
                        if cell_inst_in_parent==cellindex
                            parent_instElement = RBA::InstElement.new(parent_inst)
                            instElements      << parent_instElement
                            puts " up the tree for #{cellindex}: " + parent_cell.name                           
                            break
                        end
                    end
    
                    foundParent        = false
                    break
                end
            end
        end
        ############################################################################################################        
        def getInstanceElements(instElements, topcell, cell, cellindex, lv, cv)
            cell.each_inst do |inst|
                if inst.cell_index==cellindex
                    instElements      = []
                    instElement       = RBA::InstElement.new(inst)
                    instElements      << instElement
                    objectInstancePath          = RBA::ObjectInstPath.new
                    objectInstancePath.top      = topcell.cell_index
                    objectInstancePath.cv_index = cv.cell_index
                    objectInstancePath.clear_path   
                    objectInstancePath.path     = instElements
                    lv.select_object(objectInstancePath)
                    objectInstancePaths = []
                    objectInstancePaths << objectInstancePath
                                       # THIS CRASHES because it is missing the path elements up to the top cell
                    lv.object_selection = objectInstancePaths
                    puts " found and selected #{cellindex} in " + cell.name
                else
                    instElements << getInstanceElements(instElements, topcell, inst.cell, cellindex, lv, cv)
                end
    
            end 
            return instElements
        end
        ############################################################################################################
        def getCellInstancePaths(cellindex, lv, cv)
    
            puts "cv.path = " + cv.path.to_s
            objectInstancePaths = []
    
            puts " path cleared"            
            ly           = cv.layout 
            cell         = ly.cell(cellindex)
            topcell      = ly.top_cell
            iPath        = 0
            instElements = []
            instElements = getInstanceElements(instElements, topcell, topcell, cellindex, lv, cv)
            return objectInstancePaths
        end
        ############################################################################################################        
        def selectCells()
            lv       = @mw.current_view
            cv       = lv.cellview(lv.active_cellview_index)
            cv_index = cv.cell_index
            ly       = cv.layout 
            oiPaths  = []
    
            if @selectedPaths!=nil
                if @selectedPaths.length>0
                    objectInstancePaths = []
                    @selectedPaths.each do |path|
    
                        objectInstancePaths << getCellInstancePaths(path[path.length-1], lv, cv)
                        puts " path appended, now #{objectInstancePaths.length}"
                    end
                end
            end         
        end
        ############################################################################################################
        def getSelectedCellIndex()
            @selectedCellIndex = []
            @selectedPaths     = []
            lv  = @mw.current_view
            if lv!=nil
                cv  = lv.cellview(lv.active_cellview_index)
                if cv.is_valid?
                    cv_index = cv.cell_index
                    ly = cv.layout
                    if lv!=nil
                        lvi = @mw.current_view_index
                        ly.each_top_cell do |index_topcell|
                            top_cell = ly.cell(index_topcell)
                            top_cell_view_index = cv.index
    
                            selected_cells = lv.selected_cells_paths(top_cell_view_index )
                            if selected_cells!=nil
                                if selected_cells.length>0
                                    @selectedPaths = selected_cells
                                    puts selected_cells.to_s
                                    selected_cells.each do |x|
                                        if x!=nil
                                            if x.length>0
                                                @selectedCellIndex << x[x.length-1]
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    
  • Hi Thomas,

    the actual path to the top is important if you want to highlight all instances. I think that highlighting just one instance isn't giving much benefit.

    I see that the default constructor of "ObjectInstPath" isn't explicitly listed, but you can create one if you fill it's attributes manually. Here is some code which selects all instances of "CELL_TO_HIGHLIGHT" in the current cell.

    lv = pya.LayoutView.current()
    cv = pya.CellView.active()
    ly = cv.layout()
    
    top_cell = cv.cell
    cell_to_highlight = ly.cell("CELL_TO_HIGHLIGHT")
    
    def collect_paths_from_top_to_cell(ly, top, to_cell):
    
      paths = []
      for pi in to_cell.each_parent_inst():
    
        inst_element = pya.InstElement(pi.child_inst())
        parent_ci = pi.parent_cell_index()
    
        if parent_ci == top.cell_index():
    
          # we found the top cell
          paths.append([ inst_element ])
    
        else:
    
          # collect the paths leading from top to the parent and 
          # append the last instance we just derived from the parent
          parent_paths = collect_paths_from_top_to_cell(ly, top, ly.cell(parent_ci))
          for pp in parent_paths:
            pp.append(inst_element)
            paths.append(pp)
    
      return paths
    
    # highligh all instances of cell_to_highlight in top_cell
    
    paths = collect_paths_from_top_to_cell(ly, top_cell, cell_to_highlight)
    
    selections = []
    for p in paths:
      selection = pya.ObjectInstPath()
      selection.cv_index = cv.index()
      selection.top = cv.cell_index
      selection.path = p
      selections.append(selection)
    
    lv.object_selection = selections
    

    BTW: for some reason in my installation, "LayoutView#clear_selection" crashes. I need to debug this.

    Matthias

  • Hi Matthias,

    thank you soooo much for this. This works nicely. I was wracking my brain on this one.
    I know the GUI cannot even do that, but is there a way to select an array ? I noticed that this code only selects the cell instance, not the entire array if the selected cell turns out to be placed as an array.

    Thanks.

    Thomas.

  • Well, if anyone is interested, here is the full code. It is helpful when troubleshooting layouts. However, a warning is in order. It does take a long time if a cell is selected that is placed a lot of times in the layout (meaning hundreds of times - like via cells for example), since all possible paths of all instances are being collected. Not attached is the icon that I used (cell_highlighter.png).

  • Hi Thomas,

    very good. Thanks for providing the code!

    Matthias

  • Just FYI, the paths were found from the selected cell to the top cell. This turned out to be wrong, because the user may be in another cellview (not the topcell). This means that unless you're in the topcell, it will highlight the selected cells in wrong places (where there are no cells).

        paths = collect_paths_from_top_to_cell(ly, top_cell, ly.cell(selectedCellIndex))
    

    needed to be changed to

        paths = collect_paths_from_top_to_cell(ly, cv.cell, ly.cell(selectedCellIndex))
    

    and then everything worked ok.

        ############################################################################################################
        def getSelectedCellIndex()
            @selectedCellIndex = []
            @selectedPaths     = []
            lv  = @mw.current_view
            if lv!=nil
                cv  = lv.cellview(lv.active_cellview_index)
                if cv.is_valid?
                    cv_index = cv.cell_index
                    ly       = cv.layout
                    if lv!=nil
                        lvi = @mw.current_view_index
                        ly.each_top_cell do |index_topcell|
                            top_cell            = ly.cell(index_topcell)
                            top_cell_view_index = cv.index
    
                            selected_cells = lv.selected_cells_paths(top_cell_view_index)
                            if selected_cells!=nil
                                if selected_cells.length>0
                                    selected_cells.each do |x|
                                        if x!=nil
                                            if x.length>0
                                                selectedCellIndex   = x[x.length-1]
                                                @selectedCellIndex << selectedCellIndex
                                                paths               = collect_paths_from_top_to_cell(ly, cv.cell, ly.cell(selectedCellIndex))
                                                @selectedPaths      = @selectedPaths.concat(paths)
                                            end
                                        end
                                    end
                                end
                            end
                        end
    
                    end
                end
            end
        end
    
  • Very good! Thanks for sharing :)

    Matthias

Sign In or Register to comment.