Make a component and all of it's children unique

Hello everyone, hope all is well in your respective worlds.

I am trying to make a selection of multiple components and their children unique. I started with this thread. The following code, as far as I can figure, should work. But it makes the wrong components unique (video) and I can’t figure out why!?!? :thinking:. It’s the exact opposite result of what I want, how do I flip it? I have been poking at this for a while now and am out of ideas, any help is deeply appreciated. :folded_hands:

The Video:

The Code:


# Makes (supposed to) an array of components and their children unique, 
# with similar instances being made unique to one new definition.

def filter_instances(selection) # Grabs Groups and Components.
  selection.grep(Sketchup::Group).concat(selection.grep(Sketchup::ComponentInstance))
end

def select_children(inst, family) 
  # Grab Groups and Components that are children - 2nd level.
  children = filter_instances(inst.definition.entities) 
  # Add them to family array.
  family.concat(children) 
  # Repeat with 2nd level to get 3rd level, etc... 
  children.each { |child| select_children(child, family) } 
end

def make_all_unique()
  model   = Sketchup.active_model
  selection  = model.selection
  parents = filter_instances(selection)
  family = []
  family.concat(parents)
  parents.each { |inst| select_children(inst, family) }
  cdefs = Hash.new # cdefs = {old_cdef: new_cdef}

  family.each do |dc| 
    old_cdef = dc.definition
    if cdefs.key?(old_cdef) # If the definition has already been made unique.
      dc.definition = cdefs[old_cdef] # Replace the old definition with the new one.
    else # If the instances' definition is not in the hash.
      dc.make_unique # Make the instance unique, creating a new definition.
      # Pair the new definition with the old definition in the cdefs hash.
      new_cdef = dc.definition
      cdefs[old_cdef] = new_cdef 
    end 
  end
end

make_all_unique()

Test File:

Box Test.skp (133.9 KB)

Hey Ryan,

Hope there isn’t a boulder in every post hole these days :upside_down_face:

Maybe this:

With this:

Code
def filter_instances(selection)

  selection.grep(Sketchup::Group).concat(selection.grep(Sketchup::ComponentInstance))

end

def make_children_unique(inst)

  children = filter_instances(inst.definition.entities)

  children.each do |child|

    child.make_unique

    make_children_unique(child) # recurse deeper

  end

end

def make_all_unique()

  model     = Sketchup.active_model

  selection = model.selection

  parents   = filter_instances(selection)

  return if parents.empty?

  model.start_operation("Make Selected Unique", true)

  \# Stage 1: make first parent unique

  first = parents.shift

  first.make_unique

  new_parent_def = first.definition

  \# Stage 2: reassign other selected parents to same new definition

  parents.each { |inst| inst.definition = new_parent_def }

  \# Stage 3: inside the new parent definition, make children unique

  make_children_unique(first)

  model.commit_operation

end

make_all_unique()
1 Like

OK so with a little help from James @3DxJFD I have it figured out I think. The secret sauce was sorting all of the collected instances first.

def filter_instances(selection) # Grabs Groups and Components.
selection.grep(Sketchup::Group).concat(selection.grep(Sketchup::ComponentInstance))
end

def select_children(inst, family) 
  # Grab Groups and Components that are children - 2nd level.
  children = filter_instances(inst.definition.entities) 
  # Add them to family array.
  family.concat(children) 
  # Recurse deeper collecting all selected instances.
  children.each { |child| select_children(child, family) } 
end

def make_all_unique()
  model = Sketchup.active_model
  sel = model.selection
  parents = filter_instances(sel)
  family = []
  family.concat(parents)
  parents.each { |inst| select_children(inst, family) }

  # Collect instances by their definitions.
  groupt = family.group_by { |inst| inst.definition }

  groupt.each_value do |array|
    # Make the first instance of each collection unique.
    first = array.shift
    first.make_unique
    new_cdef = first.definition
    
    # Assign the remaining instances to the new definition.
    array.each do |inst|
      inst.definition = new_cdef
    end 
  end
end
2 Likes