TUTORIAL
Beginning Ruby 2 – Writing a Script – Tutorial by Chris Fullmer
Beginning Ruby 2 – Writing a Script
This is the second tutorial in the “Beginning Ruby” series here at Sketchucation. This tutorial shows how to write a fully functioning Ruby plugin that Push/Pulls multiple faces simultaneously. This includes accessing the “Undo” stack, adding to the menu system, working with a user selection, and providing an inputbox for the user to specify a distance to Push/Pull. This tutorial relies on certain programs and skills that were explained in the first tutorial titled “Getting Started”.
01
INTRODUCTION
We will be making a script that does a PushPull action on faces in the model. We will be writing most of the script in the Web Console.
Left: The text in the Web Console
Bottom: Before and after of a model, showing what our script will be capable of doing.
02
Step 1/11
From the Previous Tutorial
If you recall from the first tutorial – Beginning Ruby 1 – Getting Started – we made a very simple script that looped through the model and found all entities in the model and sorted them into arrays. That is a perfect starting point for many possible Ruby scripts.
The code below is the final code from the Beginning Ruby 1. We will be editing and building on this code in this tutorial to make a fully functioning Ruby script that Push/Pulls faces in the model by a user specified distance.
I recommend writing this script in the Web Console. After it is working, we will copy it over to a text editor for some final touches.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | model = Sketchup.active_model entities = model.active_entities selection = model.selection edges = [] faces = [] comps = [] groups = [] entities.each do |e| edges.push e if e.is_a? Sketchup::Edge faces.push e if e.is_a? Sketchup::Face comps.push e if e.is_a? Sketchup::ComponentInstance groups.push e if e.is_a? Sketchup::Group end puts "Total Edges : " + edges.length.to_s puts "Total Faces : " + faces.length.to_s puts "Total Components : " + comps.length.to_s puts "Total Groups : " + groups.length.to_s puts "Total Entities : " + entities.length.to_s |
03
Step 2/11
The Final Results
This is what we will be changing our starting code to look like. I thought I would let you see it now so you can see how much we are changing. I’ll try to go over all the changes so it is clear what everything does.
So lets move on to the next step and get started!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | require 'sketchup.rb' module BR_push_pull def BR_push_pull.main model = Sketchup.active_model selection = model.selection model.start_operation "Multiple Push/Pull" faces = [] if model.selection.empty? entities = model.active_entities else entities = model.selection end entities.each do |e| faces.push e if e.is_a? Sketchup::Face end prompts = ['Push/Pull Distance'] defaults = [0.to_l] results = UI.inputbox prompts, defaults, 'Distance to Push/Pull' distance = results[0] faces.each do |e| e.pushpull distance, true end model.commit_operation end # .main method end # BR_push_pull module #----------------menu------------------ if !file_loaded?(__FILE__) UI.menu("Plugins").add_item("Multiple Push-Pull") { BR_push_pull.main } end file_loaded(__FILE__) |
04
Step 3/11
Clean Up the Base Script
We are going to be making a plugin that does a push/pull on many faces at once. In the previous tutorial, we search the model for edges, faces, components and groups. We only need to use the faces that we collected. Therefore we need to clean up our previous script so that it is only looking for and organizing faces.
Notice I deleted all the arrays that we used for edges, components and groups. All that the script is doing now is examining all entities in the model and making an array of all the faces it finds.
To get the script to this state, I took the code from the previous tutorial (also shown in step 1 of this tutorial) and removed lines 5, 7, 8, 11, 13, 14, 16 – 21.
1 2 3 4 5 6 7 8 9 | model = Sketchup.active_model entities = model.active_entities selection = model.selection faces = [] entities.each do |e| faces.push e if e.is_a? Sketchup::Face end |
05
Step 4/11
Adding the PushPull
I’ve added lines 6, 12, 13 and 14 here.
Line 6 creates a new variable with a value of 100. This is the distance that we will push/pull each face. The units are in inches by default (in a later step we will make it work with the user’s default units instead of inches).
Lines 12-14 are very straightforward, and do most of the magic of the script. The code is taking the “faces” array and is running the pushpull on each face.
Notice the two parameters (called arguments) that are being use with the pushpull method. The first one is a distance, and we are using our variable “distance”. So it will pushpull each face 100 inches. The second argument is a true/false value. This is the equivalent of hitting ctrl (PC)/option (Mac) during a pushpull action in SketchUp. Setting this to false will make the pushpull method do a simple pushpull. Setting it to true will make the method create a new face during the pushpull – the same as if you hit ctrl in SketchUp while pushpulling.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | model = Sketchup.active_model entities = model.active_entities selection = model.selection faces = [] distance = 0 entities.each do |e| faces.push e if e.is_a? Sketchup::Face end faces.each do |e| e.pushpull distance, true end |
06
Step 5/11
Already Working!
At this point, the script is working. This image shows the script loaded in the Web Console. I have a sphere in the model. Just press the “Execute” button in the web console and the script will pushpull all faces in the model by 100 inches.
Here is the sphere after running pushpulling all the faces.
07
Step 6/11
Adding “Undo” Functionality
If you have tried the script at this point on some geometry, and then tried to undo it, you probably noticed that each pushpull is being treated as a separate action to be undone. So if you ran the script on a simple box, it has done 6 pushpulls, requiring that you hit undo 6 times to get back to your original box. Or if you ran the script on a sphere with hundreds of faces….well good luck undoing that! Lets make the script group all actions together into a single “undo” action.
I’ve added lines 4 and 16 here. Line 4 opens the undo queue with the start_operation method. Line 16 commits it with the commit_operation method. So everything that happens between those lines is treated as a single “undo” action.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | model = Sketchup.active_model entities = model.active_entities selection = model.selection model.start_operation "Multiple Push/Pull" faces = [] distance = 100 entities.each do |e| faces.push e if e.is_a? Sketchup::Face end faces.each do |e| e.pushpull distance, true end model.commit_operation |
08
Step 7/11
Making a Method
We are going to put all our code so far into a method. To do this, we define the method, end the method, then we add a line of code at the end of the script that will call the method and run our code. This will all come in very handy later when we add our plugin to SketchUp’s menu system.
I’ve added lines 1, 20 and 22. On line 1 I named the method “main” because it is the main method. Well, its the only method in this script, but naming methods wisely is important in scripts that have many methods so you can keep track of what each method is doing. Line 20 ends the method. Notice I’ve added a commented out line that repeats the name of the method. This just helps keep track of all the “end” statements so I can tell which one is actually ending the method. Line 22 calls the method. The method will not run unless it is called.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | def main model = Sketchup.active_model entities = model.active_entities selection = model.selection model.start_operation "Multiple Push/Pull" faces = [] distance = 100 entities.each do |e| faces.push e if e.is_a? Sketchup::Face end faces.each do |e| e.pushpull distance, true end model.commit_operation end # main method main |
09
Step 8/11
Safety Wrap – Module
Maybe you were already thinking about this, or maybe not. But when we named our method “main”, we set ourselves up for serious problems. If any other script has a method simply named “main” like ours, we will probably break eachother’s scripts. The way to fix this is to wrap our code in a Ruby Module. There is an entire thread devoted to this topic on the SketchUcation forums here.
I’ve added lines 1 and 24 and tweaked lines 3 and 26. Line 1 defines the module and line 24 ends the module. Its important to note that now that our method is being declared inside a module, we need to alter how we are defining it. “def main” no longer works. We need to specify that it is being defined as part of the “BR_push_pull” module. On line 26 we also have to adjust how we call the method to include the module name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | module BR_push_pull def BR_push_pull.main model = Sketchup.active_model entities = model.active_entities selection = model.selection model.start_operation "Multiple Push/Pull" faces = [] distance = 100 entities.each do |e| faces.push e if e.is_a? Sketchup::Face end faces.each do |e| e.pushpull distance, true end model.commit_operation end # main method end # BR_push_pull module BR_push_pull.main |
10
Step 9/11
Using Selected Entities
Right now, our script is going to find every face and pushpull every face in the model. Let’s change it so that it first checks if the user has geometry selected. If they do have something selected, we will only run the script on those entities. If nothing is selected, then the script should process the entire model.
I’ve added lines 13 – 17. Line 13 checks to see if the selection is empty. If it is empty (the user has nothing selected), then we assign all active_entities to our “entities” variable. But if the user does have a selection, then we assign that selection to the “entities” variable. Then only the selected entities will be processed later on line 19.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | module BR_push_pull def BR_push_pull.main model = Sketchup.active_model entities = model.active_entities selection = model.selection model.start_operation "Multiple Push/Pull" faces = [] distance = 100 if model.selection.empty? entities = model.active_entities else entities = model.selection end entities.each do |e| faces.push e if e.is_a? Sketchup::Face end faces.each do |e| e.pushpull distance, true end model.commit_operation end # main method end # BR_push_pull module |
11
Step 10/11
Adding an InputBox
Lets change how the pushpull distance is determined. Right now, it is just a variable that we are defining in the script. Lets add an inputbox for the user to input the distance they want.
I added lines 19 – 22. This is a very basic implementation of the UI.inputbox. For more information on how to use it, see the API. But the basic idea is to define an array that holds the input titles and one that holds the default values. Line 21 triggers the inputbox and whatever the user inputs, is returned to the “results” array. Since our inputbox only has one input, it is stored at “results[0]“.
The other important thing to note here is that in the default values, I have used “0.to_l”. to_l is a conversion method that converts the integer 0 to the defrault user unit – 0″, 0′, 0m, 0mm, etc. This way whatever the users enters, SketchUp will treat it like a measuerment unit. The user can enter 12″ or 1′ or 3m or 300cm and SketchUp will accurately process that as a length and convert it accordingly. All because I used the .to_l on the zero in the default value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | module BR_push_pull def BR_push_pull.main model = Sketchup.active_model entities = model.active_entities selection = model.selection model.start_operation "Multiple Push/Pull" faces = [] distance = 0 if model.selection.empty? entities = model.active_entities else entities = model.selection end prompts = ['Push/Pull Distance'] defaults = [0.to_l] results = UI.inputbox prompts, defaults, 'Distance to Push/Pull' distance = results[0] entities.each do |e| faces.push e if e.is_a? Sketchup::Face end faces.each do |e| e.pushpull distance, true end model.commit_operation end # main method end # BR_push_pull module |
12
Step 11/11
Add the Script to the Plugins Menu
We have now added all the functionality to our script that we are going to add. The only thing left to do is save our script to a file and add it to the menu system. At this point, I copy the script out of the Web Console and paste it into Notepad++, my text editor. Then save it as any file name you choose, preferably one that describes what the plugin does. multiple_push_pull.rb is logical, but anything along those lines works. “.rb” is the standard filetype for a ruby file. The file should be saved to the sketchup\Plugins folder.
I have added lines 1, and 36 – 41. On line 1 I “require” the sketchup.rb file because it adds a feature that improves the menu system that I want to use (it adds other methods to SketchUp, and I recommend that you open the sketchup.rb file and try to look at its methods). Lines 37 uses the file_loaded method defined in sketchup.rb to determine if this plugin has been loaded yet. If it has not been loaded, then line 38 loads it into the menu system. The method UI.menu () specifies what menu to load the plugin into. And the method add_item() {} specifies the name to use in the menu system and also lets us specify what method to call when the user activates the plugin in the menu . Save the script, open SketchUp and check the “Plugins” folder. Your script will be listed there as “Multiple Push-Pull”. Now we’re all done!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | require 'sketchup.rb' module BR_push_pull def BR_push_pull.main model = Sketchup.active_model selection = model.selection model.start_operation "Multiple Push/Pull" faces = [] if model.selection.empty? entities = model.active_entities else entities = model.selection end entities.each do |e| faces.push e if e.is_a? Sketchup::Face end prompts = ['Push/Pull Distance'] defaults = [0.to_l] results = UI.inputbox prompts, defaults, 'Distance to Push/Pull' distance = results[0] faces.each do |e| e.pushpull distance, true end model.commit_operation end # .main method end # BR_push_pull module #----------------menu------------------ if !file_loaded?(__FILE__) UI.menu("Plugins").add_item("Multiple Push-Pull") { BR_push_pull.main } end file_loaded(__FILE__) |
13






[...] if you can expand the code to search for more entity types than the four we implemented. Also try the 2nd tutorial in the Beginning Ruby series. Hope this [...]
[...] Chris Fullmer continues his Ruby scripting tutorial series with part 2: Writing a script. [...]
Thank you for the tutorial! How would I create a toolbar to activate the script?
Hi Chris, check out the Toolbar class:
http://code.google.com/apis/sketchup/docs/ourdoc/toolbar.html
Also, you can head over to the sketchucation forums and go to the Ruby Discussions section. If you ask this same quesion there, somone will be able to get into in more depth with you there.
And that is also another topic for a tutorial I have planned to do. But that won’t be for a while. So maybe if you ask the question in the forum, I can respond and start forming some of the text and ideas for my next tutorial. Hope that helps,
Chris Fullmer
I just wanted to comment about your site and video tutorials.
It is excellent that you (or however many people there are contributing to this site) do as much as you do all for Free. People volunteering to provide such in-depth information in video tutorials of all types, deserve all the kudos they can get.
I had already learned allot about SketchUp from video tutorials before I ever found your site. But I have learned a few new things since too, thanks to you.
I will definitely return here to learn more when needed and have already passed your web links on to many new SU users, all of whom will benefit tremendously thanks to you (again).
So .. again .. Thank You very much for all that you do. I know I speak for many when I say that it is all greatly appreciated. Your dedication will always be invaluable.
Chris aka SnowTiger
Thanks really for these great tutorials. I begin to understand the program structure.. waiting your following lessons .
MALAISE
I’m having problems viewing the tutorials on a Mac using current versions of Safari (4.0.3) or Firefox (3.0.11). I have only looked at the two ruby tutorials. On part 1 the first four steps or so were fine, and then after that it appears that inside of having content to the right of the left grey gutter, the content slips behind the grey gutter. On part 2 I can see the introduction and step 5 ok, and the rest are behind the gutter. Anyone else having this problem on mac?
So, I found out that if I save to Evernote, that I can view the tutorial there :), I don’t have the images, but the code and instructions is what I was really interested in.
Re: David Goldwasser
Yes Tutorials not displaying correctly on IE7
Yeah, I’m sorry the tutotials are broken. I’ll see if there is anything I can do. BUt like was noted, you can copy and paste the text into a text editor to read it. Sorry about all this,
Chris
On my Vista PC, I have same problems (content slip behind the grey gutter, etc.) with IE7 and Safari.
…even Chrome isn’t able to display this right ;-)
Same problem with viewing the site. Mac OS10.3 and Firefox. Resolved as follows:
1. save site as html with Firefox
2. opened saved html file in Firefox
don’t ask me why, but it worked.
Chris, Thanks, Hope you are able to write a little more about classes, and the other oop stuff.
When I try to execute the script I get the following error:
undefined local variable or method ‘e’ for WebConsole::BR_push_pull:Module
Any help you can give me regarding how to resolve this error would be greatly appreciated.
I have checked my typing several times and can’t seem to find an error. Have you supplied a text file of the script for the 2nd tutorial like you did for the first?
Wow, so sorry everyone. The board was not notifying the authors about comments made, so I did not see these comments until today!
Obviously the problem has not been fixed yet. I’ve made sure management knows about it. I hope they get it fixed soon. I’m guessing its some css issue.
William, I am not sure what is causing the problem. I would need to see your code, and see the full error (with the line numbers it returns) to help debug it a little better. You can post in the ruby forum on the sketchucation site, or just PM me on the sketchucation forum and I’ll get back to you easier there. Thanks for trying out this tutorial!
Also check out http://www.youtube.com/user/ChrisFullmer for some other ruby stuff.
The pains-taking way around the IE7 and Chrome problem with the code hidden behind the grey is to highlight the code by dragging the mouse across it (( don’t use Ctrl+A)it takes the grey with it).
Copy the selected code and paste it into an editor or wordprocessor or…
In the view menu I changed to no page style which seems to help.
thius website is coooool