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

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!

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.

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

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

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)”

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?

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.

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.

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.

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.

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.

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...

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.

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:

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.

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.

Can’t you just use:

compo = model.selection[0] ### Assuming you have the one thing selected...
trans = compo.transformation
instance = model.active_entities.add_instance(compo.definition, trans)
### NOW move it to the origin.
translate = Geom::Transformation.translation(trans.origin.vector_to(ORIGIN))
model.active_entities.transform_entities(translate, instance)
### Finally it is now at [0,0,0] with the original scaling and rotation intact

If you want it unrotated but scaled, then as @slbaumgartner says you might be best placing a ‘pure’ instance at the Origin and then scaling it afterwards ?
The API does not give easy access to a transformation’s scaling, however, the DC code adds xscale/yscale/zscale methods, these are also limited because they are always a positive value, so a negative scaling when handing/mirroring is not handled. It’d be possible to extract that from the transformation matrix, but we are having enough problems with the easier stuff thus far… because axial scaling and axial rotations interact in complex ways, it becomes particularly mind-boggling - e.g. mirroring about several axes combined with axial rotations can cancel out and have you back where you started etc…

OK, finally we understand.

#3 : Scaled the same as selected, but unrotated at the origin:
UI.add_context_menu_handler do |popup|
  sel = Sketchup::active_model.selection
  if !sel.empty? && sel.single_object?
    obj = sel.first
    if obj.is_a?(Sketchup::ComponentInstance)
      popup.add_item("Copy Selected Component to Origin (Scaled)") {

        t = obj.transformation

        Sketchup::active_model.active_entities.add_instance(
          obj.definition,
          Geom::Transformation::scaling(
            ORIGIN,
            Geom::Vector3d.new(1,0,0).transform!(t).length,
            Geom::Vector3d.new(0,1,0).transform!(t).length,
            Geom::Vector3d.new(0,0,1).transform!(t).length
          )
        )

      }
    end
  end
end

@slbaumgartner
Thank you ! You are right and I just don’t know how to get the xscale, yscale, and zscale. Thanks to Dan Rathbun. He solve the problem.

@TIG
Thank you! And luckily the component that I use is usually a box,so I don’t have to deal with the mirroring problem.
BTW, It seems that “Sketchup.active_model.active_entities.transform_entities translate, instance” should be add to your code.

@DanRathbun
Thank you very much for being patient to solve my problem! You help me a lot. Thank you again!
It is hard to find the proper method in Sketchup API for me because I have wait for a long time when sketchup.com is loading. Is there any file of Sketchup API that I can download?

Thank you all, excellent dialog/ A+ lecture series. there should be some basic methods where the basics of whatcha do and most likely are gonna do (e…g) basic combo’s creating groups, components, from the lesser animls in this zoo (lines, faces, loops, edges, points…) is done with some eloquent methods, with a video, proforma file, basic fundies. since sketchup does not have the internl code it genertes to perform sequences e.g., like excel vbA taking the basic mouse driven combination of operations, e,g, select an area, and whatever in its “selected and outlined area” can become a group, with given handle ie, name vs definition… and/or a component. then simple methods to copy, move, rotate, reflect, follow me… What also would be nice if, a glossary of terms and concepts and videos e.g., loops, ruby is so good at dynamic method argument identification, why it doesnt convert the object forms which supply the data without much worry. I rhink the honchos who created the Sketchup paradigm deserves credit, but I think the dearth explaination has a psychological basis. , thank you much again all.