Moving +1000 trees to a terrain in Sketchup with Ruby script

Normal day at the ENGworks office. We were assigned to place 1000+ trees components over a sloped terrain surface in Sketchup.

A lot to move…

The good old “Move + cursor Up” to snap to a Z point is useful, but after the tree number 50th you start feeling the task is titanic.

Since a its beginnings, Sketchup support Ruby scripting and had a vast collection of plugins created by the community – now Extension Warehouse. With this in mind, we started looking for add-ins that could do this simple task for use.

Surprisingly for us we couldn’t find any solution to our specific problem performing a batch snapped movement and that’s when we then remember the software came with a basic Ruby interpreter built in. The solution to our problems

[…] we then remember the software came with a basic Ruby interpreter built in. The solution to our problems.

A little google pointed us to a Raytrace function that makes exactly what we need.

Important note,  we need to define the name of the surface as “terrain” in order to filter it from the rest of the elements and selected all elements we wanted to move (surface included)

After that we executed the following code directly from the Ruby interpreter.

The Code

#Description: Lift objects to terrain
#Author: Pablo Derendinger
#Company: ENGworks


# Get current selection
model = Sketchup.active_model
selection = model.selection

#List to save elements to be mover
element = Array.new

#Placeholder for the terrain surface
surface = nil

# For loop to detect object named "terrain" from selection list and
#save it to surface variable
for e in selection
    if e.name != "terrain"
        element.insert(0, e)
    else
        surface = e
    end
end

#Raytrace function found in
#https://sketchucation.com/forums/viewtopic.php?f=180&t=52714
# Casts a ray through the model and return the first thing that the # ray hits.
  # _T2_ : type 2 checks all, but the given entities.
  # @param [Array<entity>] ents An array of entities to ignore.
  # @param [Geom::Point3d, Array] point Ray position.
  # @param [Geom::Vector3d, Array] vector Ray direction.
  # @param [Boolean] chg Whether to consider hidden geometry.
  # @return [Array, nil] A ray result:
def raytest_t2(ents, point, vector, chg = false)
    chg = chg ? true : false
    unless ents.is_a?(Array)
      ents = ents.respond_to?(:to_a) ? ents.to_a : [ents]
    end
    entIDs = Hash[ents.map {|e| [e.entityID, 1]}]
    hit = nil
    while true
      hit = Sketchup.active_model.raytest(point, vector, !chg)
      break unless hit
      return hit unless entIDs[hit[1][0].entityID]
      point = hit[0]
    end
    nil
  end

#Move elements to point
  # @param [entity] element Element to move
  # @param [Geom::Point3D] NewOrigin New component location 
def move(element, NewOrigin)
    # from move element to point
    element.move! NewOrigin
end

#For loop to 
for e in element
    #get element origin
    origin = e.transformation.origin
    #Return point of intersection between raytrace and surface
    hit = raytest_t2(e,origin,[0,0,1])
    #Move element to point
    move(e, hit[0])

end

What it does?

First, we got a list of all elements in the selection.
We filtered the elements by name to detect the surface and create a list with the remaining elements.
Functions to Raytrace and Move were defined and executed on each one of the elements pulling them up ( 0,0,1 vector = Z coordinate ) to the point were they meet the surface.

With this small code we saved ours on a repetitive task

The result? All trees placed at the right elevation over the terrain.

Disclaimer: I’m not a Ruby developer and this was the first time facing it so the code may be ugly but I found my self liking its python-ish way of coding. 

Leave a Reply

Your email address will not be published. Required fields are marked *