Make several components unique to one definition - in Ruby

I have for example 4 component instances (all from the same component definition).
In Sketchup, if I select 2 components, right click, and make them unique. I will end up with 2 component definitions with 2 instances per each definition.
That’s good! It’s what I want.
How can I replicate this behavior with a selection of components in ruby?

What I really want to do, is loop over all entities within a component. And make them all unique from their instances in other components, but not unique from each other…

Just using instance.make_unique within the loop, makes each and every one of them unique… not what I want.

1 Like

Let’s assume you have a reference to some entities context - like:

ents = component_definition.entities

Then find any instances in that component.

instances = ents.grep(Sketchup::ComponentInstance)

Now make other instances [if any] of their definitions unique - UNLESS they are in ents

instances.each{|i|
  next unless i.definition.instances[1]
  i.make_unique unless i.parent.entities == ents
}

So now if you have two instances in ents they are still referencing the same definition, but all other instances in other entities contexts will now be unique…

Or if you want to make each set of components unique but the same for all instances with the same parent, you could do this.

instances = subcomponent.definition.instances

parents = Hash.new

instances.each{|i|
  if parents.include? i.parent
    i.definition = parents[i.parent]
  else   
    i.make_unique 
    parents[i.parent] = i.definition
  end
}

Great!
What does this line mean though?

next unless i.definition.instances[1]

Hi Neil,
That’s an interesting one. In what practical situations would you use that?
If all the instances have the same parent… It’s like they are all in the same component? so it wouldn’t make a difference… would it!? Unless the parent is maybe a… group? (this stuff is confusing :))

[quote=“Yoni, post:5, topic:36660”]
next unless i.definition.instances[1]
[/quote] If the instance’s definition has only one instance, then there is no need to make it unique [there’s only one already], so we skip it with the ‘next’.
The likelihood is that the API will itself skip over the unnecessary make_unique anyway, but in case it doesn’t [to avoid hiccups], and also to save a nano-second of processing time, you might as well skip processing it yourself…

I thought that is what you wanted. Here is def that allows you to make all instances unique, or make instances unique to parent.

def make_unique(inst, unique_to_parent = true)
  if unique_to_parent 
    Sketchup.active_model.start_operation "Make Unique to Parent"
  else
    Sketchup.active_model.start_operation "Make Unique"
  end
  instances = inst.definition.instances
  parents = Hash.new 
  instances.each{|i| 
    if parents.include? i.parent and unique_to_parent
      i.definition = parents[i.parent]
    else 
      i.make_unique 
      parents[i.parent] = i.definition 
    end 
  }
  Sketchup.active_model.commit_operation
end

Sorry, I misread one of your lines Neil, and thought you meant something else (I’m not even sure anymore what it was I was imagining ;))
It seems like both your code and TIGs are doing what I asked. I’ve adapted it to a little plugin I’m working on. Hope to release it soon.

Thank you both very much!

Looking at the code again, in both suggestions, the scripts make all ‘unrelated’ instances unique instead of making only the selected instances or their sub-components unique.

In TIG’s code, all instances in other parents are made unique of each other (or at least that’s what happens in my adaptation).

In the animation that Neil posted, in each ‘Big Box’ the small boxes are becoming unique (small_box#1 x 6, small_box#2 x 6, small_box#3 x 6… etc.).

I wouldn’t want my operation on one Big Box and it’s sub-boxes affecting the similarities of the small boxes in the other Big Boxes…

If I were to ‘select’ manually (with the mouse) one Big Box, ‘right click’, ‘make unique’. Go into it, and select 6 small boxes, ‘right click’, ‘make unique’, it still only makes those selected unique, without touching anything else outside of context. I wish to replicate that exact function in Ruby without messing with other components “by accident”.

Or, if I were ‘forced’ to mess with all other components (I’d rather not), I would want to maintain all similarities outside my selection. (including in multiple nested levels).

You would do that like this.

def make_selection_unique
  inst = Sketchup.active_model.selection.grep(Sketchup::ComponentInstance)
  if inst.count == 0 then end
  inst[0].make_unique
  pdef = inst[0].definition
  inst.each do |i| 
    i.definition = pdef
  end
end
(Ruby coding style nitpick)

Most coding style guides (and I) would prefer:

return if inst.empty?

a. inst.empty? is one method call that does not use a literal, whilst inst.count == 0 is two method calls. The count method is called returning a reference to an integer, whose == method is called passing the literal 0 by reference into it (where a comparison is made.)

b. I am always wary of having too many end keywords in the file.
(Have gotten too many SyntaxErrors on files, with messages like “unexpected end” or “missing end”, etc.)

1 Like

Thanks Dan. Neil, in your last suggestion, if the selection had multiple instances, with different definitions, it would turn them all into instances of one definition (the first) - which is unlike how the ‘right click’ - ‘make unique’ works.
With the tips from all of you, I think I managed to get what I want. :slight_smile:

The following code works almost like the ‘right click’ - ‘make unique’, except when making a selection of ALL similar instances (all in selection are of the same definition) - It still makes them all unique (adding a new definition), instead of ignoring them. (why add new definitions if no need to).

def make_sel_unique
  def_hash = Hash.new
  sel = Sketchup.active_model.selection.grep(Sketchup::ComponentInstance)
  return if sel.empty?
  sel.each { |i| 
    # if the instant's definition exists in the hash
    # assign to it the new definition value (new_def). 
    if def_hash.key?(i.definition) 
      i.definition = def_hash[i.definition]  
    else
    # If not in the hash - make unique while adding to the hash the 
    # definitions - before & after making unique - {old_def -> new_def}
      old_def = i.definition
      i.make_unique
      new_def = i.definition
      def_hash[old_def] = new_def
    end 
  }
  return nil  
end
2 Likes

(Next step I’ll combine it with making unique to parent… )