How to use a method to dynamically add to group

I am trying to add to a group using a definition and I am not sure what I am doing wrong. Maybe I need to create two separate groups and merge them. maybe the group should be a global variable? The first photo shows the first via with the second via commented out and the second shows when I try to run the same definition twice. It says that the main_face has been deleted. Shouldn’t it be recreated as it runs through the method a second time though?


SKETCHUP_CONSOLE.clear
def via(group,pts,thickness,lateral_extension=0,inversion=false) #,:mode=>nil)
  min_x = pts.map{|p| p[0]}.min
  max_x = pts.map{|p| p[0]}.max
  min_y = pts.map{|p| p[1]}.min
  max_y = pts.map{|p| p[1]}.max
  main_face = group.entities.add_face pts
  if main_face.normal.z = -1
    main_face.reverse!
  end
  for v in main_face.vertices do
    min_v = v.position
    break if min_v.x == min_x && min_v.y == min_y
  end
  main_face.pushpull thickness
  cut_vector1 = [-lateral_extension,0,thickness]
  cut_vector2 = [0,0,thickness]
  cut = group.entities.add_line(min_v,min_v+cut_vector1)
  cut2= group.entities.add_line(min_v+cut_vector2,min_v+cut_vector1)
  cut_edges = group.entities.grep(Sketchup::Edge)
  cut_edges.each(&:find_faces)
  cut.faces[0].followme main_face.edges
  if inversion == true
    center = group.bounds.center
    vector = Geom::Vector3d.new(1,0,0)
    angle = 180.degrees 
    transformation = Geom::Transformation.rotation(center, vector, angle)
    group.transformation= transformation
  end
  return group
end

ents = Sketchup.active_model.entities
etch = ents.add_group
z = 0
pts = [0,0,z], [5,0,z], [5,8,z], [0,8,z]
etch1 = via(etch,pts,10,6)
etch2 = via(etch,pts,10,6,true)

NEVER USE GLOBALS ! All of your code should run within your own namespace modules.

I see nothing in your code that directly references any group’s definition.
However, group.entities is really a shortcut for group.definition.entities.

Please be aware that SketchUp itself will “clean up” it’s DefintionList … removing any group definitions that have 0 instances. So at this point, you need to ask yourself if you should really be using component instances instead of groups.

The code indicates that the group is created outside the via() method. But the snippet never shows how this method is called (where the group is actually created.)

To answer the topic’s main question … if you have a reference to a valid group definition …
… and want to add another group instance …

# group1 is an existing instance of a group definition ...
active_ents = model.active_entities
gdef = group1.definition
group2 = active_ents.add_instance(gdef, [0,0,distance])

Note that an array of Numerics can be accepted by many of the API methods in place of a transformation object. (The array is converted internally by the API.)

Sorry, I meant methods not definitions. If I want to add a group, then add faces and extrude using a method the second run around the method says that the group has been deleted.

Also, I accidentally changed the method name at the bottom without changing it at the top. It was supposed to be etch at the bottom.

# group1 is an existing instance of a group definition ...
active_ents = model.active_entities
gdef = group1.definition
group2 = active_ents.add_instance(gdef, [0,0,distance])

Do you have any examples of how this is used? I am not really sure how this works.

What exactly is a definition?
Would adding group2 in the way you showed make it part of group1? Or would it still be it’s own seperate group?

Well then please edit your previous post to fix the error.

I cannot see where the group is being deleted. But as I said, SketchUp will delete groups because their definitions have no instances, and if it’s entities collection has no entities. So what we usually do is add a cpoint at the origin of a group entities to keep SketchUp from deleteing it if we have other stuff to do.

In your code the next few lines are creating numeric values and arrays of Numerics, instead of dealing immediately with the group object. You are giving SketchUp time to delete the group.

Secondly, you have local variables that are not within a method or module. If you are loading code from a file, Ruby will delete local variables at the top level ObjectSpace to prevent them from propagating into everyone else’s Module and Class objects.

Lastly get in the habit of using the active_entities instead of the top level model’s entities …

ents = Sketchup.active_model.active_entities

… unless you know that geometry should be at the top level.

A definition is the object that owns (ie has) the Entities collection and other Behavior properties that instances will use.

There are 3 kinds of instances.

  • Group
    Groups are hidden from the Component Inspector (Browser) “In Model” collection listing (ie the model’s DefinitionList, even though their defintions are a member of the list.) There are most often used when only one (or a few) instances is needed and it is not something that needs to be named or reused like a part or a window.
    Manually editing one group instance (of a definition that has multiple instances) in the GUI will cause SketchUp to uniqueify that instance by creating a new unique definition for it to use.

  • ComponentInstance
    Components are usually used when a modeler will insert many instances of a component into a model. Or when behavior needs to be controlled. Thier definitions are always listed in the Component Inspector (Browser) “In Model” collection listing.
    Editing ANY instance of a component (manually or via code) will affect and change ALL instances of that component, as it is changing the definition.

  • Image
    Images are basically an texture (from a file) applied to a face with a hidden material and bounded by edges. Extensions should not manipulate these objects without exploding them.

There are likely quite a few topics on the difference between a group and a component instance.


NO.

It would be a separate instance of the same group definition sharing the same entities.
Any change to the definition’s entities using code will affect both instances.

However, manually entering the editing context of a group instance when there are more than 1 instance will cause SketchUp to make that instance unique by cloning the definition and renaming it. Thereafter each group can have separate changes as they no longer share the same definition (and so have differing entities collections.)

Using Groups and Components via the Ruby API requires understanding some technical aspects of how they work that go beyond what a user needs to know when using the GUI. It is vital to learn the relationships between ComponentDefinition, ComponentInstance, and Group.

in a nutshell

  • A ComponentDefinition provides a sort of mini-model of a collection of geometry (it actually saves as a SketchUp model file with a few flags set). Its geometry is held in its own Entities collection. The coordinate values of those Entities are independent of the model’s coordinates, they are “abstract” or “local”. All of the ComponentDefinitions in a model are kept in its DefinitionList.
  • A ComponentInstance is a concrete placement of a copy of the mini-model into your model. It is located, oriented, and scaled from the mini-model using a Transformation that maps the ComponentDefinition’s local coordinates into the full model’s coordinates. Its Entities are still held separate from the model’s Entities (and from any other Component’s Entities), which is what prevents them from sticking to other geometry. The SketchUp engine takes care of presenting the instance at the appropriate place, orientation, and scale. Many people find it convenient to think of the ComponentDefinition as a pattern or template and a ComponentInstance as the result you get by spraying paint through it at a particular place on a drawing.
  • A Group is a special kind of ComponentInstance that has a flag telling SketchUp to handle it differently than a standard ComponentInstance. It also has some shortcut methods to access information from its ComponentDefinition without explicitly going through the definition method, though that will also work. @DanRathbun noted these differences in his post.

When a user opens a Component or Group for edit via the GUI, they are actually opening the mini-model and working within the ComponentDefinition’s local coordinates. But the GUI takes care of presenting the Entities in the correct location, orientation, and scale for that instance so that if necessary placement of new geometry can be relative to surrounding objects.

But when you access the ComponentDefinition’s Entities via the Ruby API, you are working purely within the abstract local coordinates. If you want to relate those coordinates to the model, you have to transform them the same way as the GUI does to display an instance in the model.

In the attached clip. I use the way that the 2020 GUI’s Tape measure tool displays coordinates if you float over a point. If the group is not open for edit, the coordinates are model coordinates. If it is open for edit, they are the definition’s local coordinates. You can see that the x and y values match because the group’s origin is mapped to the model’s z axis by its transformation, but the z values differ because the group’s origin is actually placed below the model origin. Your edges are offset vertically because your code isn’t accounting for this origin shift.

relative coordinates

2 Likes

I’m still having trouble with this. When I try to input the second group it says that the entity has been deleted.

etch_v5.rb

SKETCHUP_CONSOLE.clear
def etch(group,pts,thickness,lateral_extension=0,inversion=false)
  min_x = pts.map{|p| p[0]}.min
  max_x = pts.map{|p| p[0]}.max
  min_y = pts.map{|p| p[1]}.min
  max_y = pts.map{|p| p[1]}.max
  p "groupa = #{group}"
  g =  group.entities
  p "g=#{g}"
  p "groupb=#{group}"
  face = g.add_face pts
  if face.normal.z = -1
    face.reverse!
  end
  for v in face.vertices do
    min_v = v.position
    break if min_v.x == min_x && min_v.y == min_y
  end
  face.pushpull thickness,true
  cut_vector1 = [-lateral_extension,0,thickness]
  cut_vector2 = [0,0,thickness]
  cut = g.add_face [min_v,min_v.offset(cut_vector2),min_v.offset(cut_vector1)]
  cut.followme face.edges
  if inversion == true
    center = group.bounds.center
    vector = Geom::Vector3d.new(1,0,0)
    angle = 180.degrees 
    transformation = Geom::Transformation.rotation(center, vector, angle)
    group.transformation= transformation
  end
end

ents = Sketchup.active_model.entities
via = ents.add_group
active_ents = model.active_entities
gdef = via.definition
sub = active_ents.add_instance(gdef,IDENTITY)
p "via = #{via}"
p "sub = #{sub}"
z = 0
pts = [0,0,z], [5,0,z], [5,8,z], [0,8,z]
etch1 = etch(via,pts,10,6)
etch2 = etch(sub,pts,10,6,true)

For some reason this works


etch1 = etch(via,pts,10,6)
via = via.to_component
sub = ents.add_group
etch2 = etch(sub,pts,10,6,true)

but this doesn’t

sub = ents.add_group
etch1 = etch(via,pts,10,6)
via = via.to_component
etch2 = etch(sub,pts,10,6,true)

In Ruby, it is allowed (but discouraged by style guides) to make assignments within the conditinal expression of an if. You are doing this but I don’t think you really meant to.

Perhaps you mean to do:

  face.reverse! if face.normal.z == -1.to_l

… or …

  face.reverse! if face.normal == Z_AXIS.reverse

Both of the above use the API’s overridden == method for the class (Length in the former, Geom::Vector3d in the latter example.) These API methods do comparison within SketchUp’s internal tolerance.


… is likely better written as …

  min_x = pts.map(&:x).min
  max_x = pts.map(&:x).max

Ie, the SketchUp API adds some nifty methods to Ruby’s base Array class.

SEE:


Regarding this …

You have an array of points (pts) that you’ve used to make a face (face), and you want the minimum point (which was used for the minimum position for the face’s vertices,) so you might as well just search the pts array rather than create a new array (face.vertices) and then create new point objects (min_v = v.position) for each one of the vertices for comparison.

Secondly, leverage the fact that the Ruby core Enumerable module is mixed into Array and API collection classes. (Ie you do not need to author custom search loops.)

  min_v = pts.find {|pt| pt.x == min_x && pt.y == min_y }
  if min_v # check that it's not nil (ie not found)
    # safe to use min_v
  end

There is no need to call the == method here and compare against true as Ruby evaluates boolean expressions for the logical if. So just use …
if inversion

Keep in mind that in Ruby only false and nil evaluate as falsehoods. Everything else evals truthy including empty arrays, empty strings and the integer 0


Other potential problems:

  • Your code is not verifying that the faces were correctly created before using them for other geometry creation. Check that a face reference is truthy (ie, not nil) or valid (Entity.valid?) after creation, and before and after doing a followme.

  • Your code has no comments telling readers what it is supposed to do.

  • It seems convoluted to create groups, and then convert them to components.

  • I think I explained above that you need to immediately add some entities to a new group (and that we often just stick a cpoint at the group’s origin) if the code needs to do some other things, or delay creating the group until just before you’ll add entities, otherwise you run the risk of SketchUp’s housekeeping of deleting the group and it’s definition. (Is this reason enough?)