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
    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$ Jun 7, 2020 at 15:29
  • $\begingroup$ Added 2.8 answer. Assumed you were on 2.77 re docs link above. $\endgroup$
    – batFINGER
    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$ Jun 7, 2020 at 17:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.