Writing a Useful Snippet
This article focuses on the ideation aspect of SketchUp Extension development. For the technical aspects of how to create a valid SketchUp Extension, see Creating a SketchUp Extension. If you've never written code before, you may want to start with Writing Your First Code.
When making a SketchUp extension, you first need an idea. The idea can be from your own modeling workflow, maybe a task you are currently doing manually but want to speed up with automation, or an idea from a customer or an investor. For beginners, something close to your own workflow is often easiest.
If you are new to coding and don't know the limitations, we'd recommend starting to automate some of the most dull mindless clicking in your workflow. Something with no creative input or decision making. Something that can be clearly defined in words and delegated to the least creative but most meticulous and tireless boring person you could think of. Or really, delegated to a machine.
It's also good to avoid something that requires a kind of visual intuition. Some tasks, like packing objects efficiently in a tight space can be easy for a human to just see the answer to, but it's trickier to write down the instructions as mathematical formulas.
Example Snippets
These snippets could be typed and worked on in the SketchUp Ruby Console (Extensions > Developers > Ruby Console) but as they get longer that gets inconvenient. You could also use a code editor, save them as an .rb file. You can use the load method followed by the file path (within quotes) in the Ruby Console to load and run the file. To later reload, you can simply press the up arrow key and Enter. You could also use Eneroth Script Runner to drag and drop the .rb file into SketchUp, or use Ruby Console+.
Randomize Colors
Maybe you've drawn a bookcase, a container terminal or a car park and want to apply a bunch of random colors to a large set of objects. For a small project you can easily click a bunch of times in the Materials panel to pick different colors but when the project grows we want a better solution.
Computers are great at iterating over objects and doing repetitive tasks to them, without getting bored or tired, like this:
Sketchup.active_model.start_operation("Paint Stuff", true)
Sketchup.active_model.selection.each do |entity|
entity.material = "red"
end
Sketchup.active_model.commit_operation
start_operation and commit_operation tells SketchUp that the code in between is a single action that should be possible to undo in just one step. If we don't do this, you may need to undo hundreds of times to go back to restore the previous colors. But if you accidentally undo one time too much, you instead revert the work you want to keep.
If used on groups and components as opposed to directly on faces, make sure the content is painted with the default material (aka no material) for the material of the container to be displayed on its content. (Learn more about material inheritance in SketchUp.)
But so far everything selected just turned red. This could just as well have been done with the Paint Bucket tool! Let's randomize the colors. For this it's easier to create a custom color, based on numbers, not named colors.
color = Sketchup::Color.new(255, 255, 0)
Computers typically use RGB colors, colors defined as a mix of the three additive primary colors red, green and blue. The value of each channel can vary between 0 (darkness) and 255 (full light). Red and green combined makes yellow.
Computers can generate psuedo-random numbers. 256 here is for the 256 possible values from 0 to 255.
number = rand(256)
You can combine this to generate a random color.
color = Sketchup::Color.new(rand(256), rand(256), rand(256))
And plug it into the script above, to do it for each selected entity.
Sketchup.active_model.start_operation("Paint Stuff")
Sketchup.active_model.selection.each do |entity|
entity.material = Sketchup::Color.new(rand(256), rand(256), rand(256))
end
Sketchup.active_model.commit_operation
This is a tiny script of just a few lines that can add actual value to a SketchUp Project! But it's far from perfect. You may have noticed the In Model view in the Materials panel is now quickly flooded with materials, many of them being almost identical. Also, as a user you have no control what colors are used.
To avoid flooding the model with new materials, we can use existing materials within the model. This code gives us an array of all materials in the model.
materials = Sketchup.active_model.materials
The sample method can be used to pick a random element from an array.
favorite_tool = ["Push Pull", "Orbit", "Swedish Key", "Line"].sample
We can combine these to pick any random material from the model, and use it in our previous code.
Sketchup.active_model.start_operation("Paint Stuff")
Sketchup.active_model.selection.each do |entity|
entity.material = Sketchup.active_model.materials.sample
end
Sketchup.active_model.commit_operation
This solved the problem of flooding the model with new materials, but we still have little control of the materials assigned. In a simple model with just a scale figure, we may get a nice color distribution, but for an actual project we could now have cars made of carpet and shipping containers made out of leaves. That's not very good.
Another approach is to not pick any random color within the model, but pick a color already present in the selection set. For this we need to first collect the colors of the selection.
materials = []
Sketchup.active_model.selection.each do |entity|
materials << entity.material
end
[] creates an empty array. << is used to append an element to the array.
A shorter way to do the same thing in Ruby is to use the map method.
materials = Sketchup.active_model.selection.map { |e| e.material }
We can shorten the code even further with a handy Ruby idiom.
materials = Sketchup.active_model.selection.map(&:material)
Then we can sample from this new list of materials.
Sketchup.active_model.start_operation("Paint Stuff")
Sketchup.active_model.selection.each do |entity|
entity.material = materials.sample
end
Sketchup.active_model.commit_operation
These could be combined to a single chunk of code, to redistribute the materials already used in that selection, but also kept separate if you want to sample from one selection and apply to another.
You can also use materials.uniq! to remove any duplicates from the array. This means all materials are equally likely to get used. Otherwise the likelihood is weighted by their original frequency in the selection.
You can also use materials.compact! to remove nil values, i.e. the absence of a material. This means every selected entity gets painted with a material and not reverted to the default (none-)material.
This code for randomly distributing existing materials can also be used in tandem with our previous code for randomly generating materials, with you the user manually picking out the colors you like in between. The possibilities are near endless!
With snippets you can quickly adjust and customize on the fly. Once you are happy with the functionality, you can wrap the chunks of code into methods, add menu entries or toolbar commands and properly package it as an extension.
If worked into an actual extension, you could have one button to generate random new materials, and another button to distribute the currently applied materials, with the user manually pruning their materials in between. There could also be separate buttons for sampling and applying. It takes a bit of testing and tweaking to see what makes the most sense for practical usage.
Book Titles

Another script useful for a bookcase is the book title script I made when moving in 2017. I wanted to plan my new bookcase in advance and naturally modeled my books in SketchUp. In practice, I modeled one generic book and scaled it to different sizes and applied different materials.
You could stop there but I also wanted to see the titles on the spines of all the books, to aid when categorizing them. For this I created a new component for each book, wrapping the sized and painted generic book component along with a 3D text on the spine. For a small set of books you could manually create the 3D text and position it on each spine, but for more than a handful this quickly gets tedious.
Instead I used a small little script to create the 3D text, based on the definition name of the component. Then the name can be controlled from Entity Info.
model = Sketchup.active_model
model.start_operation("Label Books", true)
model.selection.each do |entity|
label = entity.definition.entities.add_group
label.entities.add_3d_text(entity.definition.name, TextAlignLeft, "arial")
end
model.commit_operation
This first version iterates over the selected entities and adds a group with their definition name as text. The text is in the wrong place, with the wrong orientation and wrong size, but it is a start. To actually see the text, you may need to turn on Back Edges or X-Ray.

If the book components are not already unique (have individual definitions), this script can't add individual labels to them. To make them unique you can right click at each one and hit Make Unique. If you want to reuse the same generic book component between them to reduce the file size a bit, you can enter the component and make a generic component out of all its geometry, before making the individual instances unique.
Before adjusting the placement, we can change the script to purge any previously added text. This means we don't have to undo between iterations when developing. It also helps in actual usage, in case a book is renamed. In practice, to add a book to my collection, I make a copy of any nearby book, make it unique, change the size and material of its content, rename it and run the script again to add the new title.
For this, we can use a Tag (formerly called Layer) to differentiate what the script draws from what we draw manually as users.
model = Sketchup.active_model
model.start_operation("Label Books", true)
# layers.add either creates a new Tag or returns the existing one by that name.
tag = model.layers.add("Book labels")
model.selection.each do |entity|
entities = entity.definition.entities
old_label = entities.find { |e| e.layer == tag }
old_label.erase! if old_label
label = entities.add_group
label.entities.add_3d_text(entity.definition.name, TextAlignLeft, "arial")
label.layer = tag
end
model.commit_operation
The first time, we need to still manually delete the old labels, as they were never tagged. But from now on we can change the definition name in EntityInfo and run the snippet again to update the label.
Now let's look at the label orientation. add_3d_text creates the text on the local X-Y plane, along the X axis. add_group creates a group with the identity transformation matrix, i.e. its axes line up with its parent's axes. But we can supply a custom transformation matrix we create ourselves.
SketchUp uses transformation matrices to define the position, rotation and scaling (and shearing) of any group or component. You can think of them as a coordinate system (modeling axes) relative to another coordinate system, or as a movement in space.
I drew my books with the spine as the front, i.e. towards negative Y. For this orientation we can use the book's negative Z axis for the text's X axis, the book's X axis for the label's Y axis and the book's Y axis for the label's Z axis. To help visualize it, you can manually move and rotate the group into the desired position, open it and compare its new axes to the book's axes.
model = Sketchup.active_model
model.start_operation("Label Books", true)
# Layers#add either creates a new Tag or returns the existing one by that name.
tag = model.layers.add("Book labels")
model.selection.each do |entity|
entities = entity.definition.entities
old_label = entities.find { |e| e.layer == tag }
old_label.erase! if old_label
transformation =
Geom::Transformation.axes(ORIGIN, Z_AXIS.reverse, X_AXIS, Y_AXIS)
label = entities.add_group
label.transformation = transformation
label.entities.add_3d_text(entity.definition.name, TextAlignLeft, "arial")
label.layer = tag
end
model.commit_operation
Now the orientation should be good but the position still off. Let's center the text on the spine of the book with another transformation. For this we need to know the size of the text, so we'll move the transformation code down below the code generating the 3d text.
model = Sketchup.active_model
model.start_operation("Label Books", true)
# Layers#add either creates a new Tag or returns the existing one by that name.
tag = model.layers.add("Book labels")
model.selection.each do |entity|
entities = entity.definition.entities
old_label = entities.find { |e| e.layer == tag }
old_label.erase! if old_label
# I drew my books with the spine as the front, meaning X extent is thickness.
book_thickness = entity.definition.bounds.width
# BoundingBox#depth is the Z extent, usually representing height.
book_height = entity.definition.bounds.depth
label = entities.add_group
label.entities.add_3d_text(entity.definition.name, TextAlignLeft, "arial")
label.layer = tag
# add_3d_text draws the text on the local X-Y plane
# BoundingBox#height is the Y extent, which here is the text height.
text_height = label.definition.bounds.height
text_length = label.definition.bounds.width
text_center = Geom::Point3d.new(book_thickness / 2, 0, book_height / 2)
label.transformation =
Geom::Transformation.axes(text_center, Z_AXIS.reverse, X_AXIS, Y_AXIS) *
Geom::Transformation.translation([-text_length / 2, -text_height / 2, 0])
end
model.commit_operation
For a complex positioning like this it can make sense to create multiple transformations, as opposed to do everything in one step. First we create one with the origin on the center of the spine and axes directions representing our desired orientation. Then we can multiply it with a translation that compensates for the text group not being centered on its local origin.
Transformations are non-commitive, i.e. it matters in what order you make them. Moving something 1 m to the right and then rotating 90 degrees around the origin yields a different result than first rotating it 90 degrees around the origin and then moving it 1 m to the right.
You could try to reason about the order to get the desired result but you can also experiment and move the code around until it works. With scripting inside of SketchUp there is a very quick feedback loop.

If you've tried this on a few components, the text is hopefully centered and correctly oriented on all, but may be too big or too small. For this we can add a scaling transformation to the text. To start, let's just scale by some arbitrary factor to confirm it scales around the right point.
label.transformation =
Geom::Transformation.axes(text_center, Z_AXIS.reverse, X_AXIS, Y_AXIS) *
Geom::Transformation.scaling(2) *
Geom::Transformation.translation([-text_length / 2, -text_height / 2, 0])
When the scaling is placed between the other transformations, we only have to pass the scaling factor and it defaults to scaling around the local origin. If we change the order of the transformations, we may need to also pass in the text_center, to scale around these coordinates.
For displaying the book labels, I wanted the text to be as big as it can while still being nicely contained on the spine. For this we need to calculate what scale factor to use to fill up the spine both vertically and horizontally, and then pick the smallest of the two values, so the text doesn't overflow in either of them. Multiplying by a value slightly below 1 gives us a nice margin, so the ext doesn't touch the edges of the spine.
scale = [
book_thickness/text_height,
book_height/text_length,
].min * 0.9
All combined:
model = Sketchup.active_model
model.start_operation("Label Books", true)
# Layers#add either creates a new Tag or returns the existing one by that name.
tag = model.layers.add("Book labels")
model.selection.each do |entity|
entities = entity.definition.entities
old_label = entities.find { |e| e.layer == tag }
old_label.erase! if old_label
# I drew my books with the spine as the front, meaning X extent is thickness.
book_thickness = entity.definition.bounds.width
# BoundingBox#depth is the Z extent, usually representing height.
book_height = entity.definition.bounds.depth
label = entities.add_group
label.entities.add_3d_text(entity.definition.name, TextAlignLeft, "arial")
label.layer = tag
# add_3d_text draws the text on the local X-Y plane .
# BoundingBox#height is the Y extent, which here is the text height.
text_height = label.definition.bounds.height
text_length = label.definition.bounds.width
scale = [
book_thickness/text_height,
book_height/text_length,
].min * 0.9
text_center = Geom::Point3d.new(book_thickness / 2, 0, book_height / 2)
label.transformation =
Geom::Transformation.axes(text_center, Z_AXIS.reverse, X_AXIS, Y_AXIS) *
Geom::Transformation.scaling(scale) *
Geom::Transformation.translation([-text_length / 2, -text_height / 2, 0])
end
model.commit_operation
This is already a useful snippet, but we can give it a bit more polish. If the selection contains anything that doesn't have a definition, e.g. a face or an edge, the current script raises an exception. Instead of manually filtering the selection, we can make the script more forgiving and just skip these entities by adding this line in the beginning of the loop.
next unless entity.respond_to?(:definition)
To streamline the usage even more, we can skip everything that is not a book. This prevents the labels from accidentally being added to any decorative objects in the shelf or even the shelf itself. For this we can define that each book component's definition name must be prefixed with "Book". Doing so also makes the Components and Outliner panels more organized.
next unless entity.definition.name.start_with?("Book ")
We can also tweak the 3D text we generate to exclude this prefix.
entity.definition.name.delete_prefix("Book ")
A script like this has served me well for years, but the chances other people have the exact same use case is quite slim. To make a full fledged extension out of this, you'd typically first want to generalize it.
The script makes several assumptions, such as the text's placement and orientation, as well as tag name and component name prefix. Even the margin is an arbitrary number. These could be exposed as user settings and asked from a UI.inputbox. The arbitrary number and strings are the easiest to expose whereas the orientation and precision may need some branching logic or more generalized formulas.
Another approach is to accept the tool is highly specialized and use strict drawing standards in your organization. However the cost of documenting, communicating and educating such drawing standards could be higher than generalizing the tool.
Next Step
With snippets like these you can quickly adjust and customize on the fly. Maybe something made a lot of sense in your head but wasn't that useful in practice. Maybe you get new, better ideas as you use your scripts for an actual project. Once you are happy with the functionality, you can wrap the chunks of code into methods, add menu entries or toolbar buttons and properly package it as an extension.
Hopefully these examples gave you some ideas to continue your developer journey. Look into your workflows and your needs, and there often is something that can be enhanced by a short snippet.