[2.5] "Freezing" a particle system

This is probably so basic, but I’m trying to figure out how to freeze a particle system on a certain frame.

I have a particle system generating a cloud via Point Density texture, but I don’t want the particle system to continue animating. I need the state of the particle system to freeze at frame 3 or 4.

What’s the best way to do this?

Attachments



I was just looking into the same issue.

I notice there is a “Time Offset” and a “Time Offset Particle” parameter to each object.

(Look in outliner->datablock->objects)

Maybe you can freeze time by animating the time offset? I’m not sure if that’s how it works though.

This page explains how to freeze a particle system in blender 2.4x using the time IPO curve.

http://wiki.blender.org/index.php/Doc:Manual/Animation/Basic/Tools/Ipo_Curves_and_Keyframes

“The Time curve is especially interesting for particle systems, allowing you to “freeze” the particles or to animate particles absorbed by an object instead of emitted.”

Does anybody know how to do this in blender 2.5?

Well if it is 2.5 are not all parameters animatable? If so, animate the velocities to 0 before you reach the frame you want it to freeze on.

Thanks for the suggestion Atom.

I tried animating the ‘Normal’ value for velocity. However this affects only the emission speed of the particles. You can freeze the emission by setting this value to 0, but it doesn’t affect the particles that are already moving.

It did however lead me to a solution that seems to work although a bit ‘hacky’…
If you animate the ‘Damp’ value under ‘physics’ you can stop the particles.

Let’s say you want to freeze your particles on frame 5:

  • Add a keyframe for frame 1 setting damp to 0
  • Add a keyframe for frame 4 setting damp to 0
  • Add a keyframe for frame 5 setting damp to 1

You should also set the end frame for emission to 5. If not, new particles will be emitted but stay on the surface of the object.

This solves the problem for me, but let me know if anyone has a simpler solution.

I will have a lot of these particle systems, so individually animating properties sounds like a pain.

As a solution, I implemented simple “XYZ” file format import/export scripts. It exports objects (including their particle systems) as point clouds, and imports them as vertex-only objects, ready for use in Point Density textures.

Maybe someone else will find these useful. To use them, place them in …/.blender/scripts/io/

Tested only on Mac OS X Blender 2.5 x86_64

export_xyz.py

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

"""
Name: 'XYZ (.xyz)...'
Blender: 248
Group: 'Export'
Tooltip: 'Save a Generic XYZ File'
"""

__author__ = "Ben Syverson"
__url__ = ['www.blender.org']
__version__ = "1.0"

__bpydoc__ = """\
Exports XYZ data
"""

import os
import time

import bpy
import Mathutils

def splitExt(path):
    dotidx = path.rfind('.')
    if dotidx == -1:
        return path, ''
    else:
        return path[:dotidx], path[dotidx:]

def fixName(name):
    if name == None:
        return 'None'
    else:
        return name.replace(' ', '_')

def write(filename, objects, scene,
            EXPORT_APPLY_MODIFIERS=True,
            EXPORT_VERTS=True,
            EXPORT_PARTICLES_AS_POINT_CLOUD=False):
    '''
    Basic write function. The context and options must be alredy set
    This can be accessed externaly
    eg.
    write( 'c:\	est\\foobar.xyz', Blender.Object.GetSelected() ) # Using default options.
    '''

    import math

    print('XYZ Export path: "%s"' % filename)
    temp_mesh_name = '~tmp-mesh'

    time1 = time.clock()

    file = open(filename, "w")

    # Write Header
    version = "2.5"
    file.write('# Blender3D v%s XYZ File: %s
' % (version, bpy.data.filename.split('/')[-1].split('\\')[-1] ))
    file.write('# www.blender3d.org
')

    # Get all meshes
    for ob_main in objects:
        obs = []
        if ob_main.dupli_type != 'NONE':
            # XXX
            print('creating dupli_list on', ob_main.name)
            ob_main.create_dupli_list()

            obs = [(dob.object, dob.matrix) for dob in ob_main.dupli_list]
        else:
            obs = [(ob_main, ob_main.matrix)]

        for ob, ob_mat in obs:

            if ob.type != 'MESH':
                continue

            me = ob.create_mesh(EXPORT_APPLY_MODIFIERS, 'PREVIEW')

            # Vert
            if EXPORT_VERTS:
                file.write('# %s
' % ob_main.name) # Write Object name
                for v in me.verts:
                    file.write('%.6f %.6f %.6f
' % tuple(v.co))

            # Write particles
            if EXPORT_PARTICLES_AS_POINT_CLOUD:
                particle_systems = ob.particle_systems;
                for particle_system in particle_systems:
                    file.write('# %s
' % particle_system.name) # Write Object name
                    for particle in particle_system.particles:
                        file.write('%.6f %.6f %.6f
' % tuple(particle.location))

            # clean up
            bpy.data.remove_mesh(me)

        if ob_main.dupli_type != 'NONE':
            ob_main.free_dupli_list()

    file.close()

    print("XYZ Export time: %.2f" % (time.clock() - time1))

def do_export(filename, context,
              EXPORT_APPLY_MODIFIERS = True, # not used
              EXPORT_SEL_ONLY = True, # ok
              EXPORT_VERTS = True,
              EXPORT_PARTICLES_AS_POINT_CLOUD = False):

    base_name, ext = splitExt(filename)
    context_name = [base_name, '', '', ext] # Base name, scene name, frame number, extension

    orig_scene = context.scene

    export_scenes = [orig_scene]

    if EXPORT_SEL_ONLY:
        export_objects = context.selected_objects
    else:
        export_objects = context.scene.objects

    full_path= ''.join(context_name)

    write(full_path, export_objects, context.scene, EXPORT_APPLY_MODIFIERS, EXPORT_VERTS, EXPORT_PARTICLES_AS_POINT_CLOUD)

from bpy.props import *

class ExportXYZ(bpy.types.Operator):
    '''Save a XYZ File'''

    bl_idname = "export.xyz"
    bl_label = 'Export XYZ'

    path = StringProperty(name="File Path", description="File path used for exporting the XYZ file", maxlen= 1024, default= "")

    # context group
    use_selection = BoolProperty(name="Selection Only", description="", default= False)

    # object group
    use_modifiers = BoolProperty(name="Apply Modifiers", description="", default= True)
    use_verts = BoolProperty(name="Vertices", description="Export Vertices", default=True)
    use_particles = BoolProperty(name="Particles", description="Export Particles as Point Cloud", default=False)

    def execute(self, context):

        do_export(self.properties.path, context,
                  EXPORT_APPLY_MODIFIERS=self.properties.use_modifiers,
                  EXPORT_SEL_ONLY=self.properties.use_selection,
                  EXPORT_VERTS=self.properties.use_verts,
                  EXPORT_PARTICLES_AS_POINT_CLOUD=self.properties.use_particles)

        return ('FINISHED',)

    def invoke(self, context, event):
        wm = context.manager
        wm.add_fileselect(self)
        return ('RUNNING_MODAL',)

bpy.ops.add(ExportXYZ)

import dynamic_menu

def menu_func(self, context):
    default_path = bpy.data.filename.replace(".blend", ".xyz")
    self.layout.operator(ExportXYZ.bl_idname, text="XYZ (.xyz)...").path = default_path

menu_item = dynamic_menu.add(bpy.types.INFO_MT_file_export, menu_func)

if __name__ == "__main__":
    bpy.ops.EXPORT_OT_XYZ(filename="/tmp/test.xyz")

import_xyz.py

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

"""
Name: 'XYZ (.xyz)...'
Blender: 248
Group: 'Import'
Tooltip: 'Import a Generic XYZ File'
"""

__author__ = "Ben Syverson"
__url__ = ['www.blender.org']
__version__ = "1.0"

__bpydoc__ = """\
Imports XYZ data
"""

import os
import time

import bpy
import Mathutils


def splitExt(path):
    dotidx = path.rfind('.')
    if dotidx == -1:
        return path, ''
    else:
        return path[:dotidx], path[dotidx:]

def fixName(name):
    if name == None:
        return 'None'
    else:
        return name.replace(' ', '_')
        
def stripFile(path):
    '''Return directory, where the file is'''
    lastSlash= max(path.rfind('\\'), path.rfind('/'))
    if lastSlash != -1:
        path= path[:lastSlash]
    return '%s%s' % (path, os.sep)

def stripPath(path):
    '''Strips the slashes from the back of a string'''
    return path.split('/')[-1].split('\\')[-1]


def unpack_list(list_of_tuples):
    l = []
    for t in list_of_tuples:
        l.extend(t)
    return l

def create_mesh(scn, meshname, verts_loc):
    print('creating mesh %s with %f verts' % (meshname, len(verts_loc)))
    me = bpy.data.add_mesh(meshname)
    me.add_geometry(len(verts_loc), 0, 0)
    me.verts.foreach_set("co", unpack_list(verts_loc))
    me.update()
    
    ob = bpy.data.add_object("MESH", meshname)
    ob.data = me
    scn.objects.link(ob)

def load_xyz(filename, context,
              SPLIT_OBJECTS = True):
    '''
    Called by the user interface or another script.
    load_obj(path) - should give acceptable results.
    This function passes the file and sends the data off
        to be split into objects and then converted into mesh objects
    '''
    print('
importing xyz "%s"' % filename)
    time_main= time.time()
    verts_loc= []
    file = open(filename, 'rU')
    
    scene = context.scene
    
    current_object = filename;
    for line in file: #.xreadlines():
        line = line.lstrip() # rare cases there is white space at the start of the line
        
        line_split = line.split()
        
        if line.startswith('#'):
            print('hash line: %s' % line)
            if (len(verts_loc) > 0) and SPLIT_OBJECTS:
                create_mesh(scene, current_object, verts_loc)
                verts_loc = []
            current_object = line_split[1]
        else:
            verts_loc.append( (float(line_split[0]), float(line_split[1]), float(line_split[2])) )
            
    if len(verts_loc) > 0:
        create_mesh(scene, current_object, verts_loc)

    file.close()
    time_new= time.time()
    print('%.4f sec' % (time_new-time_main))
    time_sub= time_new


from bpy.props import *

class ImportXYZ(bpy.types.Operator):
    '''Load a XYZ File'''

    bl_idname = "import.xyz"
    bl_label = 'Import XYZ'

    path = StringProperty(name="File Path", description="File path used for importing the XYZ file", maxlen= 1024, default= "")

    # context group
    SPLIT_OBJECTS = BoolProperty(name="Split Objects", description="Use # comments as object dividers", default= True)

    def execute(self, context):

        load_xyz(self.properties.path, context,
                  self.properties.SPLIT_OBJECTS)

        return ('FINISHED',)

    def invoke(self, context, event):
        wm = context.manager
        wm.add_fileselect(self)
        return ('RUNNING_MODAL',)

bpy.ops.add(ImportXYZ)

import dynamic_menu

def menu_func(self, context):
    self.layout.operator(ImportXYZ.bl_idname, text="XYZ (.xyz)...")

menu_item = dynamic_menu.add(bpy.types.INFO_MT_file_import, menu_func)

if __name__ == "__main__":
    bpy.ops.EXPORT_OT_XYZ(filename="/tmp/test.xyz")

That sounds like a more robust solution. At least if your modelling an entire cloudy sky…

Thanks for sharing your script. I will give it a try later.

As a follow-up to Atom’s suggestion, you could add and use a wind force field instead of using Normal velocity, set the Flow to 1 and the animate the Strength. You can even get the particles to move backwards if necessary.

Here are four particle systems frozen (via XYZ) and combined. I’m really loving the volume rendering tools in 2.5!

Attachments


bensyverson:

How do you use the export script?

I added the files to the scripts folder, then added them in the ‘tool shelf’.
I have tried to export a cube with a particle system, but only the cube vertices get exported.
Are there any special settings I need to use?
I’m using the 2.5 alpha 0 on ubuntu

Thanks

dsmex, when you export, you need to check “Particles” and uncheck “Vertices” – that way only the particles will be exported, not the vertices.

bensyverson:

Thank you.
I got it working now. Not sure what I did. I didn’t notice the export options at first, but they seem to have particles set by default. Anyway… now it’s working. The scripts are great. I’ll start building my cloud collection…

such useful scripts, thank you

I don’t say this script is bad, but why do you have to complicate so much? Just make start and end of emiting <0 (like -100 and ending at -50) and click on “Died” so they’ll stay visible after ending.

@r-a because he also wants to freeze the already emitted particles in place, which the emission does not effect.

You can freeze the particles by animating the Tweak Value in the Physics panel of the particle system. If you set it to 0 the particles will freeze in place. You still will also need to make sure they have a long lifetime (or click died under render panel in particle system) as they will still disappear after their lifetime value is up. They will also continue to emit so you should set your end emit frame to the same frame you want to freeze your particles.

So basic steps are:

  1. Set Tweak to 1 (it’s the default) until it’s at the frame you want to freeze then insert a keyframe on the Tweak setting
  2. Go to next key frame, set Tweak to 0 and insert another keyframe.
  3. Set the particle emission end frame to the same frame number as the one your froze the particles on.

Why not call tweak time enabled?

The script don’t work for me i’m on svn r28242 on a 32 bits linux. I changed Mathutils to mathutils but it didn’t help.

Hi, how do you get the DOF to work when comping cloud layers together with other meshes. I have used the Defocus node, as it appears blender only renders the cube that surrounds the cloud in the Z and not the cloud, which really messes with DOF effect.
Does anyone have an idea as to what I have done wrong, thanks in advance:eyebrowlift:.