# Creating multiple holes using Ruby API causes slowness

#1

I am trying to write a Ruby program that needs to create several holes. I start with a wide flat box (a sheet of plywood) and create hundreds of holes in that box using the Group.subtract method.

This gets me the result I want (i.e. the right holes are getting created in the right places), but as I add more holes, the run time is increasing dramatically. Are there any tips or tricks that I can use to reduce the run time for my program?

#2

can you add a working sample of the code your using?

it a bit hard to guessā¦

john

#3

Here is my code. Just copy and paste into the Ruby Console:

``````class Playfield
def initialize()
@width = 20.25
@depth = 42.0
@thickness = 17.0/32.0
@wall_thickness = 0.5
@wall_height = 1.125
end

def rot(degrees)
Geom::Transformation.rotation(Geom::Point3d.new, Geom::Vector3d.new(0, 0, 1), degrees.degrees)
end

def trans(x, y, z)
Geom::Transformation.translation(Geom::Vector3d.new(x, y, z))
end

def left()
Geom::Transformation.new
end

def right()
Geom::Transformation.scaling(-1, 1, 1)
end

def create_base()
create_floor()
create_wall(0, 73.0/16.0, @wall_thickness, @depth - @wall_thickness)
create_wall(0, @depth - @wall_thickness, @width, @depth)
create_wall(@width - @wall_thickness, 17.0/4.0, @width, @depth - @wall_thickness)
create_wall(@width - @wall_thickness - 11.0/8.0 - @wall_thickness, 7.5, @width - @wall_thickness - 11.0/8.0, 18.0)
end

def create_wall(x1, y1, x2, y2)
# TODO: Create screw holes
entities = Sketchup.active_model.active_entities.add_group().entities

pt1 = [x1, y1, 0.0]
pt2 = [x1, y2, 0.0]
pt3 = [x2, y2, 0.0]
pt4 = [x2, y1, 0.0]
new_face = entities.add_face pt1, pt2, pt3, pt4
new_face.pushpull -@wall_height
end

def create_floor()
@floor = Sketchup.active_model.active_entities.add_group()
entities = @floor.entities

pt1 = [0.0, 0.0, 0.0]
pt2 = [@width, 0.0, 0.0]
pt3 = [@width, @depth, 0.0]
pt4 = [0.0, @depth, 0.0]
face = entities.add_face pt1, pt2, pt3, pt4
face.pushpull @thickness
end

def add_hole_from_edges(hole, edges)
face = hole.entities.add_face edges
face.pushpull @thickness
@floor = hole.subtract @floor
end

def create_circular_hole(t, r)
hole = Sketchup.active_model.active_entities.add_group()
entities = hole.entities

centerpoint = Geom::Point3d.new
# Create a circle perpendicular to the normal or Z axis
normal = Geom::Vector3d.new 0,0,1
edges = entities.add_circle t * centerpoint, normal, r

add_hole_from_edges hole, edges
end

def join_arcs(entities, arcs)
edges = []
(0 .. arcs.length - 2).each do |i|
edges += entities.add_edges arcs[i].last.end, arcs[i+1].first.start
edges += arcs[i]
end
edges += entities.add_edges arcs.last.last.end, arcs.first.first.start
edges += arcs.last
return edges
end

def create_round_ended_hole(t, h, w)
hole = Sketchup.active_model.active_entities.add_group()
entities = hole.entities

centerpoint = Geom::Point3d.new 0, w / 2, 0
# Create a circle perpendicular to the normal or Z axis
normal = Geom::Vector3d.new(0,0,1)
xaxis = t * Geom::Vector3d.new(1,0,0)

bottom_arc = entities.add_arc t * centerpoint, xaxis, normal, w/2.0, 180.0.degrees, 360.0.degrees
top_arc = entities.add_arc t * trans(0, h - w, 0) * centerpoint, xaxis, normal, w/2.0, 0.0.degrees, 180.0.degrees

add_hole_from_edges hole, join_arcs(entities, [bottom_arc, top_arc])
end

def add_ball_trough(t)
hole = Sketchup.active_model.active_entities.add_group()
entities = hole.entities

t2 = t * rot(29.2)
normal = Geom::Vector3d.new(0,0,1)
xaxis = t2 * Geom::Vector3d.new(1,0,0)

right_arc = entities.add_arc t2 * Geom::Point3d.new, xaxis, normal, 5.0/8.0, -90.0.degrees, 90.0.degrees
top_arc = entities.add_arc t2 * Geom::Point3d.new(-33.0/4.0, 7.0/16.0, 0.0), xaxis, normal, 3.0/16.0, 90.0.degrees, 180.0.degrees
bottom_arc = entities.add_arc t2 * Geom::Point3d.new(-33.0/4.0, -7.0/16.0, 0.0), xaxis, normal, 3.0/16.0, 180.0.degrees, 270.0.degrees

add_hole_from_edges hole, join_arcs(entities, [right_arc, top_arc, bottom_arc])
end

def create_pilot_hole(t)
# TODO: pilot holes should not be full depth
create_circular_hole(t, 1.0/32)
end

def create_t_nut_hole(t)
create_circular_hole(t, 7.0/64.0)
end

def create_lamp_hole(t)
create_circular_hole(t, 0.25)
end

def install_component(t, component)
#        component = Sketchup.active_model.definitions.load "Z:\\home\\peter\\MEGA\\Pinball\\SketchUp\\" + component + ".skp"
#        Sketchup.active_model.active_entities.add_instance(component, t)
end

def add_post(t)
create_pilot_hole(t)
install_component(t, "Star_Post_1-1'16_-03-8319-13")
end

def add_rollover_switch(t)
install_component(t * trans(0, 0, -@thickness), "Rollover_Switch_and_Bracket_A-12688")
create_round_ended_hole(t, 25.0/16.0, 3.0/16.0)
create_pilot_hole(t * trans(31.0/64.0, -29.0/64.0, 0))
create_pilot_hole(t * trans(31.0/64.0, -53.0/64.0, 0))
end

def add_slingshot(t)
create_round_ended_hole(t, 1.0, 1.0/2.0)
create_circular_hole(t * trans(-1.0, 3.0/8.0, 0.0), 1.0/4.0)
create_circular_hole(t * trans(1.0, 3.0/8.0, 0.0), 1.0/4.0)
end

def add_flipper_constellation(t, side)
# Flipper drill template
install_component(t * side * trans(0, 0, -@thickness), "Flipper\ Assy\ -\ Williams\ A-15205\ \(Left\)")
[-17.0/32.0, -5.0/32.0, 89.0/32.0, 101.0/32.0].each do |x|
[-17.0/8.0, 43.0/32.0].each do |y|
create_pilot_hole(t * side * trans(x, y, 0.0))
end
end

# Flipper bat
create_circular_hole(t * side, 0.25)
install_component(t * side * rot(145.0), "flipper")

# Inlane guide
t2 = t * side * trans(-2.137, 1.563, 0.0) * rot(325)
create_pilot_hole(t2)
create_pilot_hole(t2 * trans(-13.0/8.0, 0, 0))
create_pilot_hole(t2 * trans(13.0/8.0, 0, 0))
create_pilot_hole(t2 * trans(-131.0/32.0, 89.0/32.0, 0)) # TODO: This hole isn't perfect
install_component(t2 * trans(0.0, 0.0, 0.53125), "Inlane_williams_plastic")

# Inlane switch
add_rollover_switch(t * side * trans(-3.142, 4.406, 0) * side)

# Outlane switch
add_rollover_switch(t * side * trans(-4.590, 4.406, 0) * side)

t3 = t * side * trans(-1.654, 5.030, 0.0) * rot(291.2)
add_slingshot(t3 * side)
add_post(t3 * trans(1.919, 0.200, 0.0))
add_post(t3 * trans(0.596, -0.696, 0.0))
add_post(t3 * trans(-0.396, -0.425, 0.0))
add_post(t3 * trans(-1.884, 0.339, 0.0))
end

def add_guide(t)
install_component(t * trans(0, 0, 1.25) * rot(90), "Lane_Guide_03-8318-25")

create_lamp_hole(t)
add_post(t * trans(0, 1.25/2, 0))
add_post(t * trans(0, -1.25/2, 0))
end

def add_pop_bumper(t)
# Ring and rod holes
create_circular_hole(t * trans(11.0/16.0, 0.0, 0.0), 3.0/16.0)
create_circular_hole(t * trans(-11.0/16.0, 0.0, 0.0), 3.0/16.0)

# Skirt shaft hole
create_circular_hole(t, 11.0/32.0)

# Lamp lead holes
t2 = t * rot(45)
create_circular_hole(t2 * trans(0.0, 11.0/32.0, 0.0), 3.0/16.0)
create_circular_hole(t2 * trans(0.0, -11.0/32.0, 0.0), 3.0/16.0)

# Coil bracket (hammer screw) holes
create_circular_hole(t * trans(0.0, 17.0/16.0, 0.0), 3.0/64.0)
create_circular_hole(t * trans(1.0, 7.0/16.0, 0.0), 3.0/64.0)
create_circular_hole(t * trans(-1.0, 7.0/16.0, 0.0), 3.0/64.0)

# Mounting pilot holes
create_pilot_hole(t * trans(5.0/16.0, 5.0/16.0, 0.0))
create_pilot_hole(t * trans(-5.0/16.0, -5.0/16.0, 0.0))

# Spoon switch bracket holes
create_pilot_hole(t * trans(-3.0/8.0, -29.0/16.0, 0))
create_pilot_hole(t * trans(-3.0/8.0, -35.0/16.0, 0))

# Drill template
install_component(t * trans(0, 0, -@thickness), "Pop\ Bumper\ Assembly\ Williams\ Bally")

# Pop bumper
install_component(t * rot(90) * trans(0, 0, 1.0/16.0), "pop-bumper")
end
end

Sketchup.active_model.active_entities.each { |it| Sketchup.active_model.active_entities.erase_entities it }

playfield = Playfield.new()
playfield.create_base()

playfield.add_ball_trough(Geom::Transformation.translation(Geom::Vector3d.new(281.0/16.0, 47.0/8.0, 0)))

playfield.add_flipper_constellation Geom::Transformation.translation(Geom::Vector3d.new(5.635, 6.701, 0)), playfield.left
playfield.add_flipper_constellation Geom::Transformation.translation(Geom::Vector3d.new(12.479, 6.701, 0)), playfield.right

x = 36.0
(0..2).each do
playfield.add_guide Geom::Transformation.translation(Geom::Vector3d.new(x/16.0, 585.0/16.0, 0))
x += 17
playfield.add_rollover_switch Geom::Transformation.translation(Geom::Vector3d.new(x/16.0, 573.0/16.0, 0))
x += 17
end
playfield.add_guide Geom::Transformation.translation(Geom::Vector3d.new(x/16.0, 585.0/16.0, 0))

playfield.add_pop_bumper Geom::Transformation.translation(Geom::Vector3d.new(39.0/16.0, 525.0/16.0, 0))
playfield.add_pop_bumper Geom::Transformation.translation(Geom::Vector3d.new(114.0/16.0, 522.0/16.0, 0))
playfield.add_pop_bumper Geom::Transformation.translation(Geom::Vector3d.new(72.0/16.0, 467.0/16.0, 0))

Sketchup.send_action("viewTop:")
Sketchup.send_action("viewZoomExtents:")
``````

If I comment out the body of the `add_hole_from_edges` method, the code runs much faster, but creates no holesā¦

The goal of this program is to produce model of a pinball playfield that I can send to a CNC mill and produce an actual playfield.

#4

itās running at 129.545646 seconds on my mac [but could be made quicker]ā¦

oddly, one of the favourite things I have ever made was a bespoke pinball machine demonstrating the human digestive system for a childrenās museumā¦

the ball was bounced around the in the lower intestine before spiralling down into a toilet pan when it got past the flippersā¦

Iāll have a look at the code over the weekend if I get timeā¦

john

#5

Well the first issue is that none of your geometry creation is wrapped in an undo operation (which has the UI refresh suppressed.)

See:

#6

Is there some suggestion about how or where I should use these undo methods? I tried wrapping the script with a start/commit but that only dramatically increased the run time.

#7

Iāve done some analysis by commenting out certain parts of the code. If I comment out everything after the ball trough. the code runs in 0.02s. If I add one flipper constellation (it doesnāt matter which) the run time increases to about 4 seconds. If I add the second constellation the run time increases to 26 seconds.

I really think that the run time increases disproportionately with the number of individual subtract operations. Is there any way that I could ācollectā all the holes in a data structure and perform only one subtract?

#8

Did you try by setting the second argument (`disable_ui`) to `true`?

#9

Basically the start and commit are block opener and block closer calls used like a `begin` and `end`. (I personally always indent the code in between to show itās a block of code wrapped within one undo operation.)

Only actual model entity creation (or modification) code statements need be wrapped within the undo block. Often at the beginning of methods, there may be code that runs to determine IF some model geometry needs to be changed or created. When not, a conditional expression causes a ābailoutā (early `return`) from the method, before a call to a block of undoable code.

#10

So, I carved the code down a bit, so now it only builds the top left most quadrant (pop bumpers and lane guides) as a test.

The code without calls to the undo methods runs in 26 seconds.

Then I added:

``````Sketchup.active_model.start_operation('Draw item', true)
``````

before, and

``````Sketchup.active_model.commit_operation
``````

after drawing each item (the floor, each pop bumper, each post, each lane guide, and each rollover switch).

and the runtime went up to 47 seconds.

I am not worried about being able to undo anything. The purpose of the code is to generate a playfield based on a ruby description.

I am pretty sure it has to do with the holes, and the work that SketchUp has to do in order to figure out their geometry. If I comment out the subtract operation, the code runs in 0.23 of a second.

#11

The start and commit operation is mean to be at the beginning and end of your bulk operation. The disable_ui flag will suppress a lot of the notifications that goes to the UI that would otherwise slow things down.