17
$\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$
  • 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 Nov 18 '13 at 12:32
  • $\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 Nov 18 '13 at 20:21
  • $\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 Nov 18 '13 at 20:35
16
$\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()
| improve this answer | |
$\endgroup$
  • $\begingroup$ This is amazing. Do you think it can be modified to work with hair particles ? $\endgroup$ – ChameleonScales Mar 5 '17 at 0:34
8
$\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()

```
| improve this answer | |
$\endgroup$
  • $\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$ – Stephen Hamacek Dec 21 '19 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 Feb 16 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$ – daylanKifky Feb 18 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 Feb 20 at 8:25
3
$\begingroup$

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")
    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()
| improve this answer | |
$\endgroup$
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)

| improve this answer | |
$\endgroup$
  • 2
    $\begingroup$ That will convert a single frame to a static mesh but roarster is after exporting the animation of the particles. $\endgroup$ – sambler Nov 20 '13 at 11:14
  • $\begingroup$ True, I assumed this would work for animated particles too :/ $\endgroup$ – Greg Zaal Nov 20 '13 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.

| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.