20
$\begingroup$

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

$\endgroup$
3
  • 1
    $\begingroup$ If the particle system isn't doing anything particularly (no pun intended) interesting. Why not try rigid bodies? These can be keyframed easily. $\endgroup$
    – iKlsR
    Commented Nov 18, 2013 at 12:32
  • 1
    $\begingroup$ @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) $\endgroup$
    – gandalf3
    Commented Nov 18, 2013 at 20:21
  • 1
    $\begingroup$ 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. $\endgroup$
    – roarster
    Commented Nov 18, 2013 at 20:35

5 Answers 5

25
$\begingroup$

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)
        bpy.context.scene.objects.link(dupli)
        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()
$\endgroup$
2
  • $\begingroup$ This is amazing. Do you think it can be modified to work with hair particles ? $\endgroup$ Commented Mar 5, 2017 at 0:34
  • $\begingroup$ This works perfectly for me. $\endgroup$ Commented Jun 24, 2022 at 11:50
19
$\begingroup$

Version - Visibility by Scale

Works for 3.4

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

One of the advantage to control visibility by Scale (and not by Viewport/Render visibility) is ability to be handled by more exporters.

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")
    bpy.context.scene.collection.children.link(particles_coll)

    for i, _ in enumerate(ps.particles):
        dupli = bpy.data.objects.new(
                    name="particle.{:03d}".format(i),
                    object_data=mesh)
        particles_coll.objects.link(dupli)
        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()
$\endgroup$
7
  • $\begingroup$ Thank you, it was very helpful $\endgroup$
    – BuzzKirill
    Commented Aug 26, 2020 at 9:23
  • $\begingroup$ Please update this for Blender 3.0 $\endgroup$
    – Omeleto
    Commented Jan 11, 2022 at 6:28
  • $\begingroup$ I found this via a youtube video, and the issue with the scale for particle life is working great. Upvoting. Thank you. $\endgroup$ Commented Jun 23, 2022 at 11:11
  • $\begingroup$ @Omeleto ... works for me under 3.4 ... BTW +1 ... I also prefer to control visibility by Scale (widely supported in exporters) ... Thanks $\endgroup$
    – vklidu
    Commented Dec 10, 2022 at 9:11
  • $\begingroup$ How to do the same but with a collection as instances? So multiple different objects being instanced in one particle system? $\endgroup$
    – lajawi
    Commented Apr 17 at 17:41
16
$\begingroup$

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")
    bpy.context.scene.collection.children.link(particles_coll)

    for i, _ in enumerate(ps.particles):
        dupli = bpy.data.objects.new(
                    name="particle.{:03d}".format(i),
                    object_data=mesh)
        particles_coll.objects.link(dupli)
        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()

```
$\endgroup$
4
  • $\begingroup$ 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 $\endgroup$ Commented Dec 21, 2019 at 3:05
  • $\begingroup$ 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. $\endgroup$
    – Nick
    Commented Feb 16, 2020 at 17:32
  • $\begingroup$ 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 $\endgroup$ Commented Feb 18, 2020 at 16:07
  • $\begingroup$ 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/… $\endgroup$
    – Nick
    Commented Feb 20, 2020 at 8:25
1
$\begingroup$

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)

$\endgroup$
2
  • 3
    $\begingroup$ That will convert a single frame to a static mesh but roarster is after exporting the animation of the particles. $\endgroup$
    – sambler
    Commented Nov 20, 2013 at 11:14
  • $\begingroup$ True, I assumed this would work for animated particles too :/ $\endgroup$
    – Greg Zaal
    Commented Nov 20, 2013 at 11:32
1
$\begingroup$

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.

$\endgroup$

You must log in to answer this question.

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