# Convert particle system to animated meshes

I've started trying to make a 3D model that I will end up exporting to an augmented reality app but I've hit a problem in terms of the particle system. The app wants the 3D file in .dae(collada) format which blender allows, but it doesn't seem to export particle systems. I've read that blender doesn't support exporting particle systems at all so I'm now focused on converting the particle system to lots of meshes which will (hopefully)animate using key frames. Then I can export all the meshes and the particles will 'work'.

Currently I'm just using a standard particle system emitting small icospheres. I've tried a few different techniques, such as 'make duplicates real' and using a particle instance instead but they don't seem to animate. Instead it just freezes the current frame into an object(which exports fine but is not what I want). I've also tried recording the animation in the blender game engine but particle systems don't work with it so that didn't work either.

Any help would be appreciated! Thanks

• If the particle system isn't doing anything particularly (no pun intended) interesting. Why not try rigid bodies? These can be keyframed easily. – iKlsR Nov 18 '13 at 12:32
• @iKlsR Good idea, though AFAIK there is no way to make rigid bodies appear like particles (e.g. get emitted from a plane over time) – gandalf3 Nov 18 '13 at 20:21
• I found this script which converts a particle system to rigid bodies: blenderartists.org/forum/… The only issue is that when I export this as a collada file the animations run instantly, almost like bullets. – roarster Nov 18 '13 at 20:35

I think the only way to do it is with some Python scripting. First select the object you want to show in place of the particles (dupli object) and then select the object with the particle system. Then run the following script. But be aware, it can get quite slow if you have lots of particles (for 5000 particles and Suzanne as dupli object, it took almost 4 minutes on my computer).

import bpy

# Set these to False if you don't want to key that property.
KEYFRAME_LOCATION = True
KEYFRAME_ROTATION = True
KEYFRAME_SCALE = True
KEYFRAME_VISIBILITY = True  # Viewport and render visibility.

def create_objects_for_particles(ps, obj):
# Duplicate the given object for every particle and return the duplicates.
# Use instances instead of full copies.
obj_list = []
mesh = obj.data
for i, _ in enumerate(ps.particles):
dupli = bpy.data.objects.new(
name="particle.{:03d}".format(i),
object_data=mesh)
obj_list.append(dupli)
return obj_list

def match_and_keyframe_objects(ps, obj_list, start_frame, end_frame):
# Match and keyframe the objects to the particles for every frame in the
# given range.
for frame in range(start_frame, end_frame + 1):
bpy.context.scene.frame_set(frame)
for p, obj in zip(ps.particles, obj_list):
match_object_to_particle(p, obj)
keyframe_obj(obj)

def match_object_to_particle(p, obj):
# Match the location, rotation, scale and visibility of the object to
# the particle.
loc = p.location
rot = p.rotation
size = p.size
if p.alive_state == 'ALIVE':
vis = True
else:
vis = False
obj.location = loc
# Set rotation mode to quaternion to match particle rotation.
obj.rotation_mode = 'QUATERNION'
obj.rotation_quaternion = rot
obj.scale = (size, size, size)
obj.hide = not(vis)
obj.hide_render = not(vis)

def keyframe_obj(obj):
# Keyframe location, rotation, scale and visibility if specified.
if KEYFRAME_LOCATION:
obj.keyframe_insert("location")
if KEYFRAME_ROTATION:
obj.keyframe_insert("rotation_quaternion")
if KEYFRAME_SCALE:
obj.keyframe_insert("scale")
if KEYFRAME_VISIBILITY:
obj.keyframe_insert("hide")
obj.keyframe_insert("hide_render")

def main():
# Assume only 2 objects are selected.
# The active object should be the one with the particle system.
ps_obj = bpy.context.object
obj = [obj for obj in bpy.context.selected_objects if obj != ps_obj][0]
ps = ps_obj.particle_systems[0]  # Assume only 1 particle system is present.
start_frame = bpy.context.scene.frame_start
end_frame = bpy.context.scene.frame_end
obj_list = create_objects_for_particles(ps, obj)
match_and_keyframe_objects(ps, obj_list, start_frame, end_frame)

if __name__ == '__main__':
main()

• This is amazing. Do you think it can be modified to work with hair particles ? – ChameleonScales Mar 5 '17 at 0:34

In case anyone is trying this in Blender 2.80, here's the updated version:

import bpy

# Set these to False if you don't want to key that property.
KEYFRAME_LOCATION = True
KEYFRAME_ROTATION = True
KEYFRAME_SCALE = True
KEYFRAME_VISIBILITY = True  # Viewport and render visibility.

def create_objects_for_particles(ps, obj):
# Duplicate the given object for every particle and return the duplicates.
# Use instances instead of full copies.
obj_list = []
mesh = obj.data
particles_coll = bpy.data.collections.new(name="particles")

for i, _ in enumerate(ps.particles):
dupli = bpy.data.objects.new(
name="particle.{:03d}".format(i),
object_data=mesh)
obj_list.append(dupli)
return obj_list

def match_and_keyframe_objects(ps, obj_list, start_frame, end_frame):
# Match and keyframe the objects to the particles for every frame in the
# given range.
for frame in range(start_frame, end_frame + 1):
print("frame {} processed".format(frame))
bpy.context.scene.frame_set(frame)
for p, obj in zip(ps.particles, obj_list):
match_object_to_particle(p, obj)
keyframe_obj(obj)

def match_object_to_particle(p, obj):
# Match the location, rotation, scale and visibility of the object to
# the particle.
loc = p.location
rot = p.rotation
size = p.size
if p.alive_state == 'ALIVE':
vis = True
else:
vis = False
obj.location = loc
# Set rotation mode to quaternion to match particle rotation.
obj.rotation_mode = 'QUATERNION'
obj.rotation_quaternion = rot
obj.scale = (size, size, size)
obj.hide_viewport = not(vis) # <<<-- this was called "hide" in <= 2.79
obj.hide_render = not(vis)

def keyframe_obj(obj):
# Keyframe location, rotation, scale and visibility if specified.
if KEYFRAME_LOCATION:
obj.keyframe_insert("location")
if KEYFRAME_ROTATION:
obj.keyframe_insert("rotation_quaternion")
if KEYFRAME_SCALE:
obj.keyframe_insert("scale")
if KEYFRAME_VISIBILITY:
obj.keyframe_insert("hide_viewport") # <<<-- this was called "hide" in <= 2.79
obj.keyframe_insert("hide_render")

def main():
#in 2.8 you need to evaluate the Dependency graph in order to get data from animation, modifiers, etc
depsgraph = bpy.context.evaluated_depsgraph_get()

# Assume only 2 objects are selected.
# The active object should be the one with the particle system.
ps_obj = bpy.context.object
ps_obj_evaluated = depsgraph.objects[ ps_obj.name ]
obj = [obj for obj in bpy.context.selected_objects if obj != ps_obj][0]
ps = ps_obj_evaluated.particle_systems[0]  # Assume only 1 particle system is present.
start_frame = bpy.context.scene.frame_start
end_frame = bpy.context.scene.frame_end
obj_list = create_objects_for_particles(ps, obj)
match_and_keyframe_objects(ps, obj_list, start_frame, end_frame)

if __name__ == '__main__':
main()

$$$$

• Thanks so much for this script! Lifesaver. For me it didn't seem to pick up the object scaling factor from the particle settings, but I may have done something wrong. I really think this function should/could be added to blender. So useful – Stephen Hamacek Dec 21 '19 at 3:05
• Hi - thanks very much for this script, works perfectly. Would it be possible to update it so that it would copy an object's armature (and the keyed poses of that with it?) If you can imagine trying to use this same process to bake a bird flock, as it stands it only copies the static geometry and none of the animations within the object. Any pointers would be much appreciated. – Nick Feb 16 at 17:32
• I think it should be possible. You would need to create two objects for each particle instance: Object A is of 'ARMATURE' type, object B is of 'MESH' type. Parent B to A. Add an armature modifier to B (with the armature Pointer property set to the object A). All the keyframes set in keyframe_obj(obj) should go to the object A. And you should also add keyframes to all the bones in A with something like: A.keyframe_insert('pose.bones["{}"].rotation_quaternion'.format(A.name), index=-1, frame=context.scene.frame_current). I might be loosing some part, but that's the idea – daylanKifky Feb 18 at 16:07
• Hi @daylanKifky thanks for your reply and tip. I have had a go at adapting your script along the lines of your approach, but in a slightly different way involving duplicating the mesh and armature. It works but with mixed results. For clarity, I have asked for advice on my script in a separate question, if you have any time to have a look it would be much appreciated, you can see that here blender.stackexchange.com/questions/167452/… – Nick Feb 20 at 8:25

Here's a modified version for 2.8 that keys visibility as scale and does all particle systems on the emitter object.

import bpy

# Set these to False if you don't want to key that property.
KEYFRAME_LOCATION = True
KEYFRAME_ROTATION = True
KEYFRAME_SCALE = True
KEYFRAME_VISIBILITY = False  # Viewport and render visibility.
KEYFRAME_VISIBILITY_SCALE = True

def create_objects_for_particles(ps, obj):
# Duplicate the given object for every particle and return the duplicates.
# Use instances instead of full copies.
obj_list = []
mesh = obj.data
particles_coll = bpy.data.collections.new(name="particles")

for i, _ in enumerate(ps.particles):
dupli = bpy.data.objects.new(
name="particle.{:03d}".format(i),
object_data=mesh)
obj_list.append(dupli)
return obj_list

def match_and_keyframe_objects(ps, obj_list, start_frame, end_frame):
# Match and keyframe the objects to the particles for every frame in the
# given range.
for frame in range(start_frame, end_frame + 1):
print("frame {} processed".format(frame))
bpy.context.scene.frame_set(frame)
for p, obj in zip(ps.particles, obj_list):
match_object_to_particle(p, obj)
keyframe_obj(obj)

def match_object_to_particle(p, obj):
# Match the location, rotation, scale and visibility of the object to
# the particle.
loc = p.location
rot = p.rotation
size = p.size
if p.alive_state == 'ALIVE':
vis = True
else:
vis = False
obj.location = loc
# Set rotation mode to quaternion to match particle rotation.
obj.rotation_mode = 'QUATERNION'
obj.rotation_quaternion = rot
if KEYFRAME_VISIBILITY_SCALE:
if vis:
obj.scale = (size, size, size)
if not vis:
obj.scale = (0.001, 0.001, 0.001)
obj.hide_viewport = not(vis) # <<<-- this was called "hide" in <= 2.79
obj.hide_render = not(vis)

def keyframe_obj(obj):
# Keyframe location, rotation, scale and visibility if specified.
if KEYFRAME_LOCATION:
obj.keyframe_insert("location")
if KEYFRAME_ROTATION:
obj.keyframe_insert("rotation_quaternion")
if KEYFRAME_SCALE:
obj.keyframe_insert("scale")
if KEYFRAME_VISIBILITY:
obj.keyframe_insert("hide_viewport") # <<<-- this was called "hide" in <= 2.79
obj.keyframe_insert("hide_render")

def main():
#in 2.8 you need to evaluate the Dependency graph in order to get data from animation, modifiers, etc
depsgraph = bpy.context.evaluated_depsgraph_get()

# Assume only 2 objects are selected.
# The active object should be the one with the particle system.
ps_obj = bpy.context.object
ps_obj_evaluated = depsgraph.objects[ ps_obj.name ]
obj = [obj for obj in bpy.context.selected_objects if obj != ps_obj][0]

for psy in ps_obj_evaluated.particle_systems:
ps = psy  # Assume only 1 particle system is present.
start_frame = bpy.context.scene.frame_start
end_frame = bpy.context.scene.frame_end
obj_list = create_objects_for_particles(ps, obj)
match_and_keyframe_objects(ps, obj_list, start_frame, end_frame)

if __name__ == '__main__':
main()
`

To convert just a single frame of the animation:

With the emitting object selected, press Ctrl+Shift+A to make all the emitted objects real (convert them from particles into actual objects)

• That will convert a single frame to a static mesh but roarster is after exporting the animation of the particles. – sambler Nov 20 '13 at 11:14
• True, I assumed this would work for animated particles too :/ – Greg Zaal Nov 20 '13 at 11:32

I think it would be simpler and easier to attach a particle system to the blender generated geometry directly in the AR application.

You only import the object without any particle system from blender. Then in your AR app, you add a particle system to this object.

For iOS for example in ARKit, you can add a particle system to a SCNNode.

I'm trying to do exactly the same thing right now, I'll let you know. I was able to add a smoke + fire particle system in blender to a rocket, then was frustated that it would be exported in collada .dae as dumb boxes (the particle system smoke domain). So I'll try to generate the same particle system directly in iOS.