Area of objects

edited December 2009 in General
Hi Matthias,

Is it possible to add a function to calculate the total area of
all selected objects(box/path/polygon)?

And when the object properties window is opened, could the area
be showed as information that it would be easily for users to
get the information?

Thank you so much~

--
chi-hsiang-hung

Comments

  • edited November -1

    Hi,

    currently, that functionality is not provided as a standard feature.

    It is possible however, to implement such a function in ruby. Just copy the code below to a file with extension .rbm (i.e. compute_area.rbm) and copy that file into the installation directory of KLayout. It will add a new entry to the "Tools" menu which computes the area of all objects selected.

    Please note, that the objects are not merged - overlapping parts are counted multiple times.

    I hope, this script is helpful.

    Best regards,

    Matthias

    class MenuAction < RBA::Action
      def initialize( title, shortcut, &action ) 
        self.title = title
        self.shortcut = shortcut
        @action = action
      end
      def triggered 
        @action.call( self ) 
      end
    private
      @action
    end
    
    $compute_total_area = MenuAction.new( "Compute total area of selected shapes", "" ) do 
    
      app = RBA::Application.instance
      mw = app.main_window
    
      lv = mw.current_view
      if lv == nil
        raise "No view selected"
      end
    
      total_area = 0.0
    
      lv.each_object_selected do |obj|
    
        shape = obj.shape
        layout = lv.cellview(obj.cv_index).layout
    
        if shape.is_polygon? || shape.is_box? || shape.is_path?
          polygon = shape.polygon
          a = polygon.area
          m = obj.trans.mag * layout.dbu
          total_area += a * m * m
        end
    
      end
    
      RBA::MessageBox.info("Total area", "Total area of selected objects is #{total_area} square micron", RBA::MessageBox.b_ok)
    
    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", "compute_total_area", $compute_total_area)
    
  • edited November -1
    Hi Matthias,

    I tried this script and it really fits our request,
    it's a helpful code, thank you~ :)

    About the overlap issue, it could be "solved" by
    merge shapes first in Edit => Selection and re-calculate
    the area again after merging all the shapes selected.

    Best Regards,
    --
    chi-hsiang-hung
  • edited November -1
    Hello Matthias,

    To estimate quickly the amount of parasitic wire capacitance, I implemented the area calculation as given below. However, with very thin wires the perimeter is contributing also a lot to the capacitance, but there seems to be no perimeter function call in the Shapes class. Do you plan to implement that (I can try to rite it myself)?


    (Unix diff of the file extNetTracerDialog.cc compared to version 0.22)

    1575a1576,1577
    > double total_area = 0; // use area_type instead?
    > double total_perimeter = 0; // use distance_type instead?
    1649c1651
    <
    ---
    > double dbu_unidir = mp_nets [item_index]->dbu ();
    1723a1726
    > total_area += net_shape->shape.area();
    1762c1765,1773
    <
    ---
    > info.start_element ("h3");
    > info.cdata (tl::translate ("Statistics:"));
    > info.end_element ("h3");
    > info.start_element ("p");
    > info.cdata (tl::sprintf (tl::translate ("Total area: %ld um^2"), total_area));
    > info.start_element ("br");
    > info.end_element ("br");
    > info.cdata (tl::sprintf (tl::translate ("Total perimeter: %ld um"), total_area * dbu_unidir * dbu_unidir ));
    > info.end_element ("p");
  • edited October 2012

    Hi Peter,

    regarding any plans I have, they would probably involve a cold beer rather than a perimeter function :-)

    But I'm glad for any suggestion which is as useful and easy to implement as this one:

    db::Shape::polygon_edge_iterator e = shape.begin_edge();
    db::Coord p = 0;
    while (! e.at_end()) {
      p += e->length ();
    }
    

    I have to tried it myself yet, but the basic idea is to use the polygon edge iterator to collect the total length of all edges. The edge iterator is used in many places so it should work.

    Please note that shape.begin_edge() will assert if the shape is not a polygon. But I assume you are using a merged layer (because otherwise you would count overlapping areas twice) which contains only polygons and not objects like paths or boxes.

    To support the more general case (boxes and paths too) you should consider converting the shape to a db::Polygon object first:

    if (shape.is_polygon ()) {
      db::Polygon poly;
      shape.polygon (poly);
      db::Shape::polygon_edge_iterator e = poly.begin_edge();
      db::Coord p = 0;
      while (! e.at_end()) {
        p += e->length ();
      }
      ...
    }
    

    Best regards,

    Matthias

  • edited November 2012
    To prevent counting the same overlapping edges twice, I tried to collect the (shapes converted to) polygons first. This results in a vector of all polygons in the same layer. Which is again mapped by layer. Now calling the gsi::merge_to_polygon2() function fails, as it is not in the gsi namespace. Or is this function only available in Ruby? Part of the code I used:

    std::map<unsigned int, std::vector <db::Polygon> > polygons_by_layer;
    std::map<unsigned int, db::Shapes>::iterator i = shapes_by_layer.begin();
    while (i != shapes_by_layer.end() )
    {
    unsigned int l = (*i).first;
    db::ShapeProcessor sp;
    polygons_by_layer.insert(std::make_pair(l, gsi::merge_to_polygon2(sp, shapes_by_layer[l], 0, true, true) ) ); // <- this call fails,
    // with "error: ‘merge_to_polygon2’ is not a member of ‘gsi’
    }

    Any idea how to solve this?

    (And how to use the Markdown so I see a nice box around the code?)
  • edited November -1

    Hi Peter,

    "merge_to_polygon" is a method of ShapeProcessor (in fact it's called "merge"). The code should look like this:

    db::ShapeProcessor sp;
    std::vector <db::Polygon> &merged = polygons_by_layer.insert(std::make_pair(l, std::vector <db::Polygon> ())).first->second;
    sp.merge(shapes_by_layer[l], merged, 0, true, true);
    

    I have not tested it myself, but that is the way it should work.

    Best regards,

    Matthias

  • edited November -1
    Hello,

    I have a quick question (at least I hope its quick). I am a bit unfamiliar with this code... Anyhow, I'd like to find the perimeter of a shape. With the code that you have provided for the perimeter, do I need to copy and paste it somewhere in the area code? Should something be removed?

    Thanks! Natalie
  • edited November -1
    I thought maybe I should add my code:

    class MenuAction < RBA::Action
    def initialize( title, shortcut, &action )
    self.title = title
    self.shortcut = shortcut
    @action = action
    end
    def triggered
    @action.call( self )
    end
    private
    @action
    end

    $compute_total_perimeter = MenuAction.new( "Compute perimeter of selected shapes", "" ) do

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

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

    total_perimeter = 0.0

    lv.each_object_selected do |obj|

    shape = obj.shape
    layout = lv.cellview(obj.cv_index).layout

    if shape.is_polygon? || shape.is_box? || shape.is_path?
    db::Shape::polygon_edge_iterator e = shape.begin_edge();
    db::Coord p = 0;
    while (! e.at_end()) {
    p += e->length ();
    }
    end

    end


    RBA::MessageBox.info("Total perimeter", "Total perimeter of selected object is #{total_perimeter} microns", RBA::MessageBox.b_ok)

    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", "compute_total_perimeter", $compute_total_perimeter)


    I get several errors when opening KLayout with this code which are:

    SyntaxError: C:/Program Files (x86)/KLayout (64bit)/compute_perimeter.rbm:34: syntax error, unexpected '{', expecting keyword_do_cond or ';' or '\n'
    C:/Program Files (x86)/KLayout (64bit)/compute_perimeter.rbm:35: syntax error, unexpected tLPAREN_ARG, expecting keyword_do_LAMBDA or tLAMBEG
    p += e->length ();
    ^
    C:/Program Files (x86)/KLayout (64bit)/compute_perimeter.rbm:54: syntax error, unexpected $end, expecting keyword_end
    ...ter", $compute_total_perimeter)
    ... ^

    And if I take out the while loop, KLayout opens without error, but when I click to calculate parameter, I get:

    NameError: undefined local variable or method `db' for main:Object in Action::triggered
    C:/Program Files (x86)/KLayout (64bit)/compute_perimeter.rbm:32:in `block (2 levels) in <top (required)>'
    C:/Program Files (x86)/KLayout (64bit)/compute_perimeter.rbm:26:in `each_object_selected'
    C:/Program Files (x86)/KLayout (64bit)/compute_perimeter.rbm:26:in `block in <top (required)>'
    C:/Program Files (x86)/KLayout (64bit)/compute_perimeter.rbm:8:in `call'
    C:/Program Files (x86)/KLayout (64bit)/compute_perimeter.rbm:8:in `triggered'
  • edited November -1

    Hi Natalie,

    this discussion might be quite confusing. It mixes C++ code with Ruby code.

    The correct Ruby code to compute the perimeter of a polygon is:

    poly = ... # the RBA::Polygon that you want to compute the perimeter of
    perimeter = 0
    poly.each_edge { |e| perimeter += e.length }
    

    Regards,

    Matthias

  • edited November -1
    Hi Matthias,
    I used your "area calculator" code in a previous version of Klayout (32 bits ?) and it worked perfectly. Now I moved to Klayout 64bits with windows7 and I tried again to install the "area calculator" tool, but it does not work.

    When starting klayout, here is the emssage that I receive :

    "SyntaxError: C:/Program Files/KLayout (64bit)/Area_Calculator.rbm:49: syntax error, unexpected $end, expecting ')'
    ...otal_area", $compute_total_area
    ..."
    ^
    Do I do something wrong ? Is there something I should change in the code ?

    Thank you for your help.

    François
  • edited November -1

    Hi Francois,

    I think there is a closing round bracket missing at the end of your text (copy/past issue?). I changed the Ruby version - maybe the older one was less picky. Could you add the bracket and try again?

    Matthias

  • edited November -1
    Hi Matthias,
    Thanks a lot for your response. It now works very well !
    François
  • edited February 2014

    Hi Francois,

    I'd like to mention there is an alternative method now inside the DRC engine.

    The following DRC script prints the area of layer 16, datatype 0:

    puts input(16, 0).area
    

    That method is typically faster than the Ruby code. It will also merge the shapes and not count overlapping regions.

    If performance is an issue, you can use tiling to reduce memory requirements and employ multiple CPU cores to compute the area, i.e.

    threads(4)   # 4 cores
    tiles(1000)  # 1000x1000 um tiles
    puts input(16, 0).area
    

    Matthias

  • edited February 2014

    An application for this area calculation is a density check within the DRC :

    ### the density check is run only if this selection (at the top of the DRC setup) is true (false if not) :
    DENSITY = true
    
    METAL1 = input(16,0)
    
    ### layout size :
    CHIP = extent.sized(0.0) 
    
    if DENSITY
        if (METAL1.area / CHIP.area < 0.2)
            CHIP.output("M1_density", "Metal1 density < 20% :  #{(100 * METAL1.area / CHIP.area).to_s}%")
        end
    end
    

    Regards,

    Laurent

  • edited November -1

    Hi Laurent,

    thank you for pointing that out.

    It is even possible to extend the DRC language with a local density check:

    # Adds a density check method
    #
    # Synopsis:
    #   out = polygon_layer
    #   dens_check(out, input, tile_size, num_threads, min_density, max_density)
    #
    # Parameters:
    #   input       - The input layer (a polygon layer)
    #   tile_size   - The tile dimension in DBU (use x.um to convert micron to DBU)
    #   num_threads - Number of CPU's to use
    #   min_density - Minimum allowed density (0..1)
    #   max_density - Maximum allowed density (0..1)
    #
    # The function will perform a density check and write the results to the output
    # layer. The tiles which do not conform to the density criterion will receive a
    # marker box equal to the tile size.
    
    def dens_check(output, input, tile_size, threads, min, max)
    
      tp = RBA::TilingProcessor::new
      tp.tile_size(tile_size, tile_size)
    
      tp.output("res", output.data)
      tp.input("input", input.data)
      tp.var("vmin", min)
      tp.var("vmax", max)
      tp.threads = threads
      tp.queue("_tile && (var d = to_f(input.area(_tile.bbox)) / to_f(_tile.bbox.area); (d < vmin || d > vmax) && _output(res, _tile.bbox))")
      tp.execute("Density check")
    
    end
    
    # -- this is the actual DRC script --
    
    inp = input(1, 0)
    dens = polygon_layer
    # that is one use case. It checks between 0 and 5% on 1mm tiles and with two CPU's:
    dens_check(dens, inp, 1000.um, 2, 0.0, 0.05)
    dens.output(1000, 0)
    

    Regards,

    Matthias

  • Hi Matthias,

    Thank you in advance for any advise. I was trying out the local density code and wanted to ask the following as I have not managed to figure it out on my own.

    1) Is it possible to implement a step to the tiling ? That means there will be a 50% overlap when tiling through the extent when considering the local density.
    2) Is it possible to output the density of the tiling window that fails ? When I tried I seem to be getting the density of the entire layout instead of the tiling window.
    3) Is it possible to tile through the extent of all the layers in the layout instead of just the extent of the input layer ?

    Thank you in advance for your help. Not sure if I have misunderstood the intent of the code above.

    Best regards,
    Lester

  • Hi Matthias,

    Sorry it's me again. :blush:

    Currently we have two methods to count the area of selected objects:

    [Method1]
    Count the area of every selected polygon would over-count the overlap area
    
    if shape.is_polygon? || shape.is_box? || shape.is_path?
       polygon = shape.polygon
       a = polygon.area
       m = obj.trans.mag * layout.dbu
       total_area += a * m * m
    end
    

    =

    [Method2]
    Insert every selected polygon to a region would not count the overlap area,
    however it would miss the area if the selected polygon are in an array instance,
    a 4x4 array instance would count 1 time only.
    
    Another problem is the performance, if the selected objects are more than 1~2K,
    the counting speed would become more and more slow
    
    if shape.is_polygon? || shape.is_box? || shape.is_path?
      region.insert(shape.polygon)
      a = region.area
      m = obj.trans.mag * layout.dbu
      total_area = a * m * m
    end
    

    Is it possible to improve the code that we could get the correct area like "merge shapes" + method1 ?

    Really appreciate your effort~

    Best Regards,

    chhung

  • Hi @phlion,

    welcome back :)

    The solution is simple: first all the shape to the region and then compute the area:

    region = RBA::Region::new
    
    # for each shape ...
    
       region.insert(obj.trans * shape.polygon) # insert the transformed(!) shape
    
    # end
    
    area = region.area * (layout.dbu ** 2)
    

    You code above is slow since on every iteration it will add one more shape to the region making it bigger and bigger and wasting more and more time in the merged-region are computation.

    Matthias

  • Hi @Matthias,

    The code "region.insert(obj.trans * shape.polygon)" seems not work on v0.24.x with the following error message

    Unexpected object type (expected argument of class CplxTrans, got RBA::Polygon) in CplxTrans::* in Action::triggered
    

    however works like a charm after v0.25.x :smiley:

    Thanks for your great help~ XDDD

    Best Regards,

    chhung

  • Oh yes ... a lot happened since 0.24 was released six years ago :)

Sign In or Register to comment.