Snap-to-grid (post facto) function?

I've been having some issues with grid bumping up and
down (probably due to fat-fingering the bindkeys, unaware).
So I have cell instances and polygon interconnect that are
now placed at a variety of grids and it's a bit ugly.

I've looked for, but not found, any "snap to grid" utility
that I would use to get everything on the 1.0u grid again.
I only find mention of snapping as a piece of other functions'
activity in the forum. There is nothing about a snap function
for objects in the manual, only Rulers.

Wonder if someone has a macro script for this perhaps?
All I really want to do is strip decimal places (rounding to
closest, would be acceptable but not strictly necessary) in
object origin / vertices, but not by hand one object at a time.

Comments

  • Hi Jim,

    DRC has a snapping feature, but that will flatten your layer and turn all paths and boxes into polygons and remove texts.

    A dirty trick is to save your layout with 1 µm database unit and later with a finer DBU again. This will force snapping, but beware of manifold snap artefacts.

    Matthias

  • Hi Jim,

    A long time ago I made the example below when working my way into Ruby and scripting. The code is most likely not the "cleanest" but it works for me. Note that it is not recursive and the vectors of instance arrays are also put on grid (you might want to resolve instance arrays first).

    Cheers,

    Tomas

    module MyMacro

    Puts selected shapes (boxes, polygons and paths) and instances (origin and array parameters on a user-defined grid.

    This function is NOT recursive.

    include RBA

    app = Application.instance
    mw = app.main_window

    lv = mw.current_view
    cv = lv.active_cellview
    ly = cv.layout

    dbu = ly.dbu

    ok = Value.new(true)

    grid = QInputDialog.getDouble(mw, "getDouble()", "Value", 0.1, dbu, 10, 5, ok)

    if ok.value

    begin

    lv.transaction("Put selected objects on grid")

    if lv == nil
    raise "Shape Statistics: No view selected"
    end

    grid_dbu = grid/dbu

    instances_to_modify = []
    shapes_to_modify = []

    lv.each_object_selected do |sel|

    if sel.is_cell_inst?
    
     instances_to_modify.push(sel) 
    
    else 
    
     shapes_to_modify.push(sel)
    
    end
    

    end

    instances_to_modify.each do |instance|

    instance = instance.inst

    trans0 = instance.trans

    text = instance.trans.to_s
    text2 = text.gsub(",", "\s").split("\s")

    px = text2[1].to_i
    py = text2[2].to_i

    point1 = Point.new(-px,-py)
    trans1 = Trans.new(point1)

    px = ((px/grid_dbu).round.to_igrid_dbu).to_i
    py = ((py/grid_dbu).round.to_i
    grid_dbu).to_i

    point2 = Point.new(px,py)
    trans2 = Trans.new(point2)

    instance.trans=trans2trans1trans0

    a = instance.a
    b = instance.b

    ax = a.x
    ay = a.y
    bx = b.x
    by = b.y

    ax = ((ax/grid_dbu).round.to_igrid_dbu).to_i
    ay = ((ay/grid_dbu).round.to_i
    grid_dbu).to_i
    bx = ((bx/grid_dbu).round.to_igrid_dbu).to_i
    by = ((by/grid_dbu).round.to_i
    grid_dbu).to_i

    a = Point.new(ax,ay)
    b = Point.new(bx,by)

    instance.a=(a)
    instance.b=(b)

    end

    shapes_to_modify.each do |shape|

    if shape.shape.is_box?

    box = shape.shape
    
    p1 = box.box_p1
    p2 = box.box_p2
    p1x = p1.x
    p1y = p1.y
    p2x = p2.x
    p2y = p2.y
    
    p1x = ((p1x/grid_dbu).round.to_i*grid_dbu).to_i
    p1y = ((p1y/grid_dbu).round.to_i*grid_dbu).to_i
    p2x = ((p2x/grid_dbu).round.to_i*grid_dbu).to_i
    p2y = ((p2y/grid_dbu).round.to_i*grid_dbu).to_i
    
    p1 = Point.new(p1x,p1y)
    p2 = Point.new(p2x,p2y)
    box.box_p1=(p1)
    box.box_p2=(p2)
    

    elsif shape.shape.is_polygon?

    polygon = shape.shape
    
    hull = []
    
    polygon.each_point_hull do |point|
    
    px = point.x
    py = point.y
    
    px = ((px/grid_dbu).round.to_i*grid_dbu).to_i
    py = ((py/grid_dbu).round.to_i*grid_dbu).to_i
    
    point = Point.new(px,py)
    
    hull.push(point)
    
    end
    

    polygonnew = Polygon.new(hull)
    polygon.polygon=polygonnew

    elsif shape.shape.is_path?

    path = shape.shape
    
    points = []
    
    path.each_point do |point|
    
    px = point.x
    py = point.y
    
    px = ((px/grid_dbu).round.to_i*grid_dbu).to_i
    py = ((py/grid_dbu).round.to_i*grid_dbu).to_i
    
    point = Point.new(px,py)
    
    points.push(point)
    
    end
    

    width = path.path.width

    width = ((width/2/grid_dbu).round.to_i*grid_dbu).to_i * 2

    bgn_ext = path.path.bgn_ext

    end_ext = path.path.end_ext

    is_round = path.path.is_round?

    pathnew = Path.new(points, width, bgn_ext, end_ext, is_round)
    path.path=pathnew

    end

    end

    ensure
    lv.commit
    end

    end

    end

  • Hi Thomas,

    if you want to achieve readability, you can put a triple backtick line in front of and after your code. Then the code will be formatted as such (markdown). See http://en.wikipedia.org/wiki/Markdown for details.

    Kind regards,

    Matthias

  • Hi Matthias,

    Thanks for the tip, could not edit, so re-post below...


    module MyMacro # Puts selected shapes (boxes, polygons and paths) and instances (origin and array parameters on a user-defined grid. # This function is NOT recursive. include RBA app = Application.instance mw = app.main_window lv = mw.current_view cv = lv.active_cellview ly = cv.layout dbu = ly.dbu ok = Value.new(true) grid = QInputDialog.getDouble(mw, "getDouble()", "Value", 0.1, dbu, 10, 5, ok) if ok.value begin lv.transaction("Put selected objects on grid") if lv == nil raise "Shape Statistics: No view selected" end grid_dbu = grid/dbu instances_to_modify = [] shapes_to_modify = [] lv.each_object_selected do |sel| if sel.is_cell_inst? instances_to_modify.push(sel) else shapes_to_modify.push(sel) end end instances_to_modify.each do |instance| instance = instance.inst trans0 = instance.trans text = instance.trans.to_s text2 = text.gsub(",", "\s").split("\s") px = text2[1].to_i py = text2[2].to_i point1 = Point.new(-px,-py) trans1 = Trans.new(point1) px = ((px/grid_dbu).round.to_i*grid_dbu).to_i py = ((py/grid_dbu).round.to_i*grid_dbu).to_i point2 = Point.new(px,py) trans2 = Trans.new(point2) instance.trans=trans2*trans1*trans0 a = instance.a b = instance.b ax = a.x ay = a.y bx = b.x by = b.y ax = ((ax/grid_dbu).round.to_i*grid_dbu).to_i ay = ((ay/grid_dbu).round.to_i*grid_dbu).to_i bx = ((bx/grid_dbu).round.to_i*grid_dbu).to_i by = ((by/grid_dbu).round.to_i*grid_dbu).to_i a = Point.new(ax,ay) b = Point.new(bx,by) instance.a=(a) instance.b=(b) end shapes_to_modify.each do |shape| if shape.shape.is_box? box = shape.shape p1 = box.box_p1 p2 = box.box_p2 p1x = p1.x p1y = p1.y p2x = p2.x p2y = p2.y p1x = ((p1x/grid_dbu).round.to_i*grid_dbu).to_i p1y = ((p1y/grid_dbu).round.to_i*grid_dbu).to_i p2x = ((p2x/grid_dbu).round.to_i*grid_dbu).to_i p2y = ((p2y/grid_dbu).round.to_i*grid_dbu).to_i p1 = Point.new(p1x,p1y) p2 = Point.new(p2x,p2y) box.box_p1=(p1) box.box_p2=(p2) elsif shape.shape.is_polygon? polygon = shape.shape hull = [] polygon.each_point_hull do |point| px = point.x py = point.y px = ((px/grid_dbu).round.to_i*grid_dbu).to_i py = ((py/grid_dbu).round.to_i*grid_dbu).to_i point = Point.new(px,py) hull.push(point) end polygonnew = Polygon.new(hull) polygon.polygon=polygonnew elsif shape.shape.is_path? path = shape.shape points = [] path.each_point do |point| px = point.x py = point.y px = ((px/grid_dbu).round.to_i*grid_dbu).to_i py = ((py/grid_dbu).round.to_i*grid_dbu).to_i point = Point.new(px,py) points.push(point) end width = path.path.width width = ((width/2/grid_dbu).round.to_i*grid_dbu).to_i * 2 bgn_ext = path.path.bgn_ext end_ext = path.path.end_ext is_round = path.path.is_round? pathnew = Path.new(points, width, bgn_ext, end_ext, is_round) path.path=pathnew end end ensure lv.commit end end end
  • Very good ... that's better to read :-)

    I think something is wrong at the beginning (make sure the initial line is only three backticks). But that's just a small flaw.

  • I tried it, it work nearly fine except that the rounding gives : 0.999 and not 1.000 for instance.
    And the shape stay off grid :o

    Laurent

  • I tried it, it work nearly fine except that the rounding gives : 0.999 and not 1.000 for instance.
    And the shape stay off grid :o

    I tried scle_and_snap : it work very well ! But all path are turned to polygons and all arrays are flattened, so the GDS becomes huge !
    It is OK at the end of the layout before tape-out, but not to transfer a design from a technology to another.

    Laurent

  • For other readers here is the code for scale_and_snap Laurent refers to. It's based on the Layout#scale_and_snap function:

    # Usage:
    #
    # klayout -b -r scale_and_snap.rb \
    #   -rd input=<input-layout> \
    #   -rd output=<output-layout> \
    #   -rd grid=<snap-grid> \
    #   -rd mult=<scale-mult> \
    #   -rd div=<scale-div> 
    #
    # The script will read <input-layout>, apply
    # a scaling of <scale-mult>/<scale-div> (a rational factor)
    # and snap to <snap-grid> (in um). The result will be written
    # to <output-layout>.
    #
    # grid, mult and div are optional and default to "snap to 1 DBU",
    # and "no scaling".
    # 
    # Notes:
    #   1. The script requires a unique top cell. If you need to
    #      apply it to a specific cell, replace "ly.top_cell" by 
    #      "ly.cell(name)".
    #   2. The script operates hierarchically starting from the top
    #      cell.
    #   3. Scaling and snapping is "as if flat". This means that
    #      edges at the same "flat" coordinate (as seen from the top cell)
    #      will always snap to the same target coordinate. Hence, no
    #      gaps will be produced.
    #   4. In order to achieve this goal, the algorithm has to create
    #      grid offset variants of cells. Hence the cell count may
    #      increase if off-grid placements are present.
    
    ly = RBA::Layout::new
    ly.read($input)
    ly.scale_and_snap(ly.top_cell, (0.5 + ($grid || ly.dbu).to_f / ly.dbu).floor.to_i, ($mult || 1).to_i, ($div || 1).to_i)
    ly.write($output)
    

    The processing will explode instance arrays as it needs to analyse each instantiation separately. I think this does not happen when the array repetition vector is already on-grid, but I may be wrong.

    And paths can't be corner-snapped in general - just snapping the spine would not necessarily produce an on-grid hull contour. Hence the conversion to polygons.

    And Laurent's reply is valid: this script is not something to use during layout but for layout cleaning before tapeout.

    Matthias

  • I have run into trouble using paths relating to the non-grid
    vertices of the "hull". I have moved to a style where I build
    polygon "paths" out of ortho and diagonal on-grid "sticks"
    and merge them.

    I think that a cool feature could be a "partial" feature-group
    addition, "nudge selected vertices to grid". Of course the
    devil is in the details - what to give up, to get grid-aligned;
    are you going to let path width shrink below the property
    value, if so how / where? Probably entails making the path
    into a poly right off.

    I've done some of this too by hand, but it gets messy as you
    have to dial the snap-grid down to the DBU in some cases,
    then hand drag it to the grid point (otherwise you'll move -by-
    a grid-step, not -to- a grid-step).

    Maybe there's a way, though?

  • Snap to grid is a good suggestion, could be used for partial mode too.

    Snapping of paths is kind of difficult. In the general case I'd say snapping should be applied to the vertices. If path width is a multiple of twice the grid and the extensions are multiples of the grid too then the hull points would usually be on-grid too. Except if the path has any-angle segments: in this case the corners will almost always be off-grid.

    Matthias

Sign In or Register to comment.