How to create a component definition without drawing?

To replicate a box, I made a group (entities.add_group), and then drew it (group.entities.add_face, pushpull). I then made it into a component with group.to_component. I then looped and made multiple instantiations with Transformation.new(x,y,z offsets) and entities.add_instance(component.definition, transformation).
But, my question is, can I make a component definition without first drawing the item? I guess I could delete the drawn box, or move it specifically, but it just seems inelegant. Iā€™d like to create the definition, and then use it multiple times, only drawing from the definition. Thanks.

I think you can do this by way of using the Ruby Console (from within SketchUp), or via text editor and loading your Ruby plugin creation back into SU.

Hereā€™s a link to get things started. . . Homepage | SketchUp Developer

more specific details will have to come from others who really know this stuff well. Iā€™m just beginning Ruby Scripting myself, soā€¦

Sure, see: DefinitionList.add()

ā€¦ then just draw to the definitionā€™s entities collection, instead of the modelā€™s.

Thanks. Checking out the syntax and examples in developer/docs, I think I have a version working, in that there are no superimposed items. The codeā€™s below. Is this a typical way to make and use multiply instantiated items?

# Create a component definition first, using DefinitionList's entities. Then draw for real into active's entities.
# First we pull in the standard API hooks.
    
    require 'sketchup.rb'
    
    n = 3
    s = 100
    w = 20
    model=Sketchup.active_model
    entities = model.entities
    definitions=model.definitions
    compdefinition=definitions.add "Block"
    group = compdefinition.entities.add_group
    face = group.entities.add_face [0,0,0],[w,0,0],[w,w,0],[0,w,0]
    face.pushpull -w
    component = group.to_component
    (0..n).each { |i|
        (0..n).each { |j|
            (0..n).each { |k|
                transformation = Geom::Transformation.new([i*s,j*s,k*s])
                componentinstance = entities.add_instance(component.definition, transformation)
            }
        }
    }

It appears that will work, though you have one level of nesting more than strictly necessary. Your compdefinition object already has an entities collection where you can directly add your face and pushpull it. Unless you have some other reason to want nesting, there is no need to create the group within compdefinition.entities.

ā€¦ unless you already have geometry in the collection, and you do not want the new stuff to intersect with the resident stuff (until youā€™re done and ready to explode the group.)

True, though he just created a brand-new ComponentDefinition, so in the given example there is nothing that the new stuff could interact with!

Check out the Integer class for more elegant iterators like:

4.times do |i|

or

0.upto(3) do |i|

The literal (0..n) creates a Range object.

The above line is not needed. Your creating a component inside the first component.

Also,ā€¦ I hope your executing your snippets inside some module namespace ?
It is a good habit to get into.
Just put ā€œmodule Dealā€ at the top, and ā€œendā€ at the bottom with no quotes of course.

Interesting, thanks. Yeah, I got the ā€œgroupā€ style from an example somewhere a ways back when I first looked into transformations, but I see your point. Iā€™ll try to fold this style in to my other script that is already drawing various boxes to build up some shelving/base units for a room. That script drew a few things in a flat coding style. Iā€™m currently trying to method-ize (def () ) some of the drawing efforts (e.g., shelfUnit, baseUnit) so I can easily instantiate multiple units at various positions. So Iā€™m trying to become more proficient in Rubyā€™s variable type and scoping, without using too many global ($var) variables. Some code excerpts below, to see if Iā€™m stuck in the old ā€œgotoā€/global variable days; Iā€™m trying to use standard object oriented styles, so I welcome any orienting/elegantizing suggestions.

# Get handles to our model and the Entities collection it contains.
$model = Sketchup.active_model
$entities = $model.entities
$group_Shelf_Unit=$entities.add_group

# Some global values
$thickness=0.75
. . .
. . .
def draw_shelfUnit (width, height, depth, numShelves)
  # Draw bottom
  ptFL=[0,0,0]
  ptFR=[width,0,0]
  ptRL=[0,depth,0]
  ptRR=[width,depth,0]
  new_face = $group_Shelf_Unit.entities.add_face ptFL, ptFR, ptRR, ptRL
. . .
  # Draw shelves
  space=height/(numShelves+1).to_f
  for shelf in 1..numShelves
    z=space*shelf
    ptFL=[$thickness,0,z]
    ptFR=[width-$thickness,0,z]
    ptRL=[$thickness,depth,z]
    ptRR=[width-$thickness,depth,z]
    new_face = $group_Shelf_Unit.entities.add_face ptFL, ptFR, ptRR, ptRL
. . .
end

draw_shelfUnit 48,60,12,5

Also donā€™t use $ global variables - especially with simple names like $thickness - others might have also ill-advisedly used those and you will one day get a clash thatā€™s hard to debugā€¦
Inside your own module - as Dan explained earlier - you can use @ variables - which will only apply inside that module.

2 Likes

Contrary to what you see as examples and extensions (some even written by the OEM folks in Boulder,) there is NEVER a good reason to use a global variable for the exclusive use of ONE plugin, or ONE author.

Global variables are for global use, by everyone, and are usually Ruby or environment functionality.

:point_right: [Template] Ruby Plugin main file :bulb:

1 Like

Hi Dan.
Iā€™m using a global variable for organize my submenu by author. In case of the user need some organization and download the menu organizator.
It is wrapped inside my own module.
This is bad? Because the variable is my own and unique. I guess.

module My_own_module
  module plugin2
    def self.this_is_a_definition
      ...
    end

    if global_variables.include?( :$cg_Author_menu ) && $cg_Author_menu
      cg_menu = $cg_Author_menu.add_submenu("My application")
    else
      cg_menu = UI.menu('Plugins').add_submenu("My application")
    end
  end    
end

Yes. Inside your toplevel module ā€¦

make it either a constant
CG_MENU ||= UI.menu("Plugins").add_submenu("Anme")

or a module var;
@@cg_menu ||= UI.menu("Plugins").add_submenu("Anme")

You can create a module function, inside your toplevel author module, to access the var (if you choose not to use a constant):

def self.submenu
  @@cg_menu
end

But you will have to ā€œsave upā€ all you UI::Command objects in a Hash and then build your ā€œAuthorā€ menu all at once, because the menu references get garbage collected between loading files.

ADD: This happens on the C-side of the SketchUp engine. The ruby reference might still hold an Sketchup::Menu instance or a integer id for a resource, but will be invalid. [Unless things have been fixed without my knowledge?]

1 Like

OK, I think Iā€™m following the suggested style, but I donā€™t see where my error is. An excerpt below, and the initial error line number is the method invocation. The module and def "end"s seem matched. Any thoughts? Thanks.

Error:
load ā€˜C:\Users\gkd\SketchupRubyScripts\shelfUnit3.rbā€™
Error: #NoMethodError: undefined method `create_component_definition_shelfUnitā€™ for Shelving:Module
C:/Users/gkd/SketchupRubyScripts/shelfUnit3.rb:95:in module:Shelving
C:/Users/gkd/SketchupRubyScripts/shelfUnit3.rb:6:in top (required)
main:in load
main:in main
-e:1:in eval
nil

# shelfUnit3.rb
#   Draw a single top shelf unit with shelves.
#   Make component definition first. Then multiply instantiate.
#   Put everything inside a Module and use @ variables.

module Shelving
  # First we pull in the standard API hooks.
  require 'sketchup.rb'

  # Get handles to our model and the Entities collection it contains.
  @model = Sketchup.active_model
  @entities = @model.entities
  @definitions=@model.definitions
  @componentDefinitionShelfUnit=@definitions.add "ShelfUnit"

  # Some global values
  @thickness=0.75

  def create_component_definition_shelfUnit (width, height, depth, numShelves)
    # Draw bottom
    ptFL=[0,0,0]
    ptFR=[width,0,0]
    ptRL=[0,depth,0]
    ptRR=[width,depth,0]
    new_face = @componentDefinitionShelfUnit.entities.add_face ptFL, ptFR, ptRR, ptRL
    if new_face.normal == [0,0,1]
      new_face.pushpull @thickness
    else
      new_face.pushpull -@thickness
    end
    . . . . .
  end

  create_component_definition_shelfUnit 48,60,12,5      (line 95)
  @transform1=Geom::Transformation.new([50,0,0])
  @entities.add_instance(@componentDefinitionShelfUnit,@transform1)
  @transform2=Geom::Transformation.new([150,0,0])
  @entities.add_instance(@componentDefinitionShelfUnit,@transform2)

end

youā€™ve got a space between Unit (width, try

create_component_definition_shelfUnit(width, height, depth, numShelves)

and also at line 95ā€¦

create_component_definition_shelfUnit(48,60,12,5)

john

Nope. use the template.

(1) Youā€™ve defined a very generically named module at the toplevel. It should be within an author module.
You can save an indent like this:

module Deal; end
module Deal::Shelving

(2) The error messages tell you exactly the error, the file, and the line number.
It cannot see the method you called.

Reason is that you only define instance methods in library modules, that will later be mixed into a class.

ADD: Or a class definition directly,ā€¦ duh!

In the template I show how to define instance methods INSIDE an anonymous singleton proxy class of the module. The block begins:
class << self
ā€¦ where self is the proxy object, which always refers to the module itself, inside a module.

ORā€¦ you could change the method to a module function, like:
def self.create_component_definition_shelfUnit( _args_ )
ā€¦ and then you need to call the method against itā€™s receiver, which is the module identifier outside the module, but can be referred to as self inside the module. IE make the call as:
self.create_component_definition_shelfUnit( _args_ )

:bulb:

I used the second style of method creation and invocation (ā€œself.ā€) and I now have a functioning script. I can make various component definitions and instantiate them and move them around. Once I get something that looks good, Iā€™d like to make up a parts list. I see that I might step through the modelā€™s entities, and if the entity is a component, look at each entity in the componentDefinition, and then output its size. But I donā€™t see how to easily get an entityā€™s size. For entity types, I see face, edge, etc., but all of mine are faceā€™s that have been pushpullā€™d. How might I get the length, width, and height of a pushpullā€™d face? Would I have to traverse an itemā€™s faces, edges, all_connected, etc., and compute my own L/W/H? Thanks.

We have been discussing recently these two subjects either here or in the child RubyAPI forum.

It is faster to iterate the modelā€™s definition list, and for each that is a certain type (group/image/or not) check if itā€™s instances collection has members, and if so, iterate those that do.

ā€¦ so within that looping youā€™d get the untransformed bounding box of the definition, and get the scaling factors of the instanceā€™s transformation and apply them to the dimensions of the BB.

So do a search here on BoundingBox. Or see what weā€™ve been discussing or the past week.