It looks like you're new here. If you want to get involved, click one of these buttons!
Hi,
I'm creating a script called "Smart array". The idea is that you have a PCell that you want to array, but you want each instance of the PCell to have different input parameters. You can do that manually but it's not ideal for large arrays.
So, "Smart array" takes input from a spreadsheet where each column is a PCell input (e.g. for Basic.TEXT, inputs include "text", "layer", "mag"). The input is listed in the spreadsheet. For example here is a test spreadsheet called testTEXT.xlsx:
text | layer | mag
"KLAYOUT RULES" | RBA::LayerInfo::new(1, 0) | 2.5
"KLAYOUT RULES AGAIN" | RBA::LayerInfo::new(1, 0) | 2.5
"KLAYOUT RULES YET AGAIN" | RBA::LayerInfo::new(1, 0) | 2.5
And here is a supporting .rb script called "Misc tools.rb" that just does the extraction of data from the spreadsheet:
class MiscTools
def self.get_spreadsheet_values(spreadsheet_file)
include RBA
require 'win32ole'
begin
excel = WIN32OLE.connect('excel.application')
rescue
excel = WIN32OLE.new('excel.application')
RBA::MessageBox::warning("Error", "Excel is not running. Starting it in the background now.", RBA::MessageBox::Ok)
end
excel.DisplayAlerts = false
wb = excel.Workbooks.Open(spreadsheet_file)
values_read = ''
#Loop through sheets. This is ok even if you only have data on one sheet
wb.sheets.each do |sh|
values_read = sh.range( sh.cells( 1, 1 ), sh.cells.specialcells( 11 )).value
end
wb.Close(0)
excel.Quit unless excel.visible
values_read
end
end
Lastly, here is the actual code. This code works just fine as-is, though:
Start with an open layout with an open cell with layer 1 present
you have to change the "load File.expand_path("./Misc/Misc tools.rb", $PHTOOLS)" line, to point to Misc tools.rb script above
It only arrays in y right now (ie it makes an array with one column and in this case three rows), and ignores the x value. But if you use the default numbers and the spreadsheet above, this is an unimportant point for now.
There is some strange behavior at the end, unrelated to my code (I think). Truly I don't mean for someone to have to dig through this long code, but just want to see if this strange behavior is something you've seen before. Basically what happens is the code generates the PCells as expected, but you can't see them until you:
4A. either show or hide a layer (any layer - it doesn't have to be the one the PCells are on), and then ...
4B. ...zoom in or out by some amount.
You have to do both 4A and 4B, then the PCells magically appear (though you might have to press "*" to see inside them).
Basically I think there is a redraw problem. Secondly, the trick with showing or hiding the layers as I just described is slow - sometimes you have to wait a few seconds to show or hide a layer even if there is nothing else in your layout, and the code is complete (evidenced by the "p 'Done!'" line at the end). So I don't think it's due to my code since the code running has completed.
Any ideas on how to force this redraw? Or speed the laggy-ness up?
Here is the code, which I call "Smart array.rb"
module SmartArray2
include RBA
lv = RBA::Application.instance.main_window.current_view
if lv == nil
raise "No view selected"
end
app = RBA::Application.instance
mw = app.main_window
layout = lv.active_cellview.layout
top = lv.active_cellview.cell
lv.transaction("Smart array")
begin
# Get the params from the user
dialog = QDialog.new(Application.instance.main_window)
dialog.windowTitle = "Smart array inputs"
qt_layout = QVBoxLayout::new(dialog)
dialog.setLayout(qt_layout)
# variable text input :
label = QLabel.new(dialog)
qt_layout.addWidget(label)
label.text = "Source file should be an .xlsx file, with header row showing the names of the PCell values to set, obtained from '$PHTOOLS\\Misc\\Find pcell params.rb'."
label = QLabel.new(dialog)
qt_layout.addWidget(label)
label.text = "Spreadsheet filepath:"
filepath = QLineEdit.new(dialog)
qt_layout.addWidget(filepath)
filepath.text = 'C:\filepath\\'
label = QLabel.new(dialog)
qt_layout.addWidget(label)
label.text = "\nSpreadsheet filename:"
filename = QLineEdit.new(dialog)
qt_layout.addWidget(filename)
filename.text = 'testTEXT.xlsx'
# x and y, and pitches, of array
label = QLabel.new(dialog)
qt_layout.addWidget(label)
label.text = "Number in x and y, and pitches in x and y. Note that num_x*num_y must equal the number of rows in the spreadsheet file"
label = QLabel.new(dialog)
qt_layout.addWidget(label)
label.text = "[num_x,num_y] = "
num_xy = QLineEdit.new(dialog)
qt_layout.addWidget(num_xy)
num_xy.text = "[1,3]"
label = QLabel.new(dialog)
qt_layout.addWidget(label)
label.text = "[pitch_x,pitch_y] (um) = "
pitch_xy = QLineEdit.new(dialog)
qt_layout.addWidget(pitch_xy)
pitch_xy.text = "[10.0,10.0]"
# Get the list of libs and pcells -- TODO: make this work
#libs = []
#pcells = []
#counter = 0;
#RBA::Library.library_names.each_with_index { |l,i|
# libs.push(l)
# pcell_list = #Get a list of the pcells in library l #lib.layout.pcell_declaration("TEXT")
# pcell_list.each_with_index { |p,j| pcells[counter] = libs[i] + "." + pcell_list[j]; counter += 1 }
#}
pcells = ["Basic.TEXT","MyLib.PCellDrawSquare"] #HACK
# combo box to choose PCell
combo = QComboBox.new(dialog)
pcells.each { |p|
combo.addItem(p)
}
qt_layout.addWidget(combo)
# separation line
label = QLabel.new(dialog)
qt_layout.addWidget(label)
label.text = "\n"
# OK button
buttonOK = QPushButton.new(dialog)
qt_layout.addWidget(buttonOK)
buttonOK.text = " OK "
buttonOK.clicked do
dialog.accept()
end
dialog.exec
# Crunch the inputs above
spreadsheet_file = filepath.text + filename.text
chosen_lib = ""
chosen_pcell = ""
num_pcell_params = -1
if combo.currentText() == "Basic.TEXT" #HACK
chosen_lib = "Basic"
chosen_pcell = "TEXT"
num_pcell_params = 5
elsif combo.current_text() == "MyLib.PCellDrawSquare" #HACK
chosen_lib = "MyLib"
chosen_pcell = "PCellDrawSquare"
num_pcell_params = 2 #HACK. Not sure if 2 is even right
else
raise "unknown combo box selection"
end
n = eval(num_xy.text) # TODO: Should probably include error checking here
num_x = n[0] # Number of rows
num_y = n[1] # Number of columns
pxy = eval(pitch_xy.text) # TODO: Should probably include error checking here
pitch_x = pxy[0] # column pitch, in microns
pitch_y = pxy[1] # row pitch, in microns
# Figure out how many inputs we should have
lib = RBA::Library.library_by_name(chosen_lib)
pcell_decl = lib.layout.pcell_declaration(chosen_pcell)
# Get the params from the spreadsheet
load File.expand_path("./Misc/Misc tools.rb", $PHTOOLS)
vals = MiscTools.get_spreadsheet_values(spreadsheet_file)
header = vals[0][0..vals[0].length] # first (actually zeroth) row
vals = vals[1,vals.length-1]# remove header row
if (num_x * num_y) != vals.length
raise "num_x * num_y is not equal to the number of rows in the spreadsheet. Click 'Cancel' on this dialog box."
end
############## Start the actual code ###############
#lv = RBA::Application.instance.main_window.current_view
# find the lib
lib = RBA::Library.library_by_name(chosen_lib)
lib || raise("Unknown lib " + chosen_lib)
# find the pcell
pcell_decl = lib.layout.pcell_declaration(chosen_pcell)
pcell_decl || raise("Unknown PCell " + chosen_pcell)
# Build the string which we will "eval" later to populate each pcell with its parameters
param_str = []
num_params = header.length
param_counter = 0
vals.each_with_index { |v,i| # Loop thru each row of data from spreadsheet. For example, v might equal ["KLAYOUT RULES", "RBA::LayerInfo::new(1, 0)", 2.5]
param_str[param_counter] = "param = { "
header.each_with_index { |h,j|
v[j] = v[j].to_s
param_str[param_counter] = param_str[param_counter] + "\"" + h + "\" => " + v[j] + ", "
}
temp = param_str[param_counter]
param_str[param_counter] = temp[0..-3] # Remove the trailing ", "
param_str[param_counter] = param_str[param_counter] + " }"
p "Number #{param_counter} is #{param_str[param_counter]}" # Sanity check
param_counter += 1
}
# Get parameters from vals
the_hash = []
param_arr = []
param_str.each_with_index { |p,i|
the_hash[i] = "#{p}"
param_arr.push(eval(the_hash[i]))
}
#p param_arr[0]["text"]
# build a param array using the param hash as a source
pv = []
param_arr.each { |par|
pv.push pcell_decl.get_parameters.collect { |p|
par[p.name] || p.default
#p par[p.name]
}
}
#p pv.length
# create a PCell variant cell
pcell_var = []
pv.each { |p|
pcell_var.push layout.add_pcell_variant(lib, pcell_decl.id, p)
}
#p pcell_var[0].class
# instantiate that cell, with offsets
t = []
pcell_inst = []
pcell_var.each_with_index { |pcv,i|
#p pcv
t.push RBA::Trans::new(0, i * pitch_y / layout.dbu)
pcell_inst.push top.insert(RBA::CellInstArray::new(pcv, t[i]))
}
p 'Done!'
end
end
Comments
Note also that I tried calling the MainWindow's redraw method at the end, but no change -- though, that may or may not be the problem.
Note also that the PCells don't show up in the cell tree on the left, which is strange because I can see the text cells on the layout.
Note lastly that occasionally I get the cryptic error message "Internal error: layEditable.cc.66 ! manager ()->transacting () was not true" which appears to be related to the issue of it being slow, after the code is complete. I haven't been able to figure out when this happens to occur though - it strangely just shows up sometimes after clicking around.
Perhaps these symptoms are somehow related..?
Hi David,
thank you for that impressive example and sorry for the late answer.
The problem is simply that the transaction is not finished by "commit":
When a transaction is pending, redrawing is disabled because the layout object might still be undergoing changes. Plus some operations are disallowed in this state too (that is the meaning of the internal error).
The ensure block will always be executed which makes sure the transaction is committed even in case of an error.
Matthias
That's it, simple, thanks.
I'll post the complete code once I have bugs out and error checking etc.
Thanks,
David