How to get global coordinates of a Point3D

Hello, I try to get global coordinates of an Point3D. I was looking for this method in documentation, but I did not found anything to match.
So, is it possible in SketchUp to get somehow global coordinates of an Point3D?
Thanks for your answers!

The answer relies heavily on your understanding of coordinate systems and the RubyAPI’s Geom::Transformation. If these are not familiar concepts you would be well advised to do some study before proceeding, as you are very likely to get confused otherwise.

At the broadest level, you must determine the Transformation that places the Point3D in the model and apply it using Point3D#transform or Point3D#transform!

There are various situations that need to be handled differently.

In SketchUp, there is always an active editing context (Entities collection) which the the user has open for drawing. This collection may belong to the model (if the user is drawing “loose geometry”) or to a ComponentDefinition (if the user has opened a Group or ComponentInstance for edit).

You can obtain the user’s editing context Entities via model#active_entities. You can determine whether the user accessed it by opening a specific nest of entities via model#active_path. You have to build the transformation back to global (model) coordinates by concatenating the transformations for each step of the active path.

On the other hand, if you obtained an object using either InputPoint or PickHelper, they provide methods to determine the Transformation for the picked object. If you obtained an object using model#raytest, along with the Point3D it returns an Array of the nest of objects the ray penetrated to reach the object containing the Point3D. You need to concatenate the Transformations of these to get the global coordinates.

Finally, if your code accessed an Entities collection owned by a ComponentDefinition by some other means (e.g. by #parent applied to some object), the question may be unanswerable. A ComponentDefinition’s coordinates are “abstract” or “local”. They are placed in the model when an instance (Group or ComponentInstance) is created, in which case the Transformation of that instance is needed. But a ComponentDefinition may have many instances, each with its own different Transformation. You have to choose one instance to make the question have meaning.

1 Like

If it helps, I got the Point3D from a (Sketchup::Edge).start.position, which is Point3d of returned Sketchup::Vertex. And the problem it is that this edge is part of an component, which also is part of another component. And I probably will have cases when edge can be encapsulated in less or more components.

So, what did you do to obtain the Edge?

First of all I got array of all components definitions which was added after importing main component.

     before = model.definitions.to_a
     status = model.import("tile_2.skp")
     added_comps = model.definitions.to_a - before
     p "component was imported?: #{status}"
     p added_comps

After that I search for needed component by it’s definition name, and after I use grep to get all it’s edges.

added_comps.each { |comp|
          if comp.name == "Component#2"
            tile_comp_def = comp
            break
          end      
            } 

edges_of_tiles_comp = tile_comp_def.entities.grep(Sketchup::Edge)

And in this edges array I found needed edge by it’s length, because it has unique length.

I’ll describe the simplest case first: assume all ComponentDefinitions involved have just one instance. In that case you can access that ComponentInstance or Group via

instance = definition.instances[0]

Then check instance.parent. If it is the model, just apply the instance.transformation.

If it is not the model, it will be another ComponentDefinition. Save instance.transformation and recurse the line above to find the instance’s parent. Keep recursing until you reach the model. Concatenate all the Transformations to get back to the model coordinates.

If some ComponentDefinitions have more than one instance, the question is fundamentally ambiguous. The Component or Group has been placed multiple times in the model. You have to decide which of them you want to use.

1 Like

Wow,

I had the same problem just the other day.
I was referencing 4 axes at once!
@slbaumgartner has got this down flat. :slightly_smiling_face:

I dealt with this accidentally a bit as I trying to learned how hash works … maybe it can be useful.
(I don’t swear it’s 100% good …)

  # Based on: Julia Christina Eneroth
  #  Eneroth Material Area Counter
  #           Thanks
  #
  #Get all edges and its lengths to hash, 
  # considering all nested transformation
  #ToDo: Copying a group using the copy tool in SketchUp
  #  will create copies of the group that share a common definition
  #  until an instance is edited manually. 
  #  If multiple copies are made, all copies share a definition
  #  until all copies are edited manually, or use #make_unique method
  # So in this case the common def will counted once.
  def iterate_entities(entities, transf = IDENTITY, lengths = Hash.new(0))
    entities.each{|entity|
      case entity
      when Sketchup::Edge
        vert_tr = entity.vertices.map{|p| p.position.transform(transf)}
        len_tr = vert_tr.first.distance(vert_tr.last)
        len = entity.length
        scale = len_tr/len
        len = len * scale
        lengths[entity] = len 
      when Sketchup::ComponentInstance, Sketchup::Group
        iterate_entities(
          entity.definition.entities,
          transf * entity.transformation,
          lengths
        )
      end
    }
    lengths
  end
  edges_and_lengths_hsh = iterate_entities Sketchup.active_model.entities
  len = 100
  e_len_is_100_hsh = edges_and_lengths_hsh.select{|k,v| v == len}
  edges_are_100_ary = e_len_is_100_hsh.keys
1 Like

Better to use one of the Enumerable methods that are mixed into arrays …

tile_comp_def = added_comps.find { |comp| comp.name == "Component#2" }

# Always test the result ...
if tile_comp_def # nil if not found
  edges_of_tiles_comp = tile_comp_def.entities.grep(Sketchup::Edge)
end

It also would be good if you are making the tile component definition(s), that you give the definition a nice name for finding, rather than have SketchUp throw a vague "Component#2" into the name property.

There is not a method named “concatenate” for the Transformation class, so Steve is talking about matrix multiplication using the #* method.

Except that the API has a strange behavior with editing contexts. And this is that any API method returns global/world coordinates when the user is in editing context.

An example, using the DC Sampler “Bench” component. If I double-click in the Bench component, and then into the 3rd back stile from the left, and choose the front bottom edge …

ss = Sketchup.active_model.selection
#=> <Sketchup::Selection:0x0000019c46636d28>
e = ss[0]
#=> <Sketchup::Edge:0x0000019c46c4cc30>
e.start.position
#=>(15", 26.5", 20.135822")

These are global coordinates.

If I exit the back stile instance’s edit context …

e.start.position
#=> (0", 0", 0.129727")

I get local coordinates.

Exit the bench instance’s edit context and the coordinates are the same local to the group instance’s definition’s entities collection.

Use the MoveTool to move the bench instance by both x and y.

e.start.position
#=> (0", 0", 0.129727")

From outside we still get local coordinates inside the back stile’s definition’s entities collection.

But double-click into the bench and then into the third back stile again …

e.start.position
#=> (75.071", 49.4928", 20.135822")

… is now in world (model) coordinates.


Besides model#active_path there is also …

Sketchup::Model#edit_transform

Returns the transformation of the current component edit session. If a user has double-clicked to edit a component’s geometry, this will return the transformation of that component, relative to its parent’s origin. This allows one to correctly calculate “local” transformations of a given entity regardless of whether the user is in edit mode.

1 Like

Thank you all for your replies, now I understood better how SketchUp works, and what better to use. Based on your answers I searched topics related to how to find instance paths and perform matrix multiplication.
It worked for me:

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

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


instance_paths = collect_occurences(edge)
global_transformation = get_transformation_for_instance_path(instance_paths[0])
vert_tr = edge.vertices.map{|p| p.position.transform(global_transformation)}


3 Likes