This small building is owned by the local electric company. It's only continuing function is to provide a charming example of Craftsman commercial architecture in a petite size.

Small commercial building, Warwick, NY, c. 1910.

Small, commercial building in Craftsman style.

Edges to Rubies The Complete SketchUp Tutorial


Defining functions in SketchTalk..

Chapter 13—SketchTalk Functions

SketchTalk has a lot of virtues, and one major vice. It's nice to be able to create a box or donut with a single line of code. It's great that you can collect those SketchTalk lines in a file where you can edit them to fix a mistake, or to do something not quite like what you did last time. This is good.

Vice: it's my code. You've used little Ruby and less SketchUp Ruby API. Here we start to fix that.

Begin by deleting sketch_talk.rb. Yes, I'm serious. Throw it out. We're burning our bridges behind us.

Create a new file, martins_sketch_talk.rb if your name is Martin. Use your own name otherwise. It should look like this:

# /r/martins_sketch_talk.rb

require 'sketchup'

UI.messagebox( 'In Martins Sketch Talk' )

# end of /r/martins_sketch_talk.rb
Load that and make sure you get the "In Martins ..." message. (You did use your own name, didn't you? That's the whole point. You don't want my SketchTalk, you want your own!)

By the end of Chapter 14 you will have written your very own SketchTalk. You will know a fair amount of Ruby and the SketchUp Ruby API. You will be a person of power. Ruby power.

Select All with the all() Function

At its heart, SketchTalk is a set of functions and classes. We'll begin with functions and we'll start with the simple ones. First, let's add some organizational dividers in our code:
# /r/martins_sketch_talk.rb

require 'sketchup'

UI.messagebox( 'In Martins Sketch Talk' ) # delete this one!

# Classes -------------------------------------------------------

# SketchTalk Functions ------------------------------------------

# Support Functions ---------------------------------------------

# Test Code -----------------------------------------------------

# end of /r/martins_sketch_talk.rb
Within each section I keep my classes and functions in alphabetical order. You'll see that this bit of organization helps as your file grows.

Now let's start the all function. This is a good way to start a function:

# SketchTalk Functions ------------------------------------------

def all()
    UI.messagebox( 'all here' )
end # of all()

# Support Functions ---------------------------------------------

Reload your SketchTalk. Nothing happens. Type all into the Ruby console. Your "all here" messagebox pops up. (If it doesn't, do not proceed. Something is broken. Find it. Fix it.)

In the above code, the parentheses can be omitted after the function's name. def all and def all() are functionally identical. Why go to the extra work?

The Ruby processor sometimes warns you that you must use parentheses to ensure compatibility with future versions. Without parentheses you don't know if you are looking at a function name or a variable name. With parentheses it is explicitly clear that this is a function name. How long do you think I can go without forgetting my own rule?

Time now for a little API background.

Nothing here is too complicated, but entities deserve a closer look. When you start modeling, everything is in model.entities(). (I assume that you make a convenience copy of Sketchup.active_model() in the variable model.) You select all and make it a group. Your model's entities is now just one long. It's the group. Your group has its own entities collection. That's where all the faces and edges are now living.

Components are a bit more complex. You right-click your group and choose Make Component. You now have another entity at the top level: a ComponentInstance. A ComponentDefinition is attached as a property of the ComponentInstance. All the faces and edges are in the definition's entities collection.

Coders: A range is specified with the ".." operator. 1..10 is the range 1 through 10. It's very much like the array [1,2,3,4,5,6,7,8,9,10] except that it's more efficient.

Topic: Iteration

So far we've used sequential execution. Iteration is our first alternative to sequential execution. It is also known as "looping". The code that defines the iteration is known as a "loop."

for i in (1..10) do puts i end will puts "1", then "2" and so on up to "10". puts i could be replaced by any number of other instructions.

Coders: The simplest Ruby loop is:
for var in array
    # do something with each var
end

You commonly want to march through a set of things. You'll see that in the all() function we go through the list of all entities and select just the ones in visible layers. A "loop", such as the one here, lets you do that.

You provide a name to assign to each item in the set. for person in people would work if you had a list of people. The code before the end will be executed once for each member of people, so person becomes the first member of people, then the second member of people and so on.

Tim Toady These are all the same.
for var in array
    ... # do something
end

for var in array do ... end

array.each do |var| ... end

array.each { |var| 
    ... 
}

array.each { |var| ... }
Hard-core Rubyists seldom use for var in array. Must be too simple. See why Monty's going to give Tim a big hug if they ever meet?

Topic: array.push()

The push method adds a new member to a list at the end of the list.

people = []
people.push( 'Tom' )
people.push( 'Dick' )
people.push( 'Harry' ) 
# people is now ['Tom', 'Dick', 'Harry']

Tim Toady Ruby also features a push method disguised as an operator.
people = []
people << 'Tom'
people << 'Dick'
Now let's get back to work on that all() function. Begin by getting rid of the messagebox and adding this convenience variable:
def all()
    model = Sketchup.active_model()
end # of all()

Now add your first testable line.

def all()
    model = Sketchup.active_model()
    model.selection().clear()
end # of all()

Draw a bit of geometry. Select some of it. Reload your SketchTalk. Type all in the Ruby Console. If all is well, nothing is selected. We're almost there!

Topic: Methods

In object-oriented programming a "method" is a function that an object knows how to perform on itself. For instance, a selection knows how to add() new members.

We need an array of entities to pass to the selection's add() method. This bit of new code creates that array and passes it to the selection's add() method.

def all()
    model = Sketchup.active_model()
    model.selection().clear()
    ents = []
    for e in model.entities()
        ents.push( e )
    end
    model.selection().add( ents )
end # of all()

Try it out. Is everything selected? Hooray!

But there's a problem here. The entities collection has all entities in all layers. There could easily be a lot of geometry on hidden layers that is now selected. We want to be more specific.

Topic: Conditional Execution

Conditional execution means "run this code, but only if it meets a specified condition." For instance, we want to add entities to the selection, but only entities in visible layers. You'll see the syntax for doing that. It does what it looks like it will do.

Coders: Ruby provides a suffix if clause. Read on.

I hope you programmers who have been routinely skipping everything past the first paragraph stopped to take a look here. This is probably new.

With Ruby, you can put an if clause at the end of any statement. The example here does exactly what it looks like it does. This is very good stuff!

Add the conditional suffix here and you should select only geometry in visible layers.

def all()
    model = Sketchup.active_model()
    model.selection().clear()
    ents = []
    for e in model.entities()
        ents.push( e ) if e.layer().visible?()
    end
    model.selection().add( ents )
end # of all()

Add another layer to your model and create some geometry in it (or move some Layer0 geometry into it). Test all() thoroughly.

Got it where you can depend on it? Congratulations. Your SketchTalk is started. Let's move on to another challenge.

Topic: Conditional Expressions

e.layer().visible?() is a simple conditional expression: it calls a method, visible?() that returns either true or false. This is also a "logical" or a "boolean" expression.

Almost as simple as calling a boolean method (one that returns either true or false) is calling a simple logical expression a == b (true if a equals b, this is the one to read "a equals b") or a != b (true if a does not equal b).

More complex expressions can be formed with operators such as && ("and", true if both sides are true) and || ("or", true if either or both sides are true). Use parentheses extensively: if (season == 'fall') && ((day=='sunny') || (raining == false)) do drive_to_orchard() end

Add the del() Function

We're going to work through SketchTalk almost alphabetically, but skipping box and donut for now. We'll get to them later.

Coders: Ruby has unique method name conventions.

A Ruby method that queries an object for a true/false value (example: is an edge hidden?) ends with "?". edge.hidden?(). A normal method that returns a value does not impact the object queried. group.explode() returns the exploded geometry in group. A method that operates on the object itself should end with "!", as in array.sort!() or group.explode!().

From the above it follows that the Selection.clear() method was misnamed. It should have been Selection.clear!(). Oh well.
Now we know what we need to know to write the del() function. Let's get started. This is a good beginning:

end # of all()

def del()
    UI.messagebox( 'del here' )
end # of del()

# Support Functions ---------------------------------------------

Again, load and type del. You should get your messagebox to pop up. (Two notes: first, if I ask you to type code, you'll know without me saying where to type it, right? Second, those messageboxes come from long experience. You want to be sure that the code you add is actually being executed, before you start tearing your hair out when it doesn't work.)

Now, replace that messagebox with the convenience synonyms shown here:

end # of all()

def del()
    model = Sketchup.active_model()
    sel = model.selection()
    ents = model.entities()
end # of del()

# Support Functions ---------------------------------------------

Create and fill an array with the entities in the selection.

def del()
    model = Sketchup.active_model()
    sel = model.selection()
    ents = model.entities()

    sel_ents = []
    for e in sel do sel_ents.push( e ) end
end # of del()

Then use the erase_entities() method of the Entities class to get rid of them.

def del()
    model = Sketchup.active_model()
    sel = model.selection()
    ents = model.entities()
    
    sel_ents = []
    for e in sel do sel_ents.push( e ) end
    
    ents.erase_entities( sel_ents )
end # of del()

Test it. Got it? Two down and five to go. We'll speed up since there will be a lot less to learn.

The g() Function

Remember g()? That's the keyboard shortcut for "Make the selected geometry a component." On your own, add a skeleton for the g() function that pops a message box when you type g.

Topic: Arguments and Parameters

When you pass values to a function, the values you pass are called the "arguments" to the function. In people.push( 'Tom' ) the string constant 'Tom' is the argument of the push() method. Remember that a method is a function that an object can perform.

When you write a function, you provide a name to receive the argument. Inside the function the name(s) that receive argument(s) are called "parameters". Remember that g() is called with an argument, the name of the group to make. In the stairway that was g 'step'. If we called the name name, and that's exactly what we'll do, we would declare the start of the function with def g( name ) and name would be the parameter.

Unlike our first two functions, g() will actually call a support function to do the hard work. Replace your messagebox with the code you see here:

def g( name )
	model = Sketchup.active_model 
	return make_component( model.selection, name )
end # of g()

Why call another function? Suppose you had a doorway-making function. It might want to turn the doorway into a component. If it had kept track of its own geometry in an array of entities, it could call make_component().

Topic: Operator Overloading

If an operator has more than one use it is called "overloaded". You saw your first overloaded operator when you saw that the minus sign meant subtract with two operands (binary operator) and it meant negate with one operand (unary operator).

Topic: String Concatenation

You will often want to combine two or more strings into a single string. Most computer languages overload the plus operator to perform this function. For example, name = first_name + " " + last_name.

Coders: Ruby is crabby about string concatenation.

Unlike most languages, Ruby is of the opinion that it is the programmer's job to turn things into strings before concatenating with other strings. Unlike most modern languages, it will not, for example, turn a number into a string on its own.

Your job is made easier because most objects, (everything in Ruby is an object, even a numeric constant) have a to_s method. This is correct: for i in (1..10) do puts 'counter is ' + i.to_s() end. Just concatenating a string with an integer is an error.

So add your first Support Functions function, a messagebox popping make_component( entities, name ). Mine says UI.messagebox( 'make_component, name = ' + name ). Got it? Good!

If I show you the whole function at once will you promise to type it, not copy it? OK. I'll do that. First some API.

The Entities class has sixteen add_x methods. The "x" could be "3d_text", "arc", "circle", etc. Typical operation: the add_face() method is called with a set of points and it returns a face. The one of immediate concern is add_group that is called with an array of entities and it returns the group it creates.

There is no direct way to add a component. You add a group and then you use the group's to_component() method to turn the group into a component. You attach the name by using the component's name=() method.

Coders: Read on. This is odd.

One of the ways that you can assign a value to an object's property is by writing a property=() method. Given this method obj.property = value is a short-hand call to obj.property=( value ). We'll meet other alternatives in Chapter 14.

Remember that making a component creates a ComponentInstance. A ComponentDefinition is available from the definition() method of the component instance. When you create a component with the G shortcut, the name you enter is the definition name. (The components may also be named.)

All that said, this method should be perfectly clear.

def make_component( entities, name )

=begin
Given an array of entities and a name, returns a named component 
containing the entities.
=end

    ents = Sketchup.active_model().entities()

    g = ents.add_group( entities ) # group all entities
    inst = g.to_component() # convert group to component instance
    inst.definition().name = name

    return inst

end # of make_component()

We haven't returned values before. Why are we starting now? Remember this sequence:

step = g 'step'
mc step, [0,9,7], 14

We need a handle on the step instance to Move/Copy.

Three down, four to go.

The l() and line() Functions

Finally we come to functions for creating geometry. Again, l() copies the keyboard shortcut while the real work (there isn't much!) is done in a supporting function, line(). The latter function accepts an array of points and draws a line from the first to the second, another from the second to the third, and so on.

Begin by putting in a stub l() that just passes a single value (an array of points) to line():

def l( points )
    line( points )
end

This time we'll skip the messagebox stage and go right to the finishing line(). Not normally the fast way, but sometimes, when the code is trivial, you can get away with it. Pop this one into your Supporting Functions section:

def line( points ) # line( rgb1, rgb2[, rgb3[, ...]] )

    ents = Sketchup.active_model.entities 
    return ents.add_edges( points )

end # of line()

You can now test by typing in an array of points: [[0,0,0],[0,10,20],[10,20,30]]

Coders: How, in Ruby, do you create a function with a variable number of arguments?

def func( *args ). Reminds me of C-style pointers. It's not. args is an array of arguments. args[0] is the first argument, and so on.

Coders: Standard Ruby conditional execution is:
if cond1
    code1
[elsif cond2
    code2]...
[else
    code3]
end

You programming beginners met your first conditional execution in an end-of-statement if clause. That is a Ruby innovation. The standard way to do conditional execution is put code inside an if block. The simplest form is:

if pet == 'Monty'
    talk = 'educated English'
    walk = 'slither'
end

Often you will have a choice between two blocks of code. You want to run a second block if the condition is false. That is done this way:

if pet=='dog'
    talk = 'woof'
    run = 'long distance'
else
    talk = 'meow'
    run = 'burst'
end
Finally, you may have several conditions for which a test is needed, such as this:
if animal == 'cow'
    talk='moo'
    walk='slowly'
elsif animal == 'crow'
    talk = 'caw'
    walk='fly'
elsif animal == 'lion'
    talk = 'roar'
    walk = 'spring'
end

With all that said, replace your simple l() with this one:

def l( *args ) 
# coded for l( [ [rgb1], [rgb2],...] ) or 
# l( [r1,g1,b1], [r2,g2,b2] )
    
    if args.length == 1
        points = args
    else
        points = [ args[0], args[1] ]
    end
    
    return line( points )
    
end # of l()

Test that with two points and with an array of points. Congratulations! You are creating geometry and that completes 4, with only 3 left.

Coders: Ruby also features the unless keyword. It means the opposite of if.

Another boolean operator is the unary !, commonly read as "not". You could write if ! raining but in Ruby it's probably more clear to write unless raining. unless can be used at the start of the line or as the start of a logical suffix.

The mc() and move_copy() Functions

It takes two things to make a new ComponentInstance: a ComponentDefinition and a Transformation. The definition contains all the geometry of the original component. We've seen that before. What's new is the Transformation.

If you want your model to sing and dance you want to play with its Transformation. (Well, dancing yes but I'm not so sure about singing.) That is a sixteen-number structure that records where your component is located, how it is scaled and rotated and more. If you create a new component from an existing one, and use a vector to create a new Transformation, you can Move/Copy by a very precise amount.

Try this in the Ruby Console:

pt = Geom::Point3d.new( 0,0,0 ) # reports Point3d(0, 0, 0)
pt += [1,2,3] # reports Point3d(1, 2, 3)
pt += [1,2,3] # reports Point3d(2, 4, 6)

This is to show that adding a 3-number array to a Point3d adds a vector to a point, producing a new point. If you think this is how we place succeeding copies of our step component to turn it into a stairway, you are right. Let's get to the code.

Begin with a stub in the SketchTalk Functions section that just passes its parameters along to the real support function:

def mc( comp, dist, times )
    move_copy( comp, dist, times )
end # of mc()

Drop a messagebox stub into the Support Functions section for the move_copy() function.

Now, back into SketchUp. Rectangle from [0,0,0] to [36,9,0]. PushPull up 7 inches. Select all. Make all a component named 'step'. Back to the Ruby Console. ents = Sketchup.active_model.entities. Your ents.length should be one. ci = ents[0] should be a ComponentInstance. defi = ci.definition and trans = ci.transformation give you two convenience variables, a ComponentDefinition and a Transformation.

pt = trans.origin gives you your component's origin a Point3d at 0,0,0. pt += [0,9,7] moves that point nine inches along the green axis, 7 inches up the blue axis. trans = Geom::Transformation.new( pt ) gets you a new transformation, origin moved by vector [0,9,7].

Drum roll. ents.add_instance( defi, trans ). Cymbals crashing. A second step appears! pt += [0,9,7], then trans = Geom::Transformation.new( pt ) and another ents.add_instance( defi, trans ) gets you a third step. Got it? You move the point, get a new transformation and add the old definition with the new transformation as a new instance in the entities collection.

Since you've already done all the typing, some of it more than once, just copy this function into your Support Functions area, replacing the stub.

def move_copy( component, distance, number_of_copies )

=begin
Creates an outer array, moving a component "distance" as many times as
specified.

"distance" is a vector, an array of [r, g, b].

"number_of_copies" is like the "Nx" in the VCB after a Move/Copy.
For 15 steps, you make 14 copies.
=end

    ents = Sketchup.active_model.entities

    defi = component.definition # the original component's definition
    trans = component.transformation # the original's transformation
    pt = trans.origin # the original's location, a Point3d

    for i in ( 1..number_of_copies )
        pt += distance
            # add vector to Point3d getting new Point3d
        trans = Geom::Transformation.new( pt )
            # create new Transformation at the new Point3d
        ents.add_instance( defi, trans )
            # add another instance at the new Point3d
    end

end # of move_copy()

Topic: Class Constructors

A "constructor" is a method provided by a class to get new objects of that class. In Ruby and other languages, the class's new() method call may be a constructor. Geom::Transformation.new() is an example of a class constructor.

Coders: The new() method is interesting, in Ruby.

Calling new() actually calls the method initialize(), if the class has one. It is up to the designer to decide if instances may be created this way. Relatively few of the API's classes provide for instantiation via a new() method. You get a new ComponentInstance, for example, by calling a Group's to_component() method or the Entities add_instance() method.

You can, in fact call Sketchup::ComponentInstance.new(). No error is thrown. This should be an error. Oh well.
We've completed five. Two are left. They won't take long.

The n and new() Functions

In their entirety:
def n # convenience substitute for new()
    all
    del
end

def new # File/New, spacebar (Select selection tool)
    Sketchup.file_new()
    Sketchup.send_action( 'selectSelectionTool:' )
end # of n()
(All the constants you can pass to the send_action() method end with a colon. If anyone finds out why, please email me!)

Type that into the SketchTalk Functions area. (Yours are in alphabetical order, aren't they? This would be a good time to get them organized if they're not.)

Let's discuss this, briefly. Sketchup.file_new() is The API way of clicking File/New. The Sketchup.send_action() method can be called with a long list of action constants. They all do what they say they will do.

Simple enough? Just one more.

The r() Function

To begin, this is r(), in its entirety:

def r( near, far )
    return rectangle( near, far )
end # of r()
Enter that into your well-organized SketchTalk Functions area.

Next, take a look at the rectangle() function that r() calls:

    r = Rectangle.new( near, far )
    r.draw()
    return r

By now you should recognize Rectangle.new() as a class constructor. It will make a new object of the Rectangle class. This chapter is all about the SketchTalk functions. Chapter 14 is about the SketchTalk classes.

So that's it for Chapter 13. I hope you feel like you're beginning to do some SketchUp Ruby programming, because you are. When we take our skills to the OOP level, you'll have some very powerful arrows in your programming quiver.


Doughnut component in SketchTalk. View of apartment contents. Defining synonym classes for easy typing.