Script to extract the pads center location and pads names into a file

edited July 2013 in Ruby Scripting

To package any die, you need a bonding diagram based on the pads center location and pads names. This script extract them from the layout and write a file based on :

  • the input layer for the pads
  • the input layer and hierarchy depth for the pads name
  • the input of a file name to save the extracted list

If you find the script useful or if you have remarks, please, tell me so that I know if it is OK for you or if I should try (at least :) to improve it.

Note that the script may be long, so I first request all the inputs, including the filename to save the extracted list, so that you can let the script run for a long time ... at lunch or evening time.

Laurent

#############################################################################
#
# DESCRIPTION: Save the pads center location and name into a file
#
# Run the script with
#   klayout -rm pads_location.rbm ...
# or put the script as "pads_location.rbm" into the installation path (on Unix for version <=0.21:
# set $KLAYOUTPATH to the installation folder).
#

# convert the layer specification into a LayerInfo structure
# format: "l", "l/d", "n(l/d)" or "n".
def string_to_layerinfo(layer_spec)
  ls = nil
  if (layer_spec =~ /^(\d+)$/) 
    ls = RBA::LayerInfo.new($1.to_i, 0)
  elsif (layer_spec =~ /^(\d+)\/(\d+)$/) 
    ls = RBA::LayerInfo.new($1.to_i, $2.to_i)
  elsif (layer_spec =~ /^(.*)\s*\((\d+)\/(\d+)\)$/) 
    ls = RBA::LayerInfo.new($2.to_i, $3.to_i, $1)
  elsif (layer_spec =~ /^(\d+)\/(\d+)(@)(\d+)$/)
    ls = RBA::LayerInfo.new($1.to_i, $2.to_i, $4)
  else
    raise "The format for the layer is not good."
  end
  return ls
end

include RBA
require 'ostruct'

def find_texts(lo, cl, layer_idx, recursion_limit)
  texts = {}
  nbr_pads = 0
  cl.each_shape(layer_idx) do |shape|
    if shape.is_text?
      text = shape.text
      point = text.trans.trans(Point.new(0,0))
      name = ([text.string]).join('.')
      texts[nbr_pads.to_s] = OpenStruct.new(:point=>point, :pad_name=>text.string)
      nbr_pads = nbr_pads + 1
    end
  end

  if (recursion_limit>0)
    cl.each_inst do |inst|
      child_cell = lo.cell(inst.cell_index)
      cell_name = child_cell.basic_name
      puts "Descending into cell #{cell_name}"
      child_texts = find_texts(lo, child_cell, layer_idx, recursion_limit-1)
      puts "Finished cell #{cell_name}"
      child_texts.each do |pad_nbr, data|
        texts[nbr_pads.to_s] = OpenStruct.new(:point=>inst.trans.trans(child_texts[pad_nbr].point), :pad_name=>data.pad_name)
        nbr_pads = nbr_pads + 1
      end
    end
  end
  return texts
end

def find_polygons(lo, cl, layer_idx, recursion_limit=0)
  polygons = {}
  nbr_pads = 0
  cl.each_shape(layer_idx) do |shape|
    if shape.is_polygon?
      polygons[nbr_pads.to_s] = OpenStruct.new(:point1=>shape.bbox.p1, :point2=>shape.bbox.p2)
      nbr_pads = nbr_pads + 1
    end
  end

  # if recursion_limit > 0 # the recusion limit was used only for debug, for usage it is unlimited
    cl.each_inst do |inst|
      child_cell = lo.cell(inst.cell_index)
      cell_name = child_cell.basic_name
      puts "Descending into cell #{cell_name}"
      child_polygons = find_polygons(lo, child_cell, layer_idx, recursion_limit-1)
      puts "Finished cell #{cell_name}"
      child_polygons.each do |name, point1, point2|
        polygons[nbr_pads.to_s] = OpenStruct.new(:point1=>inst.trans.trans(child_polygons[name].point1), :point2=>inst.trans.trans(child_polygons[name].point2))
        nbr_pads = nbr_pads + 1
      end
    end
  # end # the recusion limit was used only for debug, for usage it is unlimited
  return polygons
end


$pads_location = MenuAction.new( "Pads name/location", "" ) do 
  app = RBA::Application.instance
  mw = app.main_window

  lv = mw.current_view
  if lv == nil
    raise "No view selected"
  end

  cv = lv.active_cellview
  if !cv.is_valid?
    raise "No cell or no layout found"
  end

  cl = cv.cell
  ci = cv.cell_index
  lo = cv.layout

  ### Layer used to draw each pad opening
  pad_layer = RBA::InputDialog.get_string(" Layer of the pad opening","Enter the GDS number and datatype (ex: 1 or 1/0)\n of the layer of the pad opening :", "*")
  if !pad_layer.has_value?
    return
  end

  # The layer specification string is either "l", "l/d", "n(l/d)" or "n".
  lp = string_to_layerinfo(pad_layer.to_s)

  # locate the layer if it already exists
  pad_layer_index = nil
  lp_found = 0
  (0..(lo.layers-1)).each do |i|
    if lo.is_valid_layer?(i) && lo.get_info(i).is_equivalent?(lp)
      pad_layer_index = i
      lp_found = 1
      break
    end
  end
  if lp_found == 0
    raise "Layer not found !"
  end
  lp_found = 0
  iter = lv.begin_layers
  while !iter.at_end?
    lp = iter.current
    # if lp.cellview == ci && lp.layer_index == pad_layer_index
    if lp.layer_index == pad_layer_index
      pad_layerPropertiesNode = lp
      lp_found = 1 
    end
    iter.next
  end
  if lp_found == 0
    raise "Layer not found!"
  end

  ### Layer used to set the names of each pad
  name_layer = RBA::InputDialog.get_string(" Layer of the pad names","Enter the GDS number and datatype (ex: 1 or 1/0)\n of the layer of the pad names :", "*")
  if !name_layer.has_value?
    return
  end
  # The layer specification string is either "l", "l/d", "n(l/d)" or "n".
  lp = string_to_layerinfo(name_layer.to_s)

  # locate the layer if it already exists
  name_layer_index = nil
  lp_found = 0
  (0..(lo.layers-1)).each do |i|
    if lo.is_valid_layer?(i) && lo.get_info(i).is_equivalent?(lp)
      name_layer_index = i
      lp_found = 1
      break
    end
  end
  if lp_found == 0
    raise "Layer not found !"
  end
  lp_found = 0
  iter = lv.begin_layers
  while !iter.at_end?
    lp = iter.current
    if lp.layer_index == name_layer_index
      lp_found = 1
      name_layerPropertiesNode = lp 
    end
    iter.next
  end
  if lp_found == 0
    raise "Layer not found!"
  end

  name_recursion = RBA::InputDialog.get_string(" Hierarchy of the pad names","Enter the depth in the hierarchy to find the pad names \n 0 ( = TopLevel ) should be OK :", "0")
  if !name_recursion.has_value?
    return
  end
  # Ask for the output file name 
  filename = RBA::FileDialog.get_save_file_name("Pads location file ", ".", "All files (*)")
  if !filename.has_value?
    return
  end

  confirm = RBA::MessageBox.info("Take your time !","Pad Layer   : #{pad_layerPropertiesNode.name}\nNames layer :  #{name_layerPropertiesNode.name}\n\nHierarchy to find the name : #{name_recursion.to_s}\n\nThe routine will look for the pads and their names.\nIt may need several minutes, please, be patient.", RBA::MessageBox.b_ok + RBA::MessageBox.b_cancel)
  if confirm == RBA::MessageBox.b_cancel
    raise "Operation aborted"
  end

  texts = find_texts(lo, cl, name_layer_index, name_recursion.to_s.to_i)
  polygons = find_polygons(lo, cl, pad_layer_index, 1)

  pad_layer = lo.get_info(pad_layer_index)
  name_layer = lo.get_info(name_layer_index)

  # save the data in the file :
  File.open(filename.value, "w") do |file|
    file.puts("*****************************************************************")
    file.puts("*\n*  PADS LOCATION extracted with KLayout.de")
    file.puts("*  Date : #{Time.now.strftime("%d/%m/%Y %H:%M")}")
    file.puts("*\n*  GDS filename : #{cv.name}")
    file.puts("*\n*  TopCell name : #{lo.cell_name(ci)}")
    file.puts("*\n*  Pad Layer    : #{pad_layerPropertiesNode.name}")
    file.puts("*  Names layer  : #{name_layerPropertiesNode.name}")
    file.puts("*\n*  Hierarchy depth to find the names :  #{name_recursion.to_s}")
    file.puts("*\n*****************************************************************\n\n")

    pad_count = 0
    polygons.each do |textpl, datapl|
      find_padname = 0
      pad_count = pad_count + 1
      texts.each do |textnm, datanm|
        if ([datapl.point1.x, datapl.point2.x].min<datanm.point.x && [datapl.point1.x, datapl.point2.x].max>datanm.point.x && [datapl.point1.y,datapl.point2.y].min<datanm.point.y && [datapl.point1.y,datapl.point2.y].max>datanm.point.y)
          find_padname = 1
          file.puts(" #{pad_count} : #{datanm.pad_name} : #{((datapl.point1.x+datapl.point2.x)*lo.dbu/2).round} , #{((datapl.point1.y+datapl.point2.y)*lo.dbu/2).round}")
          break
        end
      end
      if (find_padname == 0)
        file.puts(" #{pad_count} : no_name : #{((datapl.point1.x+datapl.point2.x)*lo.dbu/2).round} , #{((datapl.point1.y+datapl.point2.y)*lo.dbu/2).round}")
      end
    end
  end

end

app = RBA::Application.instance
mw = app.main_window

menu = mw.menu
menu.insert_separator("tools_menu.end", "name")
menu.insert_item("tools_menu.end", "pads_location", $pads_location)

Comments

  • edited November -1

    Hi Laurent,

    thanks for sharing the script with us.

    I took the liberty to nicely format your nice script :-)

    The forum supports Markdown formatting - if the Markdown checkbox is checked, code can be formatted by indenting it with four spaces.

    Thanks,

    Matthias

Sign In or Register to comment.