Run .lydrc from .rb?

edited February 2015 in Ruby Scripting

Hi,

I understand I can run .lydrc DRC script either from (1) Macro Editor or (2) from a menu (if configured that way), or (3) from Windows command prompt "klayout -b -r my.drc".

Can I call it from within a ruby script? Could not find any Forum questions to answer that.

Thanks,
David

Comments

  • edited February 2015

    Ah - never mind. I forgot there is a quick and dirty way to achieve this:

    RBA::Application.instance.main_window.menu.action("tools_menu.drc.macro_in_menu_x").trigger
    

    assuming you set up the .lydrc file to show in the usual place, and where 'x' is some number, given under File > Settings > Application > Key Bindings.

  • edited November -1

    Hi David,

    I have not tested that, but it should be possible this way:

    engine = DRC::DRCEngine::new
    fn = "mydrc.lydrc"
    File.open(fn, "r") do |file|
      engine.instance_eval(file.read, fn)
    end
    

    The first argument is the content of the file, the second is the file path. The path is used to show the file name in error messages only.

    Matthias

  • edited February 2015

    Thanks! Works great (with the modifications below).

    For future readers:

    This gives an error due to instance_eval wanting the raw DRC string rather than the xml .lydrc file. The error is

    C:\filename.lydrc: syntax error, unexpected '<'
    <?xml version="1.0" encoding="utf-8"?>
    

    So one could write some code to extract the heart of .lydrc file as a string.

    However for my purposes it is actually fine to do the following:

    drc_string = %Q[
    # Paste your entire drc script in this multi-line quote
    ]
    
    engine = DRC::DRCEngine::new
    engine.instance_eval(drc_string, '')
    

    Works great, thanks!

    I see some comments about this DRC module here (http://www.klayout.de/doc/manual/drc_runsets.html), but not an official documentation - do you plan on adding documentation for this, or is it an "internal" thing?

    I found by typing the following

    DRC.constants.select {|c| Class === DRC.const_get(c)} - RBA.constants.select {|c| Class === RBA.const_get(c)}
    

    that the DRC module is just the RBA module with some extra classes, viz.,

    [:DRCInterpreter, :DRCPlainTextInterpreter, :DRCVar, :DRCSizingMode, :DRCJoinFlag, :DRCAngleLimit, :DRCMetrics, :DRCWholeEdges, :DRCProjectionLimits, :DRCLayer, :DRCSource, :DRCEngine]
    

    and I can see the methods of each class by typing something like

    DRC::DRCEngine.methods
    

    so, this documentation may be lower priority as I have a way to peek under the hood for now. Just a suggestion.

    Thanks,

    David

  • edited February 2015

    Hi David,

    you're right, the .lydrc is an XML wrapper around the DRC script. "eval_instance" only takes the script's text, not the XML tags.

    Regarding the DRC's internals, the DRC engine is not as mystic as it looks. Actually, the DRC scripts are just a different interface to the underlying functionality. The pure functionality is formed by the RBA::Region class which provides almost all of the DRC functions.

    The DRC language is a DSL (domain specific language) around the Region object. DRCLayer simply wraps that object and you can even access it through the DRCLayer's "data" attribute. The DRC classes provide methods and objects which form a simple layer and intuitive notation. That's the beauty of DSL's and Ruby is in particular strong in that.

    So in the end, the reasoning is simply that it's more convenient to write

    input(1, 0).sized(1.um).output(1, 0)
    

    rather than

    tc = layout.top_cell
    l1 = layout.layer(1, 0)
    
    region = RBA::Region::new(tc.begin_shapes_rec(l1))
    region.size(1000)
    tc.shapes(l1).clear
    tc.shapes(l1).insert(region)
    

    The documentation does not say a lot about the internals since that is actually not something new. If you want to utilize the DRC functions in your script I think it's easier to use the Region objects and not use the DRC... wrapper layer.

    Matthias

  • edited June 2015

    Hi Matthias,

    Having a small problem with using DRC::DRCEngine::new. I have a simple DRC script, let's say

    ((input(1)|input(2)) & input(3)).sized(3.um).output(4)
    

    Each time I run it in DRC tab in Macro Editor it clears layer 4 and then outputs the new shapes to layer 4.

    However each time I run the same script using this code, it doesn't clear the layer 4 first.

    module QuickDRC
    
      include RBA
      engine = DRC::DRCEngine.new
    
      app = Application.instance
      mw = app.main_window
    
      dialog = QDialog.new(Application.instance.main_window)
      dialog.windowTitle = "Quick DRC"
      qt_layout = QVBoxLayout::new(dialog)
      dialog.setLayout(qt_layout)
    
      # Text input
      label = QLabel.new(dialog)
      qt_layout.addWidget(label)
      label.text = "DRC script:"
      script = QTextEdit.new(dialog)
      qt_layout.addWidget(script)
    
      # Run button
      buttonRun = QPushButton.new(dialog)
      qt_layout.addWidget(buttonRun)
      buttonRun.text = "Run"
      buttonRun.clicked { engine.instance_eval(script.toPlainText(), '') }
    
      # Close button
      buttonClose = QPushButton.new(dialog)
      qt_layout.addWidget(buttonClose)
      buttonClose.text = "Close"
      buttonClose.clicked { dialog.accept() }
    
      dialog.exec
    
    end
    

    Actually it's nice to have the option to either clear it first or not. Perhaps just adding a .clear method here would do it?

    I found one way to clear it first, but it is a slight pain. Instead of

    ((input(1)|input(2)) & input(3)).sized(3.um).output(4)
    

    You type

    lv = Application.instance.main_window.current_view;
    raise "No view selected" if lv.nil?;
    ly = lv.active_cellview.layout;
    li = ly.layer(4,0);
    ly.clear_layer(li);
    ((input(1)|input(2)) & input(3)).sized(3.um).output(4)
    

    Anyway just let me know if there is a simpler way... As there usually is... :-)

    Thanks,
    David

  • edited June 2015

    Hi David,

    the difference between both methods is that in the build-in DRC feature the DRCEngine object is created once per run while in your case it's being reused.

    This has one specific effect in your case: The DRCEngine object tracks the output layers used so far to allow multiple outputs in one DRC run to the same layer - those will actually accumulate output shapes. It also duplicates them so that an output layer won't mess with input layers on the same layer/datatype. Reusing the DRCEngine object will keep these layers open with the effect you observe.

    The DRCEngine object wasn't designed with reuse in mind, but you can try to initialize it before running the script (i.e. engine.initialize) or you create a fresh object for each run.

    Matthias

  • edited November -1

    You are correct, thanks. Moving the engine = DRC::DRCEngine.new initializer inside the .clicked method worked great. Thanks!

Sign In or Register to comment.