# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # A feasibility study for a cross section generation using # boolean operations. See "cmos.xs" for a brief description of the # commands available and some examples. # TODO: use a much smaller dbu for the simulation to have a really small delta # the paths used for generating the masks are somewhat too thick # TODO: the left and right areas are not treated correctly # Problems: empty polyon arrays produce errors with boolean_to_polygon because # RBA does not recognize the empty array as an array of polygons and then there is # an ambiguity between the edge-input and polygon input variants # Thus this extension which checks for empty input and performs some default operation: class RBA::EdgeProcessor def safe_boolean_to_polygon(pa, pb, mode, rh, mc) if pa.length > 0 && pb.length > 0 return self.boolean_to_polygon(pa, pb, mode, rh, mc) elsif mode == RBA::EdgeProcessor::mode_and return [] elsif mode == RBA::EdgeProcessor::mode_or if pa.length > 0 return pa else return pb end elsif mode == RBA::EdgeProcessor::mode_xor if pa.length > 0 return pa else return pb end elsif mode == RBA::EdgeProcessor::mode_anotb return pa elsif mode == RBA::EdgeProcessor::mode_bnota return pb else return [] end end end def string_to_layerinfo(layer_spec) # convert the layer specification into a LayerInfo structure # format: "l", "l/d", "n(l/d)" or "n". 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) else ls = RBA::LayerInfo.new(layer_spec) end return ls end class LayoutData def initialize(polygons, xs) @polygons = polygons @ep = RBA::EdgeProcessor.new @xs = xs end def load(layout, cell, box, layer_spec) ls = string_to_layerinfo(layer_spec) # look up the layer layer_index = nil layout.layer_indices.each do |li| if (layout.get_info(li).is_equivalent?(ls)) layer_index = li end end # collect polygons from the specified layer if (layer_index) shape_iter = layout.begin_shapes_touching(cell, layer_index, box) while !shape_iter.at_end shape = shape_iter.shape if shape.is_polygon? || shape.is_path? || shape.is_box? @polygons.push(shape.polygon.transformed_cplx(shape_iter.itrans)) end shape_iter.next end end end def data @polygons end def data=(polygons) @polygons = polygons end def sized(*args) if args.length == 1 LayoutData.new(@ep.size_to_polygon(@polygons, (args[0] / @xs.dbu + 0.5).floor.to_i, 2, true, true), @xs) elsif args.length == 2 LayoutData.new(@ep.size_to_polygon(@polygons, (args[0] / @xs.dbu + 0.5).floor.to_i, (args[1] / @xs.dbu + 0.5).floor.to_i, 2, true, true), @xs) end end def inverted LayoutData.new(@ep.safe_boolean_to_polygon(@polygons, [RBA::Polygon.new(@xs.background)], RBA::EdgeProcessor::mode_xor, true, true), @xs) end def or(other) raise "'or': argument is not a layer" unless other.kind_of?(LayoutData) LayoutData.new(@ep.safe_boolean_to_polygon(@polygons, other.data, RBA::EdgeProcessor::mode_or, true, true), @xs) end def orp(other) LayoutData.new(@ep.safe_boolean_to_polygon(@polygons, other, RBA::EdgeProcessor::mode_or, true, true), @xs) end def and(other) raise "'and': argument is not a layer" unless other.kind_of?(LayoutData) LayoutData.new(@ep.safe_boolean_to_polygon(@polygons, other.data, RBA::EdgeProcessor::mode_and, true, true), @xs) end def andp(other) LayoutData.new(@ep.safe_boolean_to_polygon(@polygons, other, RBA::EdgeProcessor::mode_and, true, true), @xs) end def xor(other) raise "'xor': argument is not a layer" unless other.kind_of?(LayoutData) LayoutData.new(@ep.safe_boolean_to_polygon(@polygons, other.data, RBA::EdgeProcessor::mode_xor, true, true), @xs) end def xorp(other) LayoutData.new(@ep.safe_boolean_to_polygon(@polygons, other, RBA::EdgeProcessor::mode_xor, true, true), @xs) end def not(other) raise "'not': argument is not a layer" unless other.kind_of?(LayoutData) LayoutData.new(@ep.safe_boolean_to_polygon(@polygons, other.data, RBA::EdgeProcessor::mode_anotb, true, true), @xs) end def notp(other) LayoutData.new(@ep.safe_boolean_to_polygon(@polygons, other, RBA::EdgeProcessor::mode_anotb, true, true), @xs) end def add(other) raise "'add': argument is not a layer" unless other.kind_of?(LayoutData) @polygons = @ep.safe_boolean_to_polygon(@polygons, other.data, RBA::EdgeProcessor::mode_or, true, true) end def addp(other) @polygons = @ep.safe_boolean_to_polygon(@polygons, other, RBA::EdgeProcessor::mode_or, true, true) end def sub(other) raise "'sub': argument is not a layer" unless other.kind_of?(LayoutData) @polygons = @ep.safe_boolean_to_polygon(@polygons, other.data, RBA::EdgeProcessor::mode_anotb, true, true) end def subp(other) @polygons = @ep.safe_boolean_to_polygon(@polygons, other, RBA::EdgeProcessor::mode_anotb, true, true) end def mask(other) raise "'mask': argument is not a layer" unless other.kind_of?(LayoutData) @polygons = @ep.safe_boolean_to_polygon(@polygons, other.data, RBA::EdgeProcessor::mode_and, true, true) end def maskp(other) @polygons = @ep.safe_boolean_to_polygon(@polygons, other, RBA::EdgeProcessor::mode_and, true, true) end def transform(t) @polygons = @polygons.collect { |p| p.transformed(t) } end def close_gaps sz = 1 d = @polygons d = @ep.size_to_polygon(d, 0, sz, 2, true, true) d = @ep.size_to_polygon(d, 0, -sz, 2, true, true) d = @ep.size_to_polygon(d, sz, 0, 2, true, true) d = @ep.size_to_polygon(d, -sz, 0, 2, true, true) @polygons = d end def remove_slivers sz = 1 d = @polygons d = @ep.size_to_polygon(d, 0, -sz, 2, true, true) d = @ep.size_to_polygon(d, 0, sz, 2, true, true) d = @ep.size_to_polygon(d, -sz, 0, 2, true, true) d = @ep.size_to_polygon(d, sz, 0, 2, true, true) @polygons = d end end class MaterialData < LayoutData # 'delta' is the intrinsic height (required for mask data because there cannot be # an infinitly small mask layer (in database units) def initialize(polygons, xs, delta) super(polygons, xs) @delta = delta end def grow(*args) # parse the arguments (xy, z, into, through, on, taper, bias, mode) = parse_grow_etch_args(args, :grow) # produce the geometry of the new material d = produce_geom(xy, z, into, through, on, taper, bias, mode, :grow) # prepare the result res = MaterialData.new(d, @xs, 0) # consume material if into into.each do |i| i.sub(res) i.remove_slivers end else @xs.air.sub(res) @xs.air.remove_slivers end return res end def etch(*args) # parse the arguments (xy, z, into, through, on, taper, bias, mode) = parse_grow_etch_args(args, :etch) if !into raise "'etch' method: requires an 'into' specification" end # prepare the result d = produce_geom(xy, z, into, through, on, taper, bias, mode, :etch) # produce the geometry of the etched material res = MaterialData.new(d, @xs, 0) # consume material and add to air if into into.each do |i| i.sub(res) i.remove_slivers end end @xs.air.add(res) @xs.air.close_gaps end def parse_grow_etch_args(args, method) xy = nil z = nil into = nil through = nil on = nil taper = nil bias = nil mode = :square args.each do |a| if a.kind_of?(Hash) a.each_pair do |k,v| if k == :into if !v.kind_of?(Array) into = [v] else into = v end into.each do |i| if !i.kind_of?(LayoutData) # should be MaterialData @@@ raise "'#{method}' method: 'into' expects a material parameter or an array of such" end end elsif k == :on if !v.kind_of?(Array) on = [v] else on = v end on.each do |i| if !i.kind_of?(LayoutData) # should be MaterialData @@@ raise "'#{method}' method: 'on' expects a material parameter or an array of such" end end elsif k == :through if !v.kind_of?(Array) through = [v] else through = v end through.each do |i| if !i.kind_of?(LayoutData) # should be MaterialData @@@ raise "'#{method}' method: 'through' expects a material parameter or an array of such" end end elsif k == :mode mode = v if v != :round && v != :square && v != :octagon raise "'#{method}' method: 'mode' expects ':round', ':square' or ':octagon'" end elsif k == :taper taper = v.to_f elsif k == :bias bias = v.to_f else raise "'#{method}' method: undefined parameter key '#{k}'" end end elsif !z z = a.to_f elsif !xy xy = a.to_f else raise "Too many arguments for '#{method}' method" end end if !z raise "Too few arguments for '#{method}' method" end if on && (through || into) raise "'on' option cannot be combined with 'into' or 'through' option" end [xy || 0.0, z, into, through, on, taper, bias, mode] end def produce_geom(xy, z, into, through, on, taper, bias, mode, method) prebias = bias || 0.0 if xy < 0.0 xy = -xy prebias += xy end if taper prebias += z * Math.tan(Math::PI / 180.0 * taper) - xy xy += prebias end # determine the "into" material by joining the data of all "into" specs # or taking "air" if required. if into if into.length == 1 into_data = into[0].data else into_data = [] into.each do |i| if into_data.length == 0 into_data = i.data else into_data = @ep.safe_boolean_to_polygon(i.data, into_data, RBA::EdgeProcessor::mode_or, true, true) end end end else into_data = @xs.air.data end # determine the "through" material by joining the data of all "through" specs if through if through.length == 1 through_data = through[0].data else through_data = [] through.each do |i| if through_data.length == 0 through_data = i.data else through_data = @ep.safe_boolean_to_polygon(i.data, through_data, RBA::EdgeProcessor::mode_or, true, true) end end end end # determine the "on" material by joining the data of all "on" specs if on if on.length == 1 on_data = on[0].data else on_data = [] on.each do |i| if on_data.length == 0 on_data = i.data else on_data = @ep.safe_boolean_to_polygon(i.data, on_data, RBA::EdgeProcessor::mode_or, true, true) end end end end offset = @delta d = @polygons # in the "into" case determine the interface region between self and into if into || through || on # apply an artificial sizing to create an overlap before if offset == 0 offset = @xs.delta_dbu / 2 d = @ep.size_to_polygon(d, 0, offset, 2, true, true) end if on d = @ep.safe_boolean_to_polygon(d, on_data, RBA::EdgeProcessor::mode_and, true, true) elsif through d = @ep.safe_boolean_to_polygon(d, through_data, RBA::EdgeProcessor::mode_and, true, true) else d = @ep.safe_boolean_to_polygon(d, into_data, RBA::EdgeProcessor::mode_and, true, true) end end pi = (prebias / @xs.dbu + 0.5).floor.to_i if pi < 0 d = @ep.size_to_polygon(d, -pi, 0, 2, true, true) elsif pi > 0 # apply a positive prebias by filtering with a sized box dd = [] d.each do |p| box = p.bbox if box.width > 2 * pi box = RBA::Box.new(box.left + pi, box.bottom, box.right - pi, box.top) @ep.boolean_to_polygon([ RBA::Polygon.new(box) ], [p], RBA::EdgeProcessor::mode_and, true, true).each { |pp| dd.push(pp) } end end d = dd end xyi = (xy / @xs.dbu + 0.5).floor.to_i zi = (z / @xs.dbu + 0.5).floor.to_i - offset if taper d = @ep.size_to_polygon(d, xyi, zi, 0, true, true) elsif xyi <= 0 d = @ep.size_to_polygon(d, 0, zi, 2, true, true) elsif mode == :round # emulate "rounding" of corners by performing soft-edged sizes d = @ep.size_to_polygon(d, xyi / 3, zi / 3, 1, true, true) d = @ep.size_to_polygon(d, xyi / 3, zi / 3, 0, true, true) d = @ep.size_to_polygon(d, xyi - 2 * (xyi / 3), zi - 2 * (zi / 3), 0, true, true) elsif mode == :square d = @ep.size_to_polygon(d, xyi, zi, 2, true, true) elsif mode == :octagon d = @ep.size_to_polygon(d, xyi, zi, 1, true, true) end if through d = @ep.safe_boolean_to_polygon(d, through_data, RBA::EdgeProcessor::mode_anotb, true, true) end d = @ep.safe_boolean_to_polygon(d, into_data, RBA::EdgeProcessor::mode_and, true, true) if nil # remove small features # Hint: this is done separately in x and y direction since that is more robust against # snapping distortions d = @ep.size_to_polygon(d, 0, @xs.delta_dbu / 2, 2, true, true) d = @ep.size_to_polygon(d, 0, -@xs.delta_dbu, 2, true, true) d = @ep.size_to_polygon(d, 0, @xs.delta_dbu / 2, 2, true, true) d = @ep.size_to_polygon(d, @xs.delta_dbu / 2, 0, 2, true, true) d = @ep.size_to_polygon(d, -@xs.delta_dbu, 0, 2, true, true) d = @ep.size_to_polygon(d, @xs.delta_dbu / 2, 0, 2, true, true) end return d end end # The main class that creates a cross-section file class XSectionGenerator # Constructor def initialize(file_path) # TODO: adjust this path: @file_path = file_path @lyp_file = nil @ep = RBA::EdgeProcessor.new @flipped = false end def layer(layer_spec) ld = LayoutData.new([], self) ld.load(@layout, @cell, @line_dbu.bbox.enlarge(RBA::Point.new(@extend, @extend)), layer_spec) return ld end def output(layer_spec, layer_data) raise("'output' method: second parameter must be a geometry object") unless layer_data.kind_of?(LayoutData) ls = string_to_layerinfo(layer_spec) li = @target_layout.insert_layer(ls) shapes = @target_layout.cell(@target_cell).shapes(li) # confine the shapes to the region of interest @ep.boolean_to_polygon([ RBA::Polygon.new(@roi) ], layer_data.data, RBA::EdgeProcessor::mode_and, true, true).each do |polygon| shapes.insert(polygon) end end def flip @air, @air_below = @air_below, @air @flipped = !@flipped end def all return xpoints_to_mask([[ -@extend, 1 ], [ @line_dbu.length + @extend, -1 ]]) end def diffuse(*args) all.grow(*args) end def deposit(*args) all.grow(*args) end def grow(*args) all.grow(*args) end def etch(*args) all.etch(*args) end def mask(layer_data) crossing_points = [] layer_data.data.each do |polygon| polygon.each_edge do |edge_dbu| if @line_dbu.crossed_by?(edge_dbu) && (@line_dbu.side_of(edge_dbu.p1) > 0 || @line_dbu.side_of(edge_dbu.p2) > 0) # compute the crossing point of "edge" and "line" in database units # confine the point to the length of the line z = (edge_dbu.dx.to_f * (edge_dbu.p1.y.to_f - @line_dbu.p1.y.to_f) - edge_dbu.dy.to_f * (edge_dbu.p1.x.to_f - @line_dbu.p1.x.to_f)) / (edge_dbu.dx.to_f * (@line_dbu.p2.y.to_f - @line_dbu.p1.y.to_f) - edge_dbu.dy.to_f * (@line_dbu.p2.x.to_f - @line_dbu.p1.x.to_f)) z = (z * @line_dbu.length + 0.5).floor if z < -@extend z = -@extend elsif z > @line_dbu.length + @extend z = @line_dbu.length + @extend end s = ((edge_dbu.dy * @line_dbu.dx - edge_dbu.dx * @line_dbu.dy) <=> 0) # store that along with the orientation of the edge (+1: "enter geometry", -1: "leave geometry") crossing_points.push([ z, s ]) end end end # compress the crossing points by collecting all of those which cut the measure line at the # same position compressed_crossing_points = [] last_z = nil sum_s = 0 crossing_points.sort.each do |i| z = i[0] s = i[1] if z == last_z sum_s += s else if sum_s != 0 compressed_crossing_points.push([ last_z, sum_s ]) end last_z = z sum_s = s end end if last_z && sum_s != 0 compressed_crossing_points.push([ last_z, sum_s ]) end # create the final intervals by selecting those crossing points which # denote an entry or leave point into or out of drawn geometry. This # basically does a merge of all drawn shapes. return xpoints_to_mask(compressed_crossing_points) end def planarize(*args) downto = nil less = nil to = nil into = nil args.each do |a| if a.kind_of?(Hash) a.each_pair do |k,v| if k == :downto if !v.kind_of?(Array) downto = [v] else downto = v end downto.each do |i| if !i.kind_of?(LayoutData) # should be MaterialData @@@ raise "'#{method}' method: 'into' expects a material parameter or an array of such" end end elsif k == :into if !v.kind_of?(Array) into = [v] else into = v end into.each do |i| if !i.kind_of?(LayoutData) # should be MaterialData @@@ raise "'#{method}' method: 'into' expects a material parameter or an array of such" end end elsif k == :less less = (0.5 + v.to_f / self.dbu).floor.to_i elsif k == :to to = (0.5 + v.to_f / self.dbu).floor.to_i end end else raise "'planarize' expects named arguments 'less', 'downto', 'into' or 'to'" end end if !into raise "'planarize' requires an 'into' argument" end if downto downto_data = nil if downto.length == 1 downto_data = downto[0].data else downto.each do |i| if downto_data.length == 0 downto_data = i.data else downto_data = @ep.safe_boolean_to_polygon(i.data, downto_data, RBA::EdgeProcessor::mode_or, true, true) end end end # determine upper bound of material if downto_data downto_data.each do |p| yt = p.bbox.top yb = p.bbox.bottom to ||= yt if !@flipped to = [ to, yt, yb ].max else to = [ to, yt, yb ].min end end end elsif into && !to # determine upper bound of our material into.each do |i| i.data.each do |p| yt = p.bbox.top yb = p.bbox.bottom to ||= yt if !@flipped to = [ to, yt, yb ].max else to = [ to, yt, yb ].min end end end end if to less ||= 0 if @flipped into.each do |i| i.maskp([RBA::Polygon.new(RBA::Box.new(-@extend, to + less, @line_dbu.length + @extend, self.height_dbu))]) end air.addp([RBA::Polygon.new(RBA::Box.new(-@extend, -self.depth_dbu - self.below_dbu, @line_dbu.length + @extend, to + less))]) else into.each do |i| i.maskp([RBA::Polygon.new(RBA::Box.new(-@extend, -self.depth_dbu - self.below_dbu, @line_dbu.length + @extend, to - less))]) end air.addp([RBA::Polygon.new(RBA::Box.new(-@extend, to - less, @line_dbu.length + @extend, self.height_dbu))]) end end end def xpoints_to_mask(iv) s = 0 last_s = 0 p1 = 0 p2 = 0 mask_polygons = [] iv.each do |i| z = i[0] s += i[1] if s > 0 && last_s <= 0 p1 = z elsif s <= 0 && last_s > 0 p2 = z mask_polygons.push(RBA::Polygon.new(RBA::Box.new(p1, -@depth - @below, p2, @height))) end last_s = s end air = @air.data air_sized = @ep.size_to_polygon(air, @delta, @delta, 2, true, true) air_border = @ep.safe_boolean_to_polygon(air_sized, air, RBA::EdgeProcessor::mode_anotb, true, true) mask_data = @ep.safe_boolean_to_polygon(air_border, mask_polygons, RBA::EdgeProcessor::mode_and, true, true) return MaterialData.new(mask_data, self, @delta) end def delta(x) @delta = (x / @dbu + 0.5).floor.to_i end def delta_dbu @delta end def height(x) @height = (x / @dbu + 0.5).floor.to_i update_basic_regions end def height_dbu @height end def depth(x) @depth = (x / @dbu + 0.5).floor.to_i update_basic_regions end def depth_dbu @depth end def below(x) @below = (x / @dbu + 0.5).floor.to_i update_basic_regions end def below_dbu @below end def extend(x) @extend = (x / @dbu + 0.5).floor.to_i update_basic_regions end def extend_dbu @extend end def width_dbu @line_dbu.length end def background x1 = @line_dbu.p1.x y1 = @line_dbu.p1.y x2 = @line_dbu.p2.x y2 = @line_dbu.p2.y if x2 < x1 (x1, x2) = [x2, x1] end if y2 < y1 (y1, y2) = [y2, y1] end x1 -= @extend y1 -= @extend x2 += @extend y2 += @extend RBA::Box.new(RBA::Point.new(x1 - @delta * 5, y1 - @delta * 5), RBA::Point.new(x2 + @delta * 5, y2 + @delta * 5)) end def air @air end def bulk MaterialData.new(@bulk.data, self, 0) end def dbu @dbu end def layers_file(lyp_file) @lyp_file = lyp_file end # The basic generation method def run if !setup return end update_basic_regions text = nil File.open(@file_path) do |file| text = file.read end if !text RBA::MessageBox::critical("Error", "Error reading file #{@file_path}", RBA::MessageBox::b_ok) return end begin eval(text) # Without this code, stack traces are shown (works better for development ..) # rescue # RBA::MessageBox::critical("Error", $!, RBA::MessageBox::b_ok) # return end RBA::Application.instance.main_window.cm_lv_add_missing #@@@ if @lyp_file @target_view.load_layer_props(@lyp_file) end @target_view.zoom_fit end private def update_basic_regions @area = RBA::Box.new(-@extend, -@depth - @below, @line_dbu.length + @extend, @height) @air = MaterialData.new([RBA::Polygon.new(RBA::Box.new(-@extend, 0, @line_dbu.length + @extend, @height))], self, 0) @air_below = MaterialData.new([RBA::Polygon.new(RBA::Box.new(-@extend, -@depth - @below, @line_dbu.length + @extend, -@depth))], self, 0) @bulk = MaterialData.new([RBA::Polygon.new(RBA::Box.new(-@extend, -@depth, @line_dbu.length + @extend, 0))], self, 0) @roi = RBA::Box.new(0, -@depth - @below, @line_dbu.length, @height) end def setup # locate the layout and the (single) ruler app = RBA::Application.instance view = app.main_window.current_view if !view RBA::MessageBox::critical("Error", "No view open for creating the cross section from", RBA::MessageBox::b_ok) return false end ruler = nil nrulers = 0 view.each_annotation do |a| # Use only rulers with "plain line" style # @@@ if a.style == RBA::Annotation::style_line ruler = a nrulers += 1 # @@@ end end if nrulers == 0 RBA::MessageBox::critical("Error", "No ruler present for the cross section line", RBA::MessageBox::b_ok) return end if nrulers > 1 RBA::MessageBox::critical("Error", "More than one ruler present for the cross section line (with 'plain line' style)", RBA::MessageBox::b_ok) return false end cv = view.cellview(view.active_cellview_index) if ! cv.is_valid? RBA::MessageBox::critical("Error", "The selected layout is not valid", RBA::MessageBox::b_ok) return false end @cv = cv @layout = cv.layout @dbu = @layout.dbu @cell = cv.cell_index # get the start and end points in database units and micron p1_dbu = RBA::Point::from_dpoint(ruler.p1 * (1.0 / @dbu)) p2_dbu = RBA::Point::from_dpoint(ruler.p2 * (1.0 / @dbu)) @line_dbu = RBA::Edge.new(p1_dbu, p2_dbu) # create a new layout for the output cv = app.main_window.create_layout(1) cell = cv.layout.add_cell("XSECTION") @target_view = app.main_window.current_view @target_view.select_cell(cell, 0) @target_layout = cv.layout @target_layout.dbu = @dbu @target_cell = cell # initialize height and depth @extend = (2.0 / @dbu + 0.5).floor.to_i @delta = 10 @height = (2.0 / @dbu + 0.5).floor.to_i @depth = (2.0 / @dbu + 0.5).floor.to_i @below = (2.0 / @dbu + 0.5).floor.to_i return true end end # ---------------------------------------------------------------- # A special action to implement the cross section MRU menu item class XSectionMRUAction < RBA::Action def initialize(&action) @action = action end def triggered @action.call(self) end def script=(s) @script = s self.visible = (s != nil) if s self.title = File.basename(s) end end def script @script end private @action @script end # ---------------------------------------------------------------- # The cross section script environment class XSectiongScriptEnvironment def initialize app = RBA::Application.instance mw = app.main_window @action = RBA::Action.new @action.title = "XSection Script" @action.on_triggered do view = RBA::Application.instance.main_window.current_view if !view raise "No view open for running the xsection script on" end fn = RBA::FileDialog::get_open_file_name("Select Script", "", "XSection Scripts (*.xs);;All Files (*)"); if fn.has_value? file_path = run_script(fn.value) make_mru(fn.value) end end menu = mw.menu menu.insert_separator("tools_menu.end", "xsection_script_group") menu.insert_menu("tools_menu.end", "xsection_script_submenu", "XSection Scripts") menu.insert_item("tools_menu.xsection_script_submenu.end", "xsection_script_load", @action) menu.insert_separator("tools_menu.xsection_script_submenu.end.end", "xsection_script_mru_group") @mru_actions = [] (1..4).each do |i| a = XSectionMRUAction.new do |action| run_script(action.script) make_mru(action.script) end @mru_actions.push(a) menu.insert_item("tools_menu.xsection_script_submenu.end", "xsection_script_mru#{i}", a) a.script = nil end # try to save the MRU list to $HOME/.klayout-processing-mru i = 0 home = ENV["HOME"] if home fn = File.join(home, ".klayout-xsection") begin File.open(fn, "r") do |file| file.readlines.each do |line| if line =~ /(.*)<\/mru>/ if i < @mru_actions.size @mru_actions[i].script = $1 end i += 1 end end end rescue end end end def run_script(fn) view = RBA::Application.instance.main_window.current_view if !view raise "No view open for running the boolean script on" end cv = view.cellview(view.active_cellview_index) begin XSectionGenerator.new(fn).run rescue RBA::MessageBox.critical("Script failed", $!.to_s, RBA::MessageBox.b_ok) end end def make_mru(script) scripts = [script] @mru_actions.each do |a| if a.script != script scripts.push(a.script) end end while scripts.size < @mru_actions.size scripts.push(nil) end (0..(@mru_actions.size-1)).each do |i| @mru_actions[i].script = scripts[i] end # try to save the MRU list to $HOME/.klayout-xsection #home = ENV["HOME"] #if home # fn = File.join(home, ".klayout-xsection") # File.open(fn, "w") do |file| # file.puts(""); # @mru_actions.each do |a| # if a.script # file.puts("#{a.script}") # end # end # file.puts(""); # end # #end end end $xsection_processing_environment = XSectiongScriptEnvironment.new