Manipulating particles in python

What about a gradient texture in Z?

could work, not slope tho

True, it would be just height

Hi colleagues, let´s see if you can help me,
following this great page I have been able to control the positions of a particle system using python, and it works perfectly and wonderfully

The problem comes when I want to render an animation that contains this. It will simply not take the positions that Im setting with python.

If in the timeline I select one frame, and then move to next frame and then render a single frame then it works. But If I render the entire animation it will not show the positions I set with python.

And if I render frames one by one from python same problem, it will simply not show them.

How can I make sure that when rendering the entire animation, 200 frames or similar, the positions of the particles will be the ones Im setting in python,

as I say, in the viewport all works perfect, the positions are taken from the python script and all works perfect

Im using your particlesetter code:

def particleSetter(self):
particle_systems = object.evaluated_get(degp).particle_systems
particles = particle_systems[0].particles
totalParticles = len(particles)

scene = bpy.context.scene
cFrame = scene.frame_current
sFrame = scene.frame_start

# at start-frame, clear the particle cache
if cFrame == sFrame:
    psSeed = object.particle_systems[0].seed
    object.particle_systems[0].seed = psSeed
    
f=cFrame
c=0
c2=1

totalData=20
data = np.zeros(shape=(totalParticles,3))
for i in range(totalParticles):
    if (c2==(totalData)): 
        c2=1 
        
    x=easeInOutExpo(float(f), float(cxa[c]),float(cxb[c2])-float(cxa[c]),frange)     
    y=easeInOutExpo(float(f), float(cya[c]),float(cyb[c2])-float(cya[c]),frange) 
    z=easeInOutExpo(float(f), float(cza[c]),float(czb[c2])-float(cza[c]),frange)
    data[c]= np.array([x, y, z]) 
    c=c+1
    c2=c2+1

flatList = data.ravel()


# Set the location of all particle locations to flatList
particles.foreach_set("location", flatList)

this is great and works perfectly for me,
but how do you make it so that when you render the animation the coordinates are taken from the script, because for me all works and behaves perfectly on the viewport, and also if I render a single frame (after moving from the previous one though), but if I try to render the entire 200 frames as an animation, (or frame by frame from python), it just wont work, it wont take then the position coordinates from the python script (Im using your same code), any tips? thank you :wink:

Hi Javier!

I only utilized the solution provided by 3DSinghVFX, saw that positions were properly set for viewport and could render a single frame. However, I have not tried rendering animations from it.

(For the moment I’ve actually reverted to 2.79 because I couldn’t get this to work for hair particles. Seems like this depsgraph-thing is still evolving. When it’s stable and easy to set particle positions explicitly I’ll return to 2.8x).

Good luck!

^An update to my earlier issue with the Python API — by changing from Hair to Emitter mode, and then changing the Frame Start and End properties both to 1, I’m able to export the static scene that I wanted.

Still not sure whether there was something I could have done in my Python script to access the actual state of the (not-animated) scene, while staying in Hair mode. It seems like a bug to me, but I’m still not sure.

@javismiles Not sure if you’re still interested, but I thought I’d post this for anyone coming here from Google (as I did).

This answer came from john-l on blender.chat, apparently you should use the depsgraph given to the frame_change_post callback instead of creating your own, as externally created depsgraphs are always for ‘VIEWPORT’ instead of ‘RENDER’. Also, you’ll need at least 2.81, as frame_change_post doesn’t give its depsgraph in 2.80.

Here’s the code from @3DSinghVFX with john-l’s updates:

import bpy
import numpy as np

object = bpy.data.objects["Cube"]

def particleSetter(scene, degp):
    particle_systems = object.evaluated_get(degp).particle_systems
    particles = particle_systems[0].particles
    totalParticles = len(particles)

    cFrame = scene.frame_current
    sFrame = scene.frame_start
    
    #at start-frame, clear the particle cache
    if cFrame == sFrame:
        psSeed = object.particle_systems[0].seed 
        object.particle_systems[0].seed  = psSeed
    
    # sin function as location of particles            
    data = 5.0*np.sin(cFrame/20.0)
    flatList = [data]*(3*totalParticles)

    # additionally set the location of all particle locations to flatList
    particles.foreach_set("location", flatList)

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

#run the function on each frame
bpy.app.handlers.frame_change_post.append(particleSetter)
2 Likes

Hello,

Have anyone found a way to set hair_keys coordinates (not only locations)? I’ve tried solution from here, based on one of code snippets from this forum thread, but it didn’t work for me.

Thanks for code snippets presented

@bdax I’ve implemented your attached code but it doesn’t seem to work for me.I’m not sure if there’s change in API since your answer but code doesn’t give any error so i assume that is not the case. When I render animation it doesn’t take new locations into account. While in View-port it work fine. Any way to render the animation. I’m using blender 2.82 on windows.

You can try a little script along these lines:

class CM_OT_RenderAnimation(bpy.types.Operator):
    bl_idname = "cm_audio.render_animation"
    bl_label = "Render Animation"

    @classmethod
    def poll(cls, context):
        cm_node = context.node
        return cm_node.render_dir not in ["", "//"]

    def execute(self, context):
        scene = context.scene
        cm_node = context.node
        path = bpy.path.abspath(cm_node.render_dir)
        scene.render.filepath = path
        bpy.context.scene.frame_current = cm_node.strt_frame

        for i in range(cm_node.strt_frame, cm_node.stop_frame + 1):
            scene.frame_set(i)
            scene.render.filepath = f"{path}render{i}"
            bpy.ops.render.render(write_still = True)

        return {"FINISHED"}

This one runs as an operator on a node, in essence you just need to feed your script with a file path variable and start & stop frames, here is my node:

Screenshot 2020-05-12 at 14.12.38

You can run this as a script in Text Editor, or in the UI, or wherever you like. This produces a png file for each frame, which you assemble in VSE (just add the whole lot in one go).

3 Likes

Works Like Charms :slight_smile: Thanks a lot @clockmender . I thought of rendering each frame using python but i hesitated to do it thinking that it will probably not work. I have to keep in mind not to hesitate of trying next time.
I’ve attached the script when executed it will render every frame from strat frame to end frame and store in folder named “Render”. This folder will be at blend file location.

import bpy

scene = bpy.context.scene
fr_start = scene.frame_start
fr_end = scene.frame_end
for i in range(fr_start,fr_end+1):
    scene.frame_set(i)
    scene.render.filepath = "//Render/" + str(i)
    bpy.ops.render.render(write_still =True)
1 Like

Hey, has anyone gotten motion blur working for particles set from Python? I’ve tried to set “velocity” and “prev_velocity” but so far no motion blur for particles…

1 Like

I do not have an emitter, but I have particles inside a container. Kindly help me on how to determine their locations.

Hello,
What I am trying to achieve is to spawn particles on an object and then have them there rest statically.
But there is a problem… I do this by appending this function in my script:

bpy.app.handlers.frame_change_pre.append(particleSetter)

but with this function you can’t run it on multiple objects with different particle positions and sizes.
Is there any better way to spawn instanced objects (with custom scale and rotation) as fast as particles or does one know how to come around with the particles on multiple objects.
I hope I explained my problem well enough for one to understand it at least.

This is part of the code I’m using which I found on this thread:

        def particleSetter(scene, degp):
            particle_object = bpy.data.objects.get(MainObject.name)
            scene = bpy.context.scene
            particles.foreach_set("size", sizes)
            particles.foreach_set("location", locations)

The problem comes when I clear the cache with this line:

bpy.app.handlers.frame_change_post.clear() <— This line then clears particles of other objects, which makes it unusable on multiple objects and without this line the script starts giving errors since the function is unable to set the particles for 2 objects at once.
bpy.app.handlers.frame_change_pre.append(particleSetter)
bpy.app.handlers.frame_change_post.append(particleSetter)

Maybe there is a better approach to all of this that I am unfamiliar of?
My main reason for using particles is their speed.
Thanks in advance for any help.