How to Create Logarithmic Spirals

edited November 2015 in General
Hi,

I'm a little lost right now in trying to create logarithmic spirals with Klayout. Could you suggest a method to create such complex structures?

Sorry if this question has already been addressed, I haven't been able to find much information regarding logarithmic spirals in the forum.

Thanks in advance for your help!

Best.
Nick

Comments

  • edited November 2015

    Nick,

    Here's a script that draws a circle (path). Modify these lines

    x = radius * Math.cos(tt)
    y = radius * Math.sin(tt)
    

    to instead have the parametric equation for log spiral.

    I've put this in E4K.

    It looks like I spent a huge amount of time writing long comments but actually most of those comments were already there, from another example in E4K that I duplicated to make this. Anyhoo, here you go.

    ## Draw a circular path.lym
    ## By davidnhutch
    ## 
    ## Draws a circular path
    
    # Wrapping it in 'module ... end' helps avoid namespace clashes but isn't 
    # necessary.
    module DrawCircPath
    
      # Include the RBA module, then get a handle on the application.
      # "RBA" is the module provided by KLayout. "Application" is the main 
      # controller class (a singleton) that refers to the application as a whole.
      # You don't have to include RBA, but then you have to preface any classes 
      # (such as Application) with RBA:: (for example, instead of 
      # Application.instance you'd type RBA::Application.instance).
      include RBA
      app = Application.instance
    
      # Get a handle on the main window and the current view
      mw = app.main_window
      lv = mw.current_view
    
      # If lv is nil (ruby-talk for 'not defined') then we raise an error. 
      #
      # We could have said 
      #   if lv.nil? 
      # instead of 
      #   if lv == nil
      #
      # A more 'slick' and more ruby-ish way of doing the below is 
      #   lv || raise("No view selected")
      # Why does that work? 
      # || means 'or'. It evaluates the left side of the 'or' statement, and if 
      # it's true (non-nil) then it doesn't bother evaluating the right side, so 
      # the error is never raised. 
      # However if the left side evaluates to nil then it is forced to evaluate the 
      # right side, so it raises the error. Brackets are needed around the raise 
      # argument, due to the evaluation priority of the ruby operators.
      if lv == nil
        raise "No view selected. Choose File > New layout, and run this again."
      end
    
      # Get a handle on the layout
      ly = lv.active_cellview.layout
    
      # Get a handle on the conversion factor between displayed coordinates and 
      # actual database coordinates.
      # The displayed coordinates are the human-readable and useful ones. e.g. 
      # '12.345 microns'
      # The database however, can only store integers for size and speed reasons. 
      # So the above number would be stored as 12345 (an integer, twelve thousand 
      # three hundred and forty five).
      # So, the conversion factor between 1um and 1nm is 1/1000. So the 'dbu' below 
      # evaluates to 0.001 by default.
      # 
      # (Actually you can define the conversion to be whatever you want, this is 
      # just the default and is the desired conversion in the vast majority of 
      # cases.)
      dbu = ly.dbu
    
      # Get a handle on the currently-shown 'top' cell. To change the top cell, 
      # center click on the cell name in the cell list on the left side of KLayout.
      # We could have gotten the cell reference from its name too. In that case 
      # we'd use:
      # cell = ly.cell("CELL_NAME")
      cell = lv.active_cellview.cell
    
      # Get a handle on the layer you want the path on. LayerInfo objects hold 
      # various information about a layer. In contrast, the layer index is just an 
      # index used internally to KLayout, so is not always as useful as the 
      # LayerInfo object. 
      # But, a method we will use later requires the layer index rather than the 
      # LayerInfo object, so we create layer_index variable to hold that.
      layer_number = 1
      datatype = 0
      layer_info = LayerInfo.new(layer_number,datatype)
      layer_index = ly.layer(layer_info)
    
      # Define the  parameters
      width  = 1.0 # Write everything in microns
      radius = 10.0 
    
      # Define an empty array to hold the path's points
      pts = []
    
      # Create a linear "t" (theta) array from 0 to 2*pi
      num_pts = 10
      t_start = 0.0
      t_end   = 2*Math::PI
      dt = Math::PI / (num_pts - 1)
      t = t_start.step(t_end,dt).to_a # Create the array. There are other ways of doing this too.
    
      # Loop thru each t, and create a Point object holding the computed x,y 
      t.each { |tt|
        x = radius * Math.cos(tt)
        y = radius * Math.sin(tt)
        # Now here's a trick to learn: Whenever creating a new shape (Point, Box, 
        # Polygon), put the coordinates in in database units. 
        # To do that, you divide your value in microns by 'dbu' while you are 
        # inserting it.
        pt = Point.new(x/dbu,y/dbu)
        # Push that to the pts array
        pts << pt
      }
    
      # Push the first point again on to the end of the array so the ends meet
      pts << pts[0]
    
      # Now push the second point on to the end of the array.
      # Why? Try commenting this line out - Although the ends of the path will meet 
      # up, these ends will come in at different angles, so there will be an 
      # undesirable triangular sliver where they meet. This line gets rid of that 
      # sliver.
      pts << pts[1]
    
      # Make Path object. Remember to divide by dbu again
      path = Path.new(pts,width/dbu)
    
      # Insert it in this cell, to the shapes database, on the desired layer
      cell.shapes(layer_index).insert(path)
    
      # If the user didn't have layer 1 defined already (Edit > Layer > New > 
      # layer 1/0) then they won't see anything. By adding missing layers we can 
      # help to show the shape in this case.
      lv.add_missing_layers
    
      # Zoom to show the whole path
      lv.zoom_fit
    
      # Report by printing text to the Macro Editor console. "puts" means put 
      # string. Note that we could also type
      #   p msg
      # as a shortcut. Also there is 
      #   puts msg
      # which has a slightly different behavior.
      print "Inserted a path with coordinates = #{pts}"
    
    end
    
  • edited November -1
    Hi David,

    Thanks for the help! I wasn't aware of E4K until now. I'll let you know how it goes.

    Thanks again!

    Best.
    Nick
Sign In or Register to comment.