Making sense of hair_key coordinates I am trying to convert hair_key.co_local to world coordinates. As I understand it, every hair particle has a bunch of hair_keys (type:ParticleHairKey) that represent strand segments. The ParticleHairKey type has two coordinate fields:


co - always (0,0,0) on read; writing does nothing
co_local - read produces non-zero vectors; write DOES have some sort of
an effect


So my guess is, co field is not implemented  / supported and I have to work with co_local instead.

according to the docs ParticleHairKey co_local is "Location of the hair key in its local coordinate system, relative to the emitting face". and there is co_object method that returns key location (I assume in object coordinates). No matter what I tried however, the resulting coordinate doesn't seem to map to world coordinates in any meaningful way.

For example, I have a single strand of hair with 3 segments. particle.location here is already interpretable - if I put 3d cursor at that position, it matches particle location in the viewport (see pic).

obj = bpy.data.objects["gen82"]
psMod = (obj.modifiers)[1]    
ps = obj.particle_systems["ParticleSettings"]

for m, particle in enumerate(ps.particles):
    print("particle location", particle.location)
    bpy.context.scene.cursor.location = particle.location
    for k in range(0, len(particle.hair_keys)):
            print(f"key {k}", particle.hair_keys[k].co_local)            


Moves 3d cursor as expected.

 

and produces following console output

particle location &lt;Vector (0.0953, -3.7172, -0.6447)&gt;
key 0 &lt;Vector (0.0000, 0.0000, 0.0000)&gt;
key 1 &lt;Vector (-0.0036, 0.0030, 0.1666)&gt;
key 2 &lt;Vector (-0.0073, 0.0060, 0.3332)&gt;
key 3 &lt;Vector (-0.0109, 0.0090, 0.4998)&gt;


As you can see, hair_key.co_local seems to match description from the docs - it gets progressively farther from (0,0,0) as key index increases. But I have real difficulty working with those values. I tried calling co_object and transforming to world coordinates, tried offsetting by particle.location etc. - nothing produced world coordinates. Is there a way of transforming this coordinate to object or world coordinates ?
Use evaluated object in 2.8+

Example for 2.8+ adds an empty at each hair key location. Note: since it adds an empty via op, test on an object with lowish hair count

import bpy

context = bpy.context
dg = context.evaluated_depsgraph_get()
# evaluated object
ob = context.object.evaluated_get(dg)
mw = ob.matrix_world

for p in ob.particle_systems[0].particles:
    for k in p.hair_keys:
        bpy.ops.object.empty_add(location=mw @ k.co)