Save initial group transformation when moving between contexts (add_instance)

How does it work)?. For example, we have a parent group A that hosts group 1 and a parent group B that hosts group 2. I want to move group 2 to group 1. My code works fine if group 1 and group 2 are in the same context. If they are in different contexts (as in the example), then the placement of group 2 changes after the move. How to preserve group 2 transformation when moving it between contexts? Like how we just move groups between contexts in the outliner tray.
Here is the code:

ruby

def find_group_by_name(entities, name)
  entities.each do |entity|
    return entity if entity.is_a?(Sketchup::Group) && entity.name == name
    if entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
      result = find_group_by_name(entity.definition.entities, name)
      return result if result
    end
  end
  nil
end

model = Sketchup.active_model

group2 = find_group_by_name(model.entities, "2")

group1 = find_group_by_name(model.entities, "1")

if group1.nil? || group2.nil?
  puts "Одна з груп не існує"
  return
end

name = group2.name
layer = group2.layer
material = group2.material
attributes = group2.attribute_dictionaries

original_transformation = group1.transformation.inverse * group2.transformation

new_group2 = group1.entities.add_instance(group2.definition, original_transformation)

new_group2.name = name
new_group2.layer = layer
new_group2.material = material
attributes.each { |dict| new_group2.set_attribute(dict.name, dict.each_pair.to_h) } if attributes

group2.erase!

The simplest solution is to explode group B.

Thank you. And what is the more difficult solution?

Likely using InstancePath class and it’s #transformation method.

It also will depend upon whether the source context and the target context are on the same branch of the nesting hierarchy.


One possible way to do this is to take advantage of the fact that when in an editing context, the paste in place will use the transformation in world coordinates.

  1. Set the active_path to an instance path of [..., group_a]
  2. Select group_1: model.selection.clear; model.selection.add(group_1)
  3. Call Sketchup.send_action("cut:")
  4. Set the active_path to an instance path of [..., group_b]
  5. Call
    if Sketchup.platform == :platform_win
      Sketchup.send_action(21939)
    else
      Sketchup.send_action("pasteInPlace:")
    end
    

You might need to wait before step 5, until you verify that the active path has been changed to the target context.

1 Like

Thank you. Will have to try this)

Thanks @DanRathbun , I tried your advice to change the code and got what I wanted.

# This method searches for a group by its name within a collection of entities.
def find_group_by_name(entities, name)
  entities.each do |entity|
    # Returns the entity if it is a group and its name matches the given name.
    return entity if entity.is_a?(Sketchup::Group) && entity.name == name
    # If the entity is a group or component instance, recursively search its definition's entities.
    if entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
      result = find_group_by_name(entity.definition.entities, name)
      return result if result
    end
  end
  # Return nil if no matching group is found.
  nil
end

# This method finds the instance path to a target entity within a collection of entities.
def find_instance_path(entities, target, path = [])
  entities.each do |entity|
    new_path = path + [entity] # Append the current entity to the path.
    # Returns the new path if the current entity is the target.
    if entity == target
      return new_path
    # If the entity is a group or component, recursively search its definition's entities.
    elsif entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
      found_path = find_instance_path(entity.definition.entities, target, new_path)
      return found_path unless found_path.nil?
    end
  end
  # Return nil if no path to the target is found.
  nil
end

model = Sketchup.active_model

# Find groups by their names.
group1 = find_group_by_name(model.entities, "1")
group2 = find_group_by_name(model.entities, "2")

# If either group does not exist, print a message and exit.
if group1.nil? || group2.nil?
  puts "One of the groups does not exist"
  return
end

# Find the instance paths to each group.
path_to_group1 = find_instance_path(model.entities, group1)
path_to_group2 = find_instance_path(model.entities, group2)

if path_to_group1 && path_to_group2
  # Calculate the transformation from group1 to group2 using their instance paths.
  transformation_to_group1 = Sketchup::InstancePath.new(path_to_group1).transformation
  transformation_to_group2 = Sketchup::InstancePath.new(path_to_group2).transformation
  combined_transformation = transformation_to_group1.inverse * transformation_to_group2

  # Add an instance of group2 to group1 with the calculated transformation.
  new_group2 = group1.entities.add_instance(group2.definition, combined_transformation)

  # Copy properties from the original group2 to the new instance.
  new_group2.name = group2.name
  new_group2.layer = group2.layer
  new_group2.material = group2.material
  # Copy attribute dictionaries from the original group2 to the new instance.
  group2.attribute_dictionaries.each do |dict|
    new_group2.attribute_dictionaries[dict.name].update(dict)
  end if group2.attribute_dictionaries

  # Erase the original group2.
  group2.erase!
else
  puts "Failed to find paths to the groups"
end

1 Like