Trying to manipulate an object in a model, but the operation happens on the entire scene

I am using .rb script to manipulate a scene in SketchUp. The script works fine in older version (SketchUp Pro 2013), but when trying to port it to SketchUp Pro 2016, it’s not behaving correctly.

Best guess, is that in the newer version of SketchUp pro, the script is operating on the full model/scene rather than just specific items.

Here’s the original code, which works find in SketchUp Pro 2013, but doesn’t work in SketchUp Pro 2016

 def array_columns()
	model = Sketchup.active_model
	entities = model.active_entities

	rotation_center = Geom::Point3d.new [140,130,0]
	rotation_vector = Geom::Vector3d.new [0,0,1]
	current_angle = 30

	t1 = Geom::Transformation.rotation rotation_center,rotation_vector,current_angle.degrees
	t2 = Geom::Transformation.rotation rotation_center,rotation_vector,(current_angle += 30).degrees 
	t3 = Geom::Transformation.rotation rotation_center,rotation_vector,(current_angle += 30).degrees 
	t4 = Geom::Transformation.rotation rotation_center,rotation_vector,(current_angle += 30).degrees 
	t5 = Geom::Transformation.rotation rotation_center,rotation_vector,(current_angle += 30).degrees 
	t6 = Geom::Transformation.rotation rotation_center,rotation_vector,(current_angle += 30).degrees 
	t7 = Geom::Transformation.rotation rotation_center,rotation_vector,(current_angle += 30).degrees 
	t8 = Geom::Transformation.rotation rotation_center,rotation_vector,(current_angle += 30).degrees 
	t9 = Geom::Transformation.rotation rotation_center,rotation_vector,(current_angle += 30).degrees 
	t10 = Geom::Transformation.rotation rotation_center,rotation_vector,(current_angle += 30).degrees 
	t11 = Geom::Transformation.rotation rotation_center,rotation_vector,(current_angle += 30).degrees 
	

	entities.each { |entity| 
    		if entity.class == Sketchup::Group
    			src_group = entities.add_group entity.to_a
			dest_group1 = src_group.copy
			finalgroup1 = dest_group1.transform! t1
			dest_group2 = src_group.copy
			finalgroup2 = dest_group2.transform! t2
			dest_group3 = src_group.copy
			finalgroup3 = dest_group3.transform! t3
			dest_group4 = src_group.copy
			finalgroup4 = dest_group4.transform! t4
			dest_group5 = src_group.copy
			finalgroup5 = dest_group5.transform! t5
			dest_group6 = src_group.copy
			finalgroup6 = dest_group6.transform! t6
			dest_group7 = src_group.copy
			finalgroup7 = dest_group7.transform! t7
			dest_group8 = src_group.copy
			finalgroup8 = dest_group8.transform! t8
			dest_group9 = src_group.copy
			finalgroup9 = dest_group9.transform! t9
			dest_group10 = src_group.copy
			finalgroup10 = dest_group10.transform! t10
			dest_group11 = src_group.copy
			finalgroup11 = dest_group11.transform! t11

		break
		end
	}
	
	UI.messagebox("Array columns completed")
end

After changing this section

entities.each { |entity| 
		if entity.class == Sketchup::Group
			src_group = entities.add_group entity.to_a

To this (based on the this thread Move all entities to new group)

entities.each { |entity| 
	if entity.class == Sketchup::Group
		src_group = model.entities.add_group(entities.to_a)

Here’s what it looks like with the modification to the code:

I found that the that the columns will are now placed around the target layout, but the entire scene in the model is duplicated. I only need the columns to be filled in.

I have been reading through the API documentation, but I haven’t been able to figure out where I’m going wrong.

Any assistance is appreciated.

(1) Why call #to_a() upon a group entity ? This is a standard Ruby inherited method of collection type classes. A Sketchup::Group is an object type class, but it does “own” an entities collection which does inherit a #to_a() method.

Not sure what you are trying to do, as you are using confusing reference names:

# grep() is a fast filter method that returns a
# separate array from the original collection:
groups = entities.grep(Sketchup::Group)

groups.each {|group|
  src_group = entities.add_group( group.entities.to_a )

(2) Why is there a break statement at the bottom of the iterator block ? The block will only run once.

(3) Ruby uses 2 space indents. Your 8 space indents make your code hard to read on the forums.

(4) Can you explain what you are really trying to do, and post the SKP files for before and after, in this thread, please ?

1 Like

@DanRathbun

Dan, thanks for the reply

  1. I’ll be honest, I have no idea. I was asked to move this code forward to the 2016 app, and this is the first time I have ever played with Ruby.

  2. Same as #1. I get what you are saying, so I’ll pull that break out.

  3. Ok, victim of the cut and paste from my editor. Sorry about that.

  4. Sure. I have added a zip file which contains the before and after .skp files from SketchUp Pro 2013, which works as expected with that original code in my first post. I also included the before file from SketchUp Pro 2016, but the ‘after’ that matches the above screenshot is over the forum’s upload size, so I wasn’t able to include it.

columns.zip (2.4 MB)

The intent is this:
-Open the 'gazebo_one_column.skp file in SketchUp Pro 2016
-Copy the two columns in the skp, then paste them at the specified coordinates counterclockwise from the previous set of columns
-Repeat until the circle is filled in.

On SU 2013, it works, on SU 2016, I get a result that matches the screenshot from my first post.

I looked at the model(s), and I can see one main modeling mistake. The group is composed of two columns made of primitives.

Instead, each column needs to be it’s own group (or component.)

And then you place instances of those definitions, in two loops. One loop for the outer circle of 12, another for the inner circle of 6.

Also, you only need one (30 degree) rotational transformation object. You just apply it multiple times to the column instances as needed. (And will also use the 2 start columns’ translational transforms.)
For example, you start with one column in place. Therefor your “outer column” loop will have 11 steps:

for i in 1..11 do

You insert a new instance at the same place as column one using it’s transform, and then immediately apply the 30 degree rotational transform i number of times

Okay the following code works. You need to use my cleaned up model.

  • all primitives moved to “Layer0
  • columns are separate groups, each is named (without a number.)
  • all extra stuff has been moved to hidden layers

You select the inner and outer starting columns, and the gazebo perimeter circle, then run do_array()

gazebo_one_column_before2013_cleaned_up.skp (1.6 MB)

gazebo_one_column_before2016_cleaned_up.skp (1.6 MB)

Notice how afterward all the column instances are numbered and are moved to the original’s layer.

array_columns.rb (3.3 KB)

# encoding: UTF-8

  def get_center(
    selected_edge  # the gazebo circle
  )

    if selected_edge.is_a?(Sketchup::Edge)
      circle = selected_edge.curve
    else
      mod = Sketchup.active_model
      edges = mod.entities.grep(Sketchup::Edge)
      # Search the edges for one that is a curve segment,
      #  and whose angle of sweep equals 2*Pi radians.
      found = edges.find {|e|
        e.curve && (e.curve.end_angle - e.curve.start_angle == (Math::PI*2) ) 
      }
      circle = found ? found.curve : nil
    end

    if circle.is_a?(Sketchup::ArcCurve)
      gazebo_center = circle.center
    else
      UI.messagebox("Could not find Gazebo circle !")
      return false
    end

    return gazebo_center

  end ### get_center()

  def get_column_definitions(
    outer_column, # ref to group / component instance
    inner_column  # ref to group / component instance
  )
    
    deflist = Sketchup.active_model.definitions

    if outer_column.respond_to?(:definition)
      outer_definition = outer_column.definition
      inner_definition = inner_column.definition
    else # older version: no group.definition method:
      outer_definition = deflist.find {|d| d.instances.include?(outer_column) }
      inner_definition = deflist.find {|d| d.instances.include?(inner_column) }
    end

    return outer_definition, inner_definition

  end ### get_column_definitions()

  def array_columns(
    outer_column,  # ref to group / component instance
    inner_column,  # ref to group / component instance
    selected_edge  # the gazebo circle
  )

    rotation_center = get_center(selected_edge) # Geom::Point3d.new [140,130,0]
    rotation_vector = Z_AXIS # globally defined by the SketchUp API
    current_angle = 30
    
    outer_definition, inner_definition = get_column_definitions(outer_column,inner_column)
    
    modl = Sketchup.active_model
    ents = modl.active_entities
    
    tr = Geom::Transformation.rotation(
      rotation_center,
      rotation_vector,
      current_angle.degrees
    )
    
    begin
      #
      modl.start_operation("Array Columns",true)
      #
      ###
        #
        t = outer_column.transformation
        for i in 1..11
          inst = ents.add_instance(outer_definition,t)
          i.times { inst.transform!(tr) }
          inst.layer= outer_column.layer
          inst.name= "#{outer_column.name} #{i+1}"
        end
        outer_column.name= "#{outer_column.name} 1"

        t = inner_column.transformation
        for i in 1..5
          inst = ents.add_instance(inner_definition,t)
          i.times { 2.times { inst.transform!(tr) } }
          inst.layer= inner_column.layer
          inst.name= "#{inner_column.name} #{i+1}"
        end
        inner_column.name= "#{inner_column.name} 1"
        #
      ###
      #
      modl.commit_operation
      #
    end

  end ### array_columns()

  def do_array( sel = Sketchup::active_model.selection )
    edge = sel.find {|e| e.is_a?(Sketchup::Edge) }
    outer_column = sel.find {|e|
      e.respond_to?(:name) && e.name =~ /\A(Outer Column)/ 
    }
    inner_column = sel.find {|e|
      e.respond_to?(:name) && e.name =~ /\A(Inner Column)/
    }
    array_columns(outer_column,inner_column,edge)
  end ### do_array()

@DanRathbun

Great, will give this a try and report back.

@DanRathbun

Got it working. Thank you.

Now I just need to figure out how to automate selecting the three objects, then run do_array.

Thanks again for the help.

1 Like

You can select the two columns by name. The Enumerable mixin library module is also mixed into most all of the other API collection classes. So thye have find, find_all, grep, etc.

groups = ents.grep(Sketchup::Group)
outer_column = groups.find {|e| e.name =~ /\A(Outer Column)/ }
inner_column = groups.find {|e| e.name =~ /\A(Inner Column)/ }

I showed how to find a circle in the get_center() method example.

But usually you’d add this as a right_click command.
UI.add_context_menu_handler()
You would test the selection collection for the two columns and a selected circle, and if they exist, you add an item to the context menu.

Or you can write a Tool class that chooses the center point, and perhaps even places the two columns, using
model.place_component()
See also:
abstract Tool class

@DanRathbun

Ok that helps. I will keep playing with it.

Thanks again for the prompt assistance.

1 Like