How to create a component with existing entities in a component by ruby?


#1

Some of the examples I found show how to create new component and add new entities in it. The others create component with the method “ents.add_group(sel).to_component”. But i can’t get “selection” in a component. I can’t find the answer to teach me how to create a component with existing estities in a component. Could anybody tell me the answer? I’ve sought the answer for a day.


How do I copy a face with Ruby?
#2

Zephan

Here is some sample code that reaches into a selected Component and selects the internal entities. Hopefully this will help you figure it out. Be sure to select a single component first before running the code.

model = Sketchup.active_model
ss = model.selection

#Find the definition of the component instance ss[0]. ss[0] is the 1st selection in the selection set
definition = ss[0].definition

#Get the entities within the component definition.
internal_ents = definition.entities

#Clear the selection set
ss.clear

#Add an array of the internal entities to the selection set.
ss.add internal_ents.to_a

Hope this helps
CD


#3

ADD: Simplified copy method:

def copy_selected_component()

  mod  = Sketchup::active_model
  sel  = mod.selection
  ents = mod.active_entities
  
  return false if sel.empty?
  cget = sel.grep( Sketchup::ComponentInstance )
  return false if cget.empty?
  cmp1 = cget.first
  def1 = cmp1.definition

  copy = ents.add_instance( def1, IDENTITY )
  if copy.is_a?(Sketchup::ComponentInstance)
    return copy
  else
    return false 
  end

end # method

Original, more complex copy method:

def nest_selected_component(
  def_name = "My New Definition"
)

  mod  = Sketchup::active_model
  sel  = mod.selection
  ents = mod.active_entities
  
  return false if sel.empty?
  cget = sel.grep( Sketchup::ComponentInstance )
  return false if cget.empty?
  cmp1 = cget.first
  def1 = cmp1.definition
  
  def2 = mod.definitions.add( def_name )
  copy = def2.entities.add_instance( def1, IDENTITY )
  if copy
    copy.explode
  else
    return false
  end
  
  cmp2 = ents.add_instance( def2, cmp1.transformation )
  if cmp2.nil?
    return false
  else
    cmp2.glued_to= cmp1.glued_to unless cmp1.glued_to.nil?
    cmp2.casts_shadows= cmp1.casts_shadows
    cmp2.receives_shadows= cmp1.receives_shadows
    cmp2.layer= cmp1.layer
    cmp2.material= cmp1.material
    cmp2.name= cmp1.name
    if cmp1.attribute_dictionaries
      copy_attribute_dictionaries(cmp1,cmp2)
    end
    cmp2.hidden= cmp1.hidden
    cmp2.locked= cmp1.locked
    # cmp1.erase! # if you no longer need it.
    return cmp2
  end

end # method


def copy_attribute_dictionaries( source, target )
  unless source.is_a?(Sketchup::Entity) && target.is_a?(Sketchup::Entity)
    fail(TypeError,"objects of Sketchup::Entity (or subclass) expected",caller)
  end
  source.attribute_dictionaries.each {|dict1|
    dict2 = target.attribute_dictionary(dict1.name,true)
    dict1.each {|key,val|
      next if key.nil? || (key.is_a?(String) && key.empty?)
      dict2[key]= val
    }
    if dict1.attribute_dictionaries
      # since Dictionary is a Entity subclass, they can be nested.
      copy_attribute_dictionaries(dict1,dict2) # recursive call
    end
  }
end # method

EDIT:

(1)

comp.glued_to= cmp1.glued_to unless cmp1.glued_to.nil?

was, in error:

comp.glued_to= cmp1.glued_to unless cmp1.gluded_to.nil?

(2) Modified copy_attribute_dictionaries method to have two arguments: source and target.
Renamed internal variables to dict1 and dict2 for clarity.

(3) Renamed comp variable (in nest_selected_component method,) to cmp2 for clarity.


#4

Thank you very much !! It was the first time I get the professional reply!

I can get the selection of the internal entities, but can’t create the component in the selected component.
"definition.entities.add_group(ss).to_component"
The new component will appear near the origin
“definition.entities.add_3d_text(“name”, TextAlignLeft, “Arial”,false, false, 20.0, 0.0, 0, true, 0)”
But I can add a 3d text in the selected component and I don’t know why.

In fact, the exact problem that I want to work out is to copy a component instance which is a box to the origin. But the instance has been scaled. So I can only get the instance as big as the original definition of the instance.

So I try to creat a same box in the selected component so that I can mearsure the box with “bounding box” in the correct coordinate system. The selected component might be rorated so the bounding box have a bigger size.

Can you help me with that?

Please forgive my poor English expression or mistakes


#5

I really appreciate your detailed reply!
But I am sorry that I can’t understand all of it bacause I have just learned ruby for two weeks. I will try my best!
The exact problem I want to solve is in my reply to ChrisDizon. Hope you can also help me with that.
Thank you again!


#6

Instead of adding the selection to a group, add the selection to a new definition via DefinitionList.add and then add an instance via Entities.add_instance of the new definition to the origin.


#7

You can instead use the IDENTITY transform. It is referenced by a global constant, defined by the SketchUp Ruby API, during startup. So it is always available, in any scope. (I showed using it in my 1st example, above.)

Simplified example:

mod = Sketchup.active_model
def1 = mod.selection[0].definition
copy = mod.entities.add_instance( def1, IDENTITY )

You can also just use Geom::Transformation::new in place of IDENTITY, because the class constructor (new,) with no arguments, creates a new identify Transformation.
See API class documentation: Geom::Transformation::new()


ADD: Simplified copy method:

def copy_selected_component()

  mod  = Sketchup::active_model
  sel  = mod.selection
  ents = mod.active_entities
  
  return false if sel.empty?
  cget = sel.grep( Sketchup::ComponentInstance )
  return false if cget.empty?
  cmp1 = cget.first
  def1 = cmp1.definition

  copy = ents.add_instance( def1, IDENTITY )
  if copy.is_a?(Sketchup::ComponentInstance)
    return copy
  else
    return false 
  end

end # method

#8

ComponetInstances (and Groups, which are special ComponentInstances,) have bounding boxes that vary in size depending upon scaling AND rotation. Their bounding boxes are always aligned with the model axis so rotating an instance can increase a bounding box.

BUT, the definition of the group or instance always has a bounding box that is untransformed. (Ie, not subject to rotation or scaling.)

def_bbox = inst.definition.bounds

More about groups and their definition’s bounds:

Group instances have a wrapper (shortcut) method to definition.bounds

def_bbox = grp.local_bounds

… because the .definition method was only just added to the Sketchup::Group class for v2015.
The Group#local_bounds method was added in v7.0.

You can use a rescue modifier to prevent NoMethodError errors in older SketchUp versions when using :

def_bbox = grp.definition.bounds rescue grp.local_bounds

#9

I failed when I use the simplified example.
"mod = Sketchup.active_model
def1 = mod.selection[0].definition
copy = mod.entities.add_instance( def1, IDENTITY )

When I used the two visions of complex method, it returned “Nil result (no result returned or run failed)”


#10

When I try it at the command line, the code:

def1 = model.selection[0].definition

works as long as I have a group or component selected. If, while moving from another window back to Skechup’s main window, and I click inside Sketchup’s window, but I fail to click a drawing object, the selection tool will deselect everything. If I try the statement above at the Ruby console when nothing is selected, I’m seeing:

Error: #<NoMethodError: undefined method `definition' for nil:NilClass>
<main>:in `<main>'
SketchUp:1:in `eval

Your last few Ruby statements showed you are hard coding to index the first element in the “mod.selection” collection (i.e. “selection[0]”).
@DanRathbun included “return false if sel.empty?” to prevent accessing the selection if it is empty. Later on in his code he “greps” (i.e. searches) for ComponentInstance objects, and exits the method if there are none. If the selection is empty, or the selection does not include a component, his code exits returning false. Does your code evaluate the selection’s collection of entities before attempting to use it?


#11

Try the simplfied method named copy_selected_component() given in post:
http://forums.sketchup.com/t/how-to-create-a-component-with-existing-entities-in-a-component-by-ruby/18546/7

Do it at the standard Ruby Console.

I will not be debugging Alex’s “Ruby Code Editor” plugin.


#12

I tried in Ruby Console, and it failed.
My Sketchup vision is “15.1.106”.

I sincerely feel sorry to bother you for such a long time.


#13

The version in the ModelInfo dialog, is the file version, not the application version. (ie, it is the version that the file was saved as.)

The SketchUp application version is displayed in the Help > About dialog. OR you can type

Sketchup::version

… in the Ruby console, followed by enter. (It is an API module function call.)

The latest SketchUp 2015 versions are:

15.3.331 - Windows 64­-bit
15.3.330 - Windows 32­-bit
15.3.329 - OS X 64­-bit

ref: SketchUp Release Notes


(1) Please update SketchUp before reporting errors.

(2) Set End of Line characters to DOS/Windows before pasting into Ruby Console on Windows.
… OR save the file as a .rb file and use Ruby’s global load() method to load the file.


#14

If I go to the Ruby console and just type “return false” I see:

Error: #<LocalJumpError: unexpected return>
<main>:in `<main>'
SketchUp:1:in `eval'

The return statement is only recognized if it is inside a method. I’m seeing an error message because I didn’t put it inside of a method. I don’t see any “end” statement defining the end of a method definition in your snippet of code. When I paste the entire method “copy_selected_component” at once into the Ruby console, I see:

In your image, I’m not seeing the last line “end #method”. If it is missing, then you have not completed entering the method.
Select the method “copy_selected_component” that @DanRathbun provided, copy all of its text, paste it in the Ruby console and hit enter.
Before running the method, select a component. Then, to run the method, you type copy_selected_component at the Ruby console and hit enter.


#15

Thank you very much for your advice.

But in fact, I tested with the component selected as the picture showed. And after I copied all the text in “original, more complex copy method" and "simplified copy method” to Ruby Console and hit enter, nothing happened.


So I tried again without method definition, and the definition of my selected component appeared.

Today I updated my sketchup and save the "simplified copy method” as a rb file , loaded as a plugin and tried again. The situation didn’t change. I got the definition of my selected component.

Do I misunderstand some basic rules? I have tried for many times.


#16

the last line is needed as well for it to ‘run’…


def copy_selected_component
  mod  = Sketchup.active_model
  sel  = mod.selection
  ents = mod.active_entities

  return false if sel.empty?
  cget = sel.grep(Sketchup::ComponentInstance)
  return false if cget.empty?
  cmp1 = cget.first
  def1 = cmp1.definition

  copy = ents.add_instance(def1, IDENTITY)
  if copy.is_a?(Sketchup::ComponentInstance)
    return copy
  else
    return false
  end
end # method

copy_selected_component # CALL the method...

#17

Yes.

Bruce and John get it correct. Zephan you must copy the code as a method, and then execute the method by typing the method name in the console.


#18

Here is a non-method example. This example uses a mouse context menu command block:

UI.add_context_menu_handler do |popup|
  sel = Sketchup::active_model.selection
  if !sel.empty? && sel.single_object?
    if sel.first.is_a?(Sketchup::ComponentInstance)
      popup.add_item("Copy Selected Component to Origin") {

        Sketchup::active_model.active_entities.add_instance(
          sel.first.definition,
          IDENTITY 
        )

      }
    end
  end
end

Point to a component instance, right-click the mouse, and you should see the menu command at the bottom of the mouse context menu:


How do I copy a face with Ruby?
#19

Maybe I didn’t express what I want clearly.

The picture above shows what I want to achieve. I want to copy the component after it was scaled. The component appeared must be as big as the component I selected.

But I could only get the component as big as its definition.

Maybe my English is too bad to expressed clearly. I will read the reference you offered word by word. Thank you again for you patience.


#20

Sorry, not at a computer where I can write and test a code snippet, so I can only give you words right now.

What’s overlooked in the code examples given earlier is that you want to preserve the scaling that has been applied to the selected ComponentInstance. The IDENTITY Transformation applies no scaling to the definition. So, you want to get the xscale, yscale, and zscale out of the Transformation of the selection and use these to create a new scaling Transformation for use when you add_instance.