How to get components global coordination

transformation
model_space
model_coordinates
local_coordinates

#1

i try some methods.
firstly i use boundingbox.center, if the components have the same definition, the center values are the same.

secondly i use transformation.origin, if the components have the same definition, the origin values are also the same.

how do i get the components’ global coordination relative to the active model’s origin point?


Issue with unused definitions
Get Instance transformation in reference to model root
#2

Take care of two things:

  1. You need to define which coordinates you are looking for. A component contains drawing elements that extend in space and thus have many coordinates. Most likely you mean the coordinates of the component origin.

  2. You need to distinguish component definitions and component instances. An instance is a “clone” of the definition with a specific transformation and inside a specific parent entities collection. Since the entities collection itself may be contained inside a “parent” component definition (with multiple instances), a single component instance can have many occurences in the model – everywhere where the parent component has an instance. Thus refering to a component instance is ambiguous, we need to consider the whole instance path (array of nested parent component instances).

In your test you were probably calling bounds.center on the component definition, or you were comparing two occurences of the same component instance (inside two different instances of the parent component, like:
(component definition A contains an instance of definition B)
[Model, A#1, B#1]
[Model, A#2, B#1]
Then for both occurences of B#1, B#1 has the same transformation and origin. To get the global coordinates, you have to accumulate the transformations of all instances in the instance path.

Example to collect instance paths:

def collect_all_instance_paths(model, &condition)
  instance_paths = []
  contained_instances = {}
  model.definitions.each{ |definition|
    instances = definition.entities.grep(Sketchup::Group).concat(definition.entities.grep(Sketchup::ComponentInstance))
    instances = instances.select(&condition) if block_given?
    contained_instances[definition] = instances
  }
  instances = model.entities.grep(Sketchup::Group).concat(model.entities.grep(Sketchup::ComponentInstance))
  instances = instances.select(&condition) if block_given?
  queue = instances.map{ |instance| [instance] }
  until queue.empty?
    path = *(queue.shift)
    instance = path.last
    next if instance.nil?
    contained = contained_instances[instance.definition]
    instance_paths.push(path)
    unless contained.empty?
      contained.each{ |instance2|
        queue.push(path + [instance2])
      }
    end
  end
  return instance_paths
end

# or

def collect_occurences(instance)
  instance_paths = []
  queue = [ [instance] ]
  until queue.empty?
    path = *(queue.shift)
    outer = path.first
    if outer.parent.is_a?(Sketchup::Model)
      instance_paths << path
    else
      outer.parent.instances.each{ |uncle|
        queue << [uncle] + path
      }
    end
  end
  return instance_paths
end

instance_paths = collect_occurences(some_instance)

Once you have an instance path, aggregate the transformations:

def get_transformation_for_instance_path(instance_path)
  transformation = Geom::Transformation.new() # IDENTITY
  instance_path.reject{ |noninstance| # Model or DrawingElement do not have a transformation.
    !noninstance.respond_to?(:transformation)
  }.each{ |instance|
    transformation *= instance.transformation
  }
  return transformation
end

global_transformation = get_transformation_for_instance_path(instance_path)

Then apply the transformation to vertex positions of the geometry inside the most inner instance, or get the global position of the origin (which (0,0,0) when seen from inside the component definition):

global_transformation.origin

Recommendations for animation code?
#3

Thank you! I’ll have a try. Your description is a little complicated. I’ll read your code carefully.


#4

what is the operator “*” meaning between transformation?


#5

Composition of Transformations (equivalent to a matrix multiply of the equivalent matrices). Consult the SketchUp Ruby API documentation http://ruby.sketchup.com/Geom/Transformation.html#*-instance_method


#7

is there this situation that the components have the same parent-path and the same definition. but visually the component instances are in different position.


#8

Yes. The component can be nested inside of other components that are at different locations. The components would be duplicates that have the same relative coordinates nested inside another grouped object, but the copies of the other grouped object could be in different locations. The answer is no when the components are in the outer most entities collection, then they would be in world coordinates. Is there a Sketchup file that you could attach?


#9

i think your answer is correct. thank you. but i can’t upload my skp to my message


#10

you know, i can get the instance’s local center by “bounds.center”. But now i can get the instance’s global transformation. how can i get the global center by the global transformation?


#11

Matrix × vector = transformed vector
So in SketchUp it is:

global_center = global_transformation * instance.definition.bounds.center

Here using definition.bounds because we have already included the instance’s transformation in the global transformation. Potentially you may have the wrong matrix and you have to invert it (inverse).


#12

thank you for your great help. but which condition could i get a wrong matrix about the definition.bounds ? you can give an example.


#13

Matrix multiplication is not commutative, so A * B is not B * A. Sometimes I have the multiplication in wrong order. Or I have the matrix for local to global coordinates and wrongly use it for global to local.


#14

thank you


#15

@Aerilius,

your code seems to fall down if you have flipped, scaled or rotated instances of the same definition…

how can we account for those?

I’ve been trying many variations without success…

in the gif I’m adding the instances of the green component to all others to find bound intersections and add to selection…

the flipped versions don’t add them in the correct location, but the unflipped do…

john


#16

I got the code to work by only supplying unique definitions and exploding flipped ones into new groups…

not ideal, but it’s the correct end result…

john


#17

John;
You posted a slightly different version of the model on:

I was able to determine that it has two section planes at:
a) Un-nested one at “.entities[2]”; and
b) Nested one at “.entities[0].entities[0]”.

Neither of them is displayed, and selecting “View…Hidden Geometry” does not display them. According to the Ruby console, they are both unhidden and both active. The model only has one layer, and there are no hidden layers for them to hide on. If I add an additional section plane, they show up as follows:

The larger orange section plane is the one I added. The grayed one is no longer active (item ‘a’) since I added the new one.

Why are there section planes that are unhidden and active that are not noticeable on this model? How did you achieve that?


#18

add them inside a group and apply setting in that context…

the model is deliberately obstructive as an example of ‘common’ misuse…

testing on nice clean models is far easier, but won;t show edge cases…

it’s amazing what can be buried in some models that will kill clean logical code…

did you also spot the hidden nested component…

the main models the same, but I made the ‘selector’ a triangle to see when it flips…

john


#19

Luckily it isn’t completely inside another cube, and can be seen with View…Hidden Geometry:

My pick tool says it is at entities[1].entities[7], instance 2 of 3 (the other instances are not hidden). Contained within that component is a single Sketchup group object.


#20

I can’t see why you create a Proc that doesn’t seem to be used?

I may well be overlooking the obvious…

john


#21

That doesn’t do anything, but instances = instances.select(&condition) if block_given? reduces the selection array to the instances that match a condition (like select{ |i| …}). One can leave that parameter away, it was just in the code where I copied this extract from.