Editing Layout Files with the Ruby API

I am new to Ruby but have several years of professional experience developing in Python. I would like to write a script to insert a few pieces of information from a spreadsheet into a Layout template. How do I go about developing and, ultimately, executing such a script?

So far I have tinkered with the Ruby console within SketchUp and read through some of the example extensions. The piece of information I am having a hard time with is how to use the API without being in Layout. Old forum posts and some documentation refer to “third parties” being able to interact with Layout outside of SketchUp, but I am not seeing any documentation on what exactly that means.

Ultimately the goal is to be able to “sync” data from a Google Sheets spreadsheet without downloading the individual tabs as CSVs for file references

It is currently a file creation and access API which is (usually) used from a “live” SketchUp instance to read from the active model and write to a Layout::Document in memory. When the work is done, this document object is saved to a file path location.

You have the main LayOut API intro page:
File: LayOut Ruby API — SketchUp Ruby API Documentation
… but it’s “Things to Know” are not really listed in educational order.

Unfortunately, the SketchUp Team has not yet setup a repository of LayOut API examples.

One thing yet to be documented better is that entities get assigned default style properties when initially added to a document. This means that you should make any style changes after adding an entity to the document, otherwise your changes will be overridden.
There are also some other properties that are best adjusted after an entity belongs to a document. So the main “rule of thumb” should be add first, adjust afterward. This holds for both Ruby and C APIs.

“That” means they are using the LayOut C API.

I suppose if one is good at C and creating language bindings, one could write a Python binding for using the LayOut C API standalone (outside) SketchUp.

This can be done. However currently we cannot create “update links” to external spreadsheet files using the API. This can be done manually by importing the spreadsheet.

What I mean is that you can build a table and insert data into it, but later the user cannot automatically update the table when the data in the external file changes.

The open issue in the API tracker is:

@jack.mason Here is a very simplistic example.

Reading from an external file can be done in Ruby with the IO class and it’s File subclass. Also if it’s a csv file then there is a standard CSV library in Ruby.

There is also the WIN32OLE class that can read xls files.

module JackMason # author toplevel namespace
  module LayoutTableExample # extension submodule

    extend self

    def go
      # Start a new document:
      doc = Layout::Document.new
      # Create a table:
      bounds = Geom::Bounds2d.new(1, 1, 4, 4)
      rows = 4
      columns = 4
      table = Layout::Table.new(bounds, rows, columns)
      # Add the table object to the document:
      doc.add_entity(table, doc.layers.first, doc.pages.first)
      # Create some text for the table:
      anchor_type = Layout::FormattedText::ANCHOR_TYPE_TOP_LEFT
      start_point = Geom::Point2d.new(1, 1)
      text = Layout::FormattedText.new("Hello LayOut", start_point, anchor_type)
      # Insert the text into the table object:
      table[1, 1].data = text
      # Get a path where to save the document file:
      filepath = UI.savepanel(
        "Save Layout File", # Browse dialog caption
        File.join(ENV["HOME"],"Documents"), # Path to open
        "LayOut Files|*.layout||" # Windows file pattern
      )
      return if !filepath # will be nil if user cancelled dialog 
      begin
        if File.extname(filepath) != '.layout'
          filepath = File.basename(filepath,'.*')<<'.layout'
        end
        puts "Saving #{filepath.inspect}..."
        doc.save(filepath)
      rescue => error
        UI.messagebox(error.message)
      else
        Sketchup.send_to_layout(filepath) # open new file in LayOut
      end
    end

    unless defined?(@loaded)
      UI.menu("Extensions").add_item("Make LayOut table") { go() }
      @loaded = true
    end

  end
end

EDITED: To ensure that ".layout" file extension concludes the file pathname.

This might be possible, but the “sync-smarts” will have to be written by your code.
Ie … your code will need to read the online data and compare with that in the local table with a LayOut file you’ve opened using the LayOut API.

LayOut natively only (currently) supports sync’ing to CSV, TSV and XLSX files.

The Ruby gems for the Google APIs appear to install without error. But be aware that SketchUp’s Ruby is a shared objectspace. This means that care should be taken to prevent your code from interfering with other extensions.

But, gems are not written to be used in a shared environment. This means if some other extension loads an older (or newer) gem library than your code has, it could break your extension. (Ie, gems authors think that they’ll be used in a temporary private process that loads 1 ruby script, then exits.)

So the instructions at: https://developers.google.com/sheets/api/quickstart/ruby
are not written to run within SketchUp embedded Ruby.

Step 1 is really done from SketchUp’s console as …

Gem.install("google-api-client")

This takes quite a bit of time as it installs a bunch of gems.

Step 2 The file shown is not ready to run inside SketchUp.

  1. It must be module wrapped in your toplevel namespace and probably a submodule.

  2. The service initialization will need to be referenced by a @service variable for persistence.

  3. You’ll likely want to wrap the @service.get_spreadsgeet_values up in a method that can be called repeatedly.

  4. The file is not put in “your working directory” as it is not loaded by a system Ruby process.
    It will be within an extension subfolder of the user’s %AppData% SketchUp “Plugins” folder.
    It will be loaded be an extension registrar script.

  5. You don’t “run it” in a sequential manner as shown. SketchUp extension code is event driven.
    Your extension will need to create a menu command (or toolbar with a button) which the user will click to run it’s sync command. (Ie, responding to a click event.)

  6. SketchUp seizes console IO for it’s own use, so you cannot use gets inside SketchUp.
    You’ll either need to use UI.inputbox or the UI::HtmlDialog class.

~

Thanks, @DanRathbun , this is a lot of really great feedback and answers a lot of my questions!

After poking around in my LayOut template file, I feel comfortable with the writing side of the project. I will trying tinkering with the Google API in Ruby and see if this would be a viable solution.