4
$\begingroup$

Setup

I have an object Plane with 6 vertices. To each vertex I assign another object as child.

Then I apply two keyframes on location to object Plane:

  1. In frame 0 it goes to Z = 5
  2. In frame 81 it goes to Z = 0

I want to know correct global x,y,z coordinates of each Sphere during frames 0 to 81.

Frame 81. Plane and child Spheres enter image description here

Problem

I do not understand which matrix or matrix combination to use to achieve coordinates in this case.

For example for frame 81 I know that Z = 0 for Plane. It is also Z = 0 for all spheres.

I would like to know object Sphere global coordinates. If I code in Blender console:

>>> sp0.matrix_world.to_translation()
Vector((-1.0, -1.0, 0.0))

>>> sp0.location
Vector((-1.0, -1.0, 5.0))

If I write python script and run it I have:

obj = bpy.data.objects['Sphere']
x,y,z = obj.matrix_world.to_translation()
print("x,y,z ", [x,y,z])
print("setpoint_location ", obj.location)

>>>x,y,z  [-1.0, -1.0, 5.0]
>>>setpoint_location  <Vector (-1.0000, -1.0000, 5.0000)>

Additional info

Code I use to create Spheres and attach it to Plane as child objects on each vertex.

import bpy

def create_objects():
    for i in range(6):
        bpy.ops.mesh.primitive_uv_sphere_add(radius=0.25, enter_editmode=False,location=(i*2., 0, 0))
def allocate_objs(plane, objects):
    bpy.ops.object.select_all(action='DESELECT')
    for i, vert in enumerate(plane.data.vertices):
        world_matrix = plane.matrix_world
        vertice_coords = world_matrix @ vert.co
        objects[i].location = vertice_coords
        objects[i].select_set(True)

    plane.select_set(True)
    bpy.context.view_layer.objects.active = plane
    bpy.ops.object.parent_set(type='VERTEX')

#create_objects()
objects = [obj for obj in bpy.data.objects if "Plane" not in obj.name]
plane = bpy.data.objects["Plane"]
#allocate_objs(plane, objects)
obj = bpy.data.objects['Sphere']

for frame in range(0,100):
    bpy.context.scene.frame_current = frame
    if frame in [0, 81]:
        x,y,z = obj.matrix_world.to_translation()
        print(bpy.context.scene.frame_current)
        print("world matrix \n", obj.matrix_world)
        print("matrix_basis \n", obj.matrix_basis)
        print("matrix_local \n", obj.matrix_local)
        print("matrix_parent_inverse \n", obj.matrix_parent_inverse)
        print("frame ", frame)
        print("x,y,z ", [x,y,z])
        print("setpoint_location ", obj.location)
        print("======================")
$\endgroup$

2 Answers 2

5
$\begingroup$

Parent with Identity inverse.

The relationship between the matrices.

Does a child object inherit the matrix from the parent?

and if you have used the parenting operator (yick) how to reset parent inverse to identity, to avoid the hassle. (The location can be garnered from the vert location anyway_)

how to clear parent inverse without actually moving the object

Here is an alternative way to parent your spheres to the verts of the plane, code from https://blender.stackexchange.com/a/200719/15543 Each object has an identity parent inverse. (It uses empties, for 6 could use add mesh operator, location=(0, 0, 0), directly after each run the new object is context.object)

Run with plane as context object

import bpy
from bpy import context

ob = context.object
coll = context.collection

for v in ob.data.vertices:
    mt = bpy.data.objects.new(
        f"Vert{v.index}",
        None,
        )
    mt.empty_display_type = 'CIRCLE'
    mt.empty_display_size = 0.2
    mt.parent = ob
    mt.parent_type = 'VERTEX'
    mt.parent_vertices = [v.index] * 3
    coll.objects.link(mt)

Testing.

Am going to use these vert parented empties as a test case for above, for simple numbers have simply moved the plane to (0, 0, 2). Still the context object.

enter image description here

>>> C.object
bpy.data.objects['Plane']

Local and global vert locations.

>>> for v in C.object.data.vertices:
...     v.index, v.co[:], (mw @ v.co)[:]
...     
(0, (-1.0, -1.0, 0.0), (-1.0, -1.0, 2.0))
(1, (1.0, -1.0, 0.0), (1.0, -1.0, 2.0))
(2, (-1.0, 1.0, 0.0), (-1.0, 1.0, 2.0))
(3, (1.0, 1.0, 0.0), (1.0, 1.0, 2.0))
(4, (0.00918059702962637, -1.0, 0.0), (0.00918059702962637, -1.0, 2.0))
(5, (0.00918059702962637, 1.0, 0.0), (0.00918059702962637, 1.0, 2.0))

Missed the middle with a loop cut, nevermind

Global, and local object locations

>>> mw = C.object.matrix_world
>>> mwi = mw.inverted()
>>> for o in C.scene.objects:
...     o.name, o.matrix_world.translation[:], (mwi @ o.matrix_world.translation)[:]
...     
('Plane', (0.0, 0.0, 2.0), (0.0, 0.0, 0.0))
('Vert0', (-1.0, -1.0, 2.0), (-1.0, -1.0, 0.0))
('Vert1', (1.0, -1.0, 2.0), (1.0, -1.0, 0.0))
('Vert2', (-1.0, 1.0, 2.0), (-1.0, 1.0, 0.0))
('Vert3', (1.0, 1.0, 2.0), (1.0, 1.0, 0.0))
('Vert4', (0.00918059702962637, -1.0, 2.0), (0.00918059702962637, -1.0, 0.0))
('Vert5', (0.00918059702962637, 1.0, 2.0), (0.00918059702962637, 1.0, 0.0))

or alternatively instead of multiplying by mwi use matrix_local eg

>>> C.object
bpy.data.objects['Vert1']

>>> C.object.matrix_local.translation
Vector((1.0, -1.0, 0.0))

So I contend, assuming the empties (spheres) do not move from their parented vert, the local coordinates can be taken from the vertex coordinates, and the global coordinates also by multiplying by the objects matrix world.

Or conversely the global coordinates of the verts from the empties global locations, and the locals by multiplying by the parent objects inverse matrix world.

Note if the plane is to be deformed, can get the mesh coordinates with Object.to_mesh or Bmesh.from_object.

Note: It is recommended to change frame with scene.frame_set(f) rather than setting scene.frame_current = f.

$\endgroup$
2
  • $\begingroup$ thx for such elaborate and wonderful explanation! Is C.object.matrix_local.translation gives local coordinates of the vertex? I did not quite understood the ending. Below code answered my question. It shows real coordinates of child objects, attached to vertex C.object.matrix_world.translation $\endgroup$
    – Ermakx
    Mar 15, 2021 at 15:38
  • $\begingroup$ In the above example the object "Vert1" is parented to vertex 1 of the object. So yes it's local translation is the same as the vert coordinate, in that it is the distance (locally) from parent object origin.\ $\endgroup$
    – batFINGER
    Mar 15, 2021 at 15:42
0
$\begingroup$

Annex to the example above (batFINGER's post): in simple situations, to know the local location is enough, but what if "plane" is parented to another object, and both are rotated, and you want to have the same local rotation for circles around the plane then before (rot euler (0,0,0))? or a chosen local rotation for circles?

let's see the first point:
before: before

ob = context.object #plane
mw = ob.matrix_world
R = mw.to_quaternion().to_matrix().to_4x4()

for c in ob.children:
    mat = c.matrix_basis 
    loc, rot, scale = mat.decompose()
    To = Matrix.Translation(loc)
    Ro = R
    So = Matrix.Diagonal(scale).to_4x4()
    c.matrix_basis = To @ Ro @ So 

after: After

from there, let's tell we want to chose our own rotation, based on, e.g, cursor rotation (rotation_euler = (45, 0, 0))

cursor = context.scene.cursor
mat = cursor.matrix
    
ob = context.object #plane
mw = ob.matrix_world
R = mw.to_quaternion().to_matrix().to_4x4() # plane rot
R1 = mat.to_quaternion().to_matrix().to_4x4() # curs rot
R_local = R @ R1 

for c in ob.children:
    mat = c.matrix_basis 
    loc, rot, scale = mat.decompose()
    To = Matrix.Translation(loc)
    Ro = R_local
    So = Matrix.Diagonal(scale).to_4x4()
    c.matrix_basis = To @ Ro @ So
    # let's check the local rotation
    qc = c.matrix_world.to_quaternion() 
    q = mw.to_quaternion()
    rot = q.rotation_difference(qc).to_euler()
    print(degrees(rot[0]), degrees(rot[1]), degrees(rot[2])) > 45,0, 0  # we'r good
    #if we'd used R1 instead of R_local we'd a global 45,0,0 rotation

nice, we have now our local rotation of 45° with the plane

45°X rotation

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .