7
$\begingroup$

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: In Blender 2.8, what is currently the best way to set the hair strands’ position and shape programmatically?


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: Left: Blender 2.79 - Right: Blender2.80

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()

Note: I posted this exact same question on BlenderArtist.org, but afterwards read that StackExchange may be more suited for this type of question so I'll attempt here as well.

$\endgroup$
1

1 Answer 1

2
$\begingroup$

Halfway there now…

Following this answer for manipulating particles, and shoehorning this problem with the same approach led to the following:

viewport, render preview and final render

  • Viewport: OK! :D
  • Render preview: OK! :D
  • Final render: Not OK... :(

Code:

import bpy

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

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

# 2½. Animation options
psys.settings.frame_start = 1
psys.settings.frame_end = 1
psys.settings.lifetime = 1000

# 3. Setup frame_change_post-callback
depsgraph = bpy.context.evaluated_depsgraph_get()           # Get depsgraph

# Define particle setter. Set at every frame update(?)
def particleSetter(self):
    particle_systems = obj.evaluated_get(depsgraph).particle_systems

    for m, particle in enumerate(particle_systems[0].particles):
        setattr(particle, "location", (m, 0, 0))
        for n in range(len(particle.hair_keys)):
            setattr(particle.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()

# Clear the post frame handler
bpy.app.handlers.frame_change_post.clear()

# Register our particleSetter with the post frame handler
bpy.app.handlers.frame_change_post.append(particleSetter)

# Trigger frame update
bpy.context.scene.frame_current = 0
$\endgroup$

You must log in to answer this question.

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