Installation and Usage

This document describes the differences between the Ruby and Python scripting environments. It assumes that you have some familiarity with Python and with the Ruby API for scripting Sketchup.

Installation

To use SuPy, you need to put the following items from the 'plugins' folder of the SuPy distribution into your Sketchup plugins folder. (The location of the Sketchup plugins folder depends on which version of Sketchup you have. Consult the Ruby API documentation for your Sketchup version to find out where it is.)

python.bundle (for MacOSX)
python.so (for Windows)
python folder and its contents
supy folder and its contents
supyboot.rb file

Locations for Python Modules

The python folder is where you put your Python scripts. It's placed on the search path for Python modules, and furthermore, any files there whose names end in .py will be automatically imported when Sketchup starts.

The python folder contains another folder called lib that is also placed on the import path. If you have any files that you want to be able to import but don't want loaded automatically on startup, you should place them in there.

The Sketchup Environment as seen from Python

All of the Sketchup-related Ruby modules and classes are available in the Python builtin namespace, so you can use them from any Python file without having to import anything.

How Ruby objects look to Python

For the most part, you can use Ruby classes and objects in a Pythonic way as you would expect. There are a few things to keep in mind, however.

Parentheses on method calls

Remember that Ruby has no notion of accessing an attribute of an object from outside that object. Things that often look like attribute accesses in Ruby are actually method calls, and must be written that way in Python. For example,

model = Sketchup.active_model()  # Parentheses required!

There is one exception to this, and that's when calling a method whose name in Ruby ends in '='. SuPy lets you invoke this kind of method using an attribute assignment, e.g.

my_face.material = my_material  # invokes 'material='

Names ending in ? or !

Some Ruby methods have names ending in '?' or '!'. When calling these from Python, as long as there's no ambiguity, you can just leave off the punctuation, e.g.

my_face.reverse()  # invokes 'reverse!'

If there is more than one method with the same name but different punctuation, you can use the following translations.

Ruby Python
xxx? is_xxx()
xxx! xxx_ip()for "in-place"

Calling and Instantiation

Calling a Ruby object will invoke its call method if it has one, otherwise new. This lets you instantiate most Ruby classes just by calling them, as you would a Python class:

p = Point3d(1, 2, 3)

Constructors named something other than 'new' will need to be called explicitly, however.

Passing Blocks

A Ruby method that expects a block argument can be passed a callable Python object by using the keyword body. For example,

def print_ent(ent):
    print ent

ents = Sketchup.active_model().active_entities()
ents.each(body = print_ent)

The number of parameters passed to the Python function depends on what kind of value the Ruby method sends to it. If it sends a Ruby nil value, the function is called with no arguments. If it sends a Ruby list, the list is unpacked and passed as separate arguments. Any other kind of value is passed as a single argument.

Methods requiring Arrays

Python tuples are converted to Ruby arrays, so you can pass them directly to methods that require an array. If you want to pass any other kind of sequence (including a list) you will have to convert it using the Array() function, which works like its Ruby counterpart.

Booleans

Be careful with boolean values! Only the Python values True and False are properly converted into Ruby booleans. Since Ruby regards almost everything as true, including zeros, empty strings and empty collections, this can easily get you into trouble. If in doubt, use bool() on values passed to Ruby methods expecting a boolean.

Unit Conversions

The Ruby trick of using things like 5.feet to convert units needs to be converted into a multiplication in Python, e.g. 5 * feet. Suitably-named conversion factors are available as builtins.

Parsing Lengths

There is a to_length() function that parses a string as a length and returns a number. It is equivalent to calling the to_l method of a Ruby number.

Implementing Tool Classes in Python

The modules tool and observer contain Python classes that you need to inherit from if you want to implement a custom tool class or an observer class, for example,
from tool import Tool
class MyTool(Tool):
    # Implement tool methods here

Calling Python from the Ruby Console

There's currently no direct way to enter a Python expression interactively, but you can access Python modules and their contents through the Ruby Console.

Firstly, there is a Ruby module called Py that contains all of the standard Python builtins, e.g. entering

Py.abs(-42)

into the Ruby Console will invoke the Python abs() function.

The Py module also contains all of the currently imported Python modules. For example, the example.py file that comes with the SuPy distribution can be referenced using the Ruby expression

Py.example

The example module defines a function called 'cylinder' which you can call as follows:

Py.example.cylinder(0, 0, 0, 10, 30)

Python files in the 'lib' subfolder are not automatically imported, but you can cause any Python file on the import path to be imported using a Ruby require command with a name  of the form 'python/modulename'. For example, if you have a file 'mystuff.py' in the python/lib folder, you can import it using

require 'python/mystuff'

and then reference it as
Py.mystuff
Note that if you're importing a module from a package, the components of the module name should be separated with dots, not slashes, e.g.
require 'python/my_pkg.my_module'
Short expressions and statements can be evaluated using the methods Py.eval and Py.exec:
spams = Py.eval("3 * 'Spam!'")

Py.exec("print 42")
You can also use these features from any Ruby script to access Python functionality.

Reloading Python Files

If you make changes to your Python files, you can reload them without restarting Sketchup by entering the following command at the Ruby Console:

Py.refresh

This will unload any modules imported from the python or python/lib folders and then reload any scripts in the
python folder.

You can also attempt to reload a specific module using the normal Python reload() function, i.e.

Py.reload(module)

but this is risky, because other modules that have previously imported from the module being reloaded may not see the new definitions. It's safer to start with a clean slate and reload everything.

Preserving Data

Sometimes you may want to keep data in a module-level variable that won't get lost when the module is reloaded using Py.refresh. SuPy provides a builtin function keep() to facilitate this. You call it immediately after the module code that initialises the variables you want to keep, passing their names as arguments. For example,
my_cache = {} 
precious = []

keep('my_cache', 'precious')
When you call keep() with a particular name for the first time in a Sketchup session, its current value is stored away in a safe place. The next time you call it with that name, any value currently in the module under that name is replaced with the stored value.

The stored names are associated with the name of the module from which the keep() function is called, so that kept variables with the same names in different modules will not be confused.

Bear in mind that if you store any instances of your own classes this way, and you subsequently change the definition of those classes, the preserved objects will still be instances of the old classes, and won't see any of the changes. The same thing applies to any objects that manage to survive some other way.

If you want to re-initialise a kept variable, you can use the clear_keep() function to remove the stored value. You pass it the fully-qualified name of the module (as a string) and a series of variable names to be cleared. E.g. from the Ruby console,
Py.clear_keep 'my_module', 'precious'
You will then need to reload the module concerned to establish a new initial value.

Python Standard I/O

Python's standard output and standard error channels are connected to the Ruby Console.

There is currently no usable standard input for Python. If you try to read from standard input, your script will probably either hang or raise an exception.

Examples

There is an example script called example.py in the python folder that illustrates how to add a menu item and execute some Sketchup operations in response to it.

There is also a package called more_examples containing some additional example scripts. To use these, either move them into the main python folder, or load them explicitly using

require 'python/more_examples.modulename'