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.

enter image description here

and produces following console output

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

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 ?

  • 1
    $\begingroup$ Are you using 2.77? AFAIK this is different in 2.8 using the depsgraph (can post an answer if interested in answer for 2.8,) Recommend a simple plane, off global centre eg (1, 1, 1), example with a couple of hairs and results you obtained for each coord type. From local_co desc one would assume will need a space matrix centered on face center aligned with face normal (tricky). In 2.8 co returns the world coord $\endgroup$
    – batFINGER
    Commented Jun 7, 2020 at 5:30
  • $\begingroup$ I am using 2.82. Thanks for drawing my attention back to co. I googled why it is always zero vector and found this gem developer.blender.org/T60083 $\endgroup$ Commented Jun 7, 2020 at 15:29
  • $\begingroup$ Added 2.8 answer. Assumed you were on 2.77 re docs link above. $\endgroup$
    – batFINGER
    Commented Jun 7, 2020 at 16:22

1 Answer 1


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)
  • $\begingroup$ as a follow up, i also want to modify hair_key location. I figured this probably can't be done through evaluated_depsgraph and i will have to somehow compute my desired hair_key locations in co_local coordinates? $\endgroup$ Commented Jun 7, 2020 at 17:14

You must log in to answer this question.

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