Programmatically set hair position and shape in Blender 2.8

I’m working on a project where I set a (relatively) small number of hair strands using particle system. In Blender 2.79 I’d just loop through obj.particle_systems[0].particles and set each particle’s location and hair_keys[].co. As discussed in https://developer.blender.org/T58792 this doesn’t work now in Blender 2.8, but there seems to be a workaround? (or new intended behaviour?)

Anyway, I don’t get it to work. The hair strands are not updated to the positions I try to set anymore.
So my question is: what’s currently the best way to set the hair strands’ position and shape?

I’ve made a minimal example that tries to make a row of 5 hair strands. See this comparison of the code run in Blender 2.79 and Blender 2.80, respectively:

Here is the Blender 2.79 code that gives the desired result:

import bpy

# 1. Use initial cube as object and set CYCLES
obj = bpy.data.objects['Cube']
bpy.context.scene.render.engine = 'CYCLES'

# 2. Add a particle system
psys = obj.modifiers.new("hair", 'PARTICLE_SYSTEM').particle_system
psys.settings.type = 'HAIR'
psys.settings.use_render_emitter = False
psys.settings.use_strand_primitive = True
psys.settings.cycles.radius_scale = 0.2
psys.settings.hair_step = 3
psys.settings.count = 5

# Connect and disconnect (needed, but why?)
bpy.ops.particle.disconnect_hair(all=True)
bpy.ops.particle.connect_hair(all=True)

# 3. Set hair positions vertices
for m in range(len(psys.particles)):
    psys.particles[m].location = (m, 0, 0)

    for n in range(len(psys.particles[m].hair_keys)):
        psys.particles[m].hair_keys[n].co = (m, 0, n)

# Toggle back and forth to update viewport
bpy.ops.particle.particle_edit_toggle()
bpy.ops.particle.particle_edit_toggle()

And here is my current best attempt at the corresponding Blender 2.80 code (with some minor API-changes + the aforementioned workaround):

import bpy

# 1. Use initial cube as object and set CYCLES
obj = bpy.data.objects['Cube']
bpy.context.scene.render.engine = 'CYCLES'

# 2. Add a particle system
psys = obj.modifiers.new("hair", 'PARTICLE_SYSTEM').particle_system
psys.settings.type = 'HAIR'
obj.show_instancer_for_render = False
psys.settings.use_strand_primitive = True
psys.settings.radius_scale = 0.2
psys.settings.hair_step = 3
psys.settings.count = 5

# Connect and disconnect (needed, but why?)
bpy.ops.particle.disconnect_hair(all=True)
bpy.ops.particle.connect_hair(all=True)

""" https://developer.blender.org/T58792 """
#https://docs.blender.org/api/blender2.8/bpy.types.Depsgraph.html  <--- relevant?
depsgraph = bpy.context.evaluated_depsgraph_get()
object_eval = obj.evaluated_get(depsgraph)
psys_eval = object_eval.particle_systems[0]

print('Number of particles:\n  %d  (psys)\n  %d  (psys_eval)' % (len(psys.particles), len(psys_eval.particles)))
psys = psys_eval
""" ------------------------------------------------------- """

# 3. Set hair positions vertices
for m in range(len(psys.particles)):
    psys.particles[m].location = (m, 0, 0)

    for n in range(len(psys.particles[m].hair_keys)):
        psys.particles[m].hair_keys[n].co = (m, 0, n)

# Toggle back and forth to update viewport
bpy.ops.particle.particle_edit_toggle()
bpy.ops.particle.particle_edit_toggle()
1 Like

(FYI: Updated the minimal example-code. Previous did not work as it should have in Blender 2.79 either. Now it does.)

I managed to change hair shape with python on Blender 2.83.2, but I used very dirty hacks for it

import bpy

obj = bpy.data.objects['Plane']

# Add a particle system
psys = obj.modifiers.new("hair", 'PARTICLE_SYSTEM').particle_system
psys.settings.type = 'HAIR'
psys.settings.use_strand_primitive = True
psys.settings.radius_scale = 0.2
psys.settings.hair_step = 3
psys.settings.count = 5

eobj = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())

""" https://developer.blender.org/T58792 """
#https://docs.blender.org/api/blender2.8/bpy.types.Depsgraph.html  <--- relevant?
bpy.context.view_layer.objects.active = obj
psys = eobj.particle_systems[0]

bpy.ops.particle.disconnect_hair(all=True)
bpy.ops.particle.connect_hair(all=True)
bpy.ops.particle.particle_edit_toggle() # We need to make changes in particle edit mode
psys.particles[0].hair_keys[0].co = (0, 0, 0) # do some small change
bpy.ops.particle.particle_edit_toggle()

# Very dirty hack! Use lattice modifier to mark hair particle system as "edited"
lattice = bpy.data.lattices.new("Lattice")
lattice_ob = bpy.data.objects.new("Lattice", lattice)
mod = eobj.modifiers.new("Lattice", 'LATTICE')
mod.object=lattice_ob;
bpy.ops.object.modifier_apply(modifier=mod.name)
bpy.data.objects.remove(lattice_ob)
bpy.data.lattices.remove(lattice)

# Set hair positions vertices

bpy.ops.particle.particle_edit_toggle() # We need to make changes in particle edit mode

for m in range(len(psys.particles)):
    for n in range(len(psys.particles[m].hair_keys)):
        psys.particles[m].hair_keys[n].co = (m, 0, n)

# For some reason we can't exit particle edit mode immediately :( But exiting by hand after hair generation works well
#bpy.ops.particle.particle_edit_toggle()