Rounding Corners through Ruby

edited April 2013 in Ruby Scripting
Matthias, I have to thank you for this amazing piece of software. You are the man!

I just discovered PCells, and I'm trying to work my way through my first version to speed my workflow. I would like to be able to apply the 'Round Corners' command from the selection edit menu. So here are a few questions that I have, apologies if the answers exist elsewhere, but I didn't see them in the documentation or the forum:

1) Are the commands accessible in the menu all in the Ruby API somewhere, and is there an easy way to figure out how to access them?
2) If not all commands are in the Ruby API, does the corner rounding command happen to be?

and finally, although this is a lower priority to me:

3) It would be beautiful if the round corners command could be applied on a per-corner basis (both through the GUI and through Ruby). In my designs it is nice to have different parts of a single polygon have different corner radii, which seems difficult to achieve as is.

Thanks for any help that Matthias or the community can lend!

Comments

  • edited 11:00AM

    Hallo,

    thank you for that feedback :-)

    Not all commands in the menu are accessible as Ruby functions, because the Ruby API is on a lower level than the menu functions. The Ruby API provides methods as classes from the database level while menu functions usually provide functionality on a higher level. I gradually try to make the high-level functionality available but that adds a lot of complexity to the API.

    For the corner rounding, however there is a Ruby method available in the API (method "round_corners" of the Polygon class, see http://www.klayout.de/doc/code/class_Polygon.html#method45).

    Regarding different rounding radii this method allows to specify two radii for inner and outer corners. In most cases that appears to be sufficient. It is probably possible to extend this to allow specification of a per-corner radius but right now there is no such option available.

    Regards,

    Matthias

  • edited 11:00AM
    That's great. I didn't notice the round_corners method!

    I think that I'll implement a simple function to round arbitrary corners of a polygon, given a list of points defining the polygon, points to round and their respective radii. If it turns out nice, I'll post it here, along with an example of an output file.

    Thanks Matthias!

    -Michael
  • edited 11:00AM
    Hello Matthias and Michael,

    I am also looking to make circles. I was wondering if there is a function yet to create real circles, or if I need to make a Ruby Script to do this or round the corners of a polygon.

    Have any of you been successful with the code?

    Thanks, Natalie
  • edited April 2013

    Hi Natalie,

    there are not circles in GDS, hence there is no support for that kind of shape yet.

    One approximation to a circle is a path with round corners and a single point. I don't know however, how portable that solution is. Such an object will be converted to OASIS circles if you save it to OASIS:

    pts = [ RBA::Point::new(100, 200) ]   # center
    r = 10000   # radius
    p = RBA::Path::new(pts, 2 * r, r, r, true)
    

    Another way is to create polygons with a sufficient number of points which form a circle, i.e. like this:

    n = 200    # number of points
    r = 10000  # radius
    da = 2 * Math::PI / n
    pts = n.times.collect { |i| RBA::Point.new(r * cos(i * da), r * sin(i * da)) }
    poly = RBA::Polygon::new(pts)
    

    Matthias

  • edited 11:00AM

    I went and made a promise to post some code, and then once I wrote it I promptly forgot! It may be superseded by now, but I thought that I'd post it here to wrap up my part in this thread.

    Here is my silly little code which takes a list of points defining a polygon, two lists which in turn define the points you wish to round, and the radii you wish the curves to have, and finally the number of points per 360 degrees of arc.

    Be warned! It's a very amateurish bit of code; the method is simplistic and there is no checking to see if the radii you request result in intersecting curves, leading to awful behavior if the radii is large compared to the distance between unrounded points. It ended up working well enough for me.

    def polygon_round_corners(pts,cornerstoround,radii,ptspercircle)
    
      roundpts = []
    
      if cornerstoround == nil
        return RBA::Polygon::new(pts)
      end
    
      unroundedpts = (pts.length)
    
      for i in 0..unroundedpts
    
        pt = pts[i]
    
        # check to see if this point is to be rounded
        rindex = cornerstoround.index(i)
    
        # if we don't want to round this corner, add this unrounded point to the list, and skip to the next point
        if rindex == nil
          roundpts.push(pt)
          next
        end
    
        radius = 1000*radii[rindex]
    
        #if we do want to round this corner, we need to start by finding the positions of this point, plus the previous and next points
        # first - determine the index of the previous and next points
        if i ==0
          npt = pts[1]
          ppt = pts[unroundedpts-1]
        elsif i == unroundedpts -1
          npt = pts[0]
          ppt = pts[unroundedpts-2]
        else
          npt = pts[i + 1]
          ppt = pts[i - 1]
        end
    
       # Next, I'll find the angle between the two line segments
       theta1 = Math::atan2(npt.y-pt.y,npt.x-pt.x)
       theta2 = Math::atan2(ppt.y-pt.y,ppt.x-pt.x) 
    
        phi = (theta1 - theta2).modulo(2*Math::PI)
        xbar = Math::cos(theta1) + Math::cos(theta2)
        ybar = Math::sin(theta1) + Math::sin(theta2)
        theta = Math::atan2(ybar,xbar)
    
        dc = radius / Math::sin(phi/2)
    
        # and then it is easy to find the coordinates of the center of curvature:
    
        dcx = dc*Math::cos(theta)
        dcy = dc*Math::sin(theta)
    
        cx = pt.x + dcx
        cy = pt.y + dcy
    
        # OK... moving along! We now need to find the starting and ending angles to define the arc.  This should be the angle found above, plus \pi
        theta = theta + Math::PI
        theta1 = theta - (Math::PI-phi)/2
        theta2 = theta + (Math::PI-phi)/2
    
    
        # check to make sure we don't need to switch theta1 and theta2:
    
        dprevlinex = pt.x - ppt.x
        dprevliney = pt.y - ppt.y
    
        dtangentx =  (cx + radius * Math::cos(theta1))-pt.x
        dtangenty = (cy + radius * Math:: sin(theta1))-pt.y
    
        if dprevlinex == 0
          error = dtangentx
        else
          error = (dtangenty - dtangentx*dprevliney/dprevlinex)/(dtangenty.abs + dtangentx.abs)
        end
    
        if error.abs > 1e-4
          temp = theta1
          theta1 = theta2
          theta2 = temp
        end
    
    
        thetarange = (theta1 - theta2)
        numpts  = 2*ptspercircle * (theta1 - theta2).abs / Math::PI
        numpts = [numpts,2].max
        numpts = numpts.round
        dtheta = (-thetarange)/(numpts)
    
    
        #generate a list of the angles used to generate the points:
        angles = (0..numpts).to_a
        angles = angles.collect { |n| n * dtheta }
        angles = angles.collect{ |n| n + theta1 }
    
        # step over the angles, and add the points to our list
    
    
        for angle in angles
           roundpts.push(Point.new(cx + radius * Math::cos(angle), cy + radius * Math::sin(angle)))
        end    
    
      end
    
      # return the polygon composed of all these points!
    
      index = 0
      roundpts = roundpts[0...-1]
    
    
      poly =  RBA::Polygon::new(roundpts)
    
      return poly
    
    end
    
Sign In or Register to comment.