Accessing Particle Effects / Systems from code

Hi,

after loading a particle effect like:

            p = ParticleEffect()
            p.loadConfig("smokering.ptf")

How do i access it’s properties? What I want to do is to create some base system and alter some of it’s stats depending on it’s final use with my code (mainly stuff related to it’s scale).

Hello Christoph,

If all you want to do is manipulate the transform data of your particle system, then you do so like you would for any other nodepath:

p = ParticleEffect()
p.loadConfig("smokering.ptf")
p.start(parent = render, renderParent = render)
#you want to manipulate the scale:
p.setScale(scale)
#hpr:
p.setHpr(hpr)
#pos:
p.setPos(pos)
#etc.

For all other parameters of the particle system, I would suggest using the particle panel to set those [it’s use is explained in section 21. of the manual], as it is much more convenient than doing it manually, and then loading your system into the scene graph.

Looks like i tried to solve something simple a complicated way :open_mouth:

Don’t worry bro, you’ll be a pro at this stuff in no time. :smiley:

Yes - I just started.

I got my particles now somewhat right. However I’d like to have some particle System that runs only once (and then dies) and does not restart ?

To stop a particle effect:

p.disable()

To completely remove a particle effect:

p.cleanup()

One way to do what you want is to manually stop the particle effect using a task. You’d need to know how long your particle effect is going to last, say for instance 5 seconds. After starting your particle effect, start the task that checks if 5 seconds or more have elapsed since the particle effect started playing. If five seconds or more have elapsed, then kill the particle effect, using p.disable() or p.cleanup() if you want to completely remove the particle effect, after that, remove the task as well:

#function to end particle effect:
counter=0
def manipulate_particle_system (time_condition,task):
   global counter
   if(counter>=time_condition):
      #end the particle effect:
      p.disable()
      #end the task:
      taskMgr.remove("task-name")
   counter+=1
   return task.again
#start the particle effect:
p.start(parent = render, renderParent = render)
#start your task, make it executed every second:
taskMgr.doMethodLater(1,manipulate_particle_system, "task-name", extraArgs=[5], appendTask=True)

That’s one way to do it, though I’m not sure if its the best way! :slight_smile:

Why not just (untested):

taskMgr.doMethodLater(5, lambda task: (p.disable(), task.done)[1], "task")

I don’t see a reason to check the status each second, if the amount of seconds to run is given.

However, looking at the api, there seems to be:
set_spawn_on_death_flag()

Calling that with False will probably prevent it from respawning.

Wouldn’t a Sequence be better for this?

Sequence(Wait(5.0), Func(p.disable)).start()

Setting that flag causes the particle system to run - then python crashes the moment it should stop… hmm.

Can you make a small example showing the issue?

Sure, here you go, I did some minimal example:

Code: crash.py

from direct.showbase.ShowBase import ShowBase
from panda3d.core import *

from panda3d.physics import *
from direct.particles.Particles import Particles
from direct.particles.ParticleEffect import ParticleEffect
from direct.particles.ForceGroup import ForceGroup
 
class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        #Hintergrundfarbe setzen
        self.setBackgroundColor(0, 0, 0)
        base.disableMouse()
        #Particles enabled
        base.enableParticles()
        self.camera.setPos(0, 0, 20)
        self.camera.setHpr(0, -90, 0)
        
                    
        pRing = ParticleEffect()
        pRing.loadConfig("crash.ptf")
        pRing.start(parent = render, renderParent = render)
 
 
app = MyApp()
app.run()

Particles: crash.ptf

If you set p0.setSpawnOnDeathFlag(1) -> p0.setSpawnOnDeathFlag(0) it will run smooth. “smoke.png” is the supplied one with the particle example.

self.reset()
self.setPos(0.000, 0.000, 0.000)
self.setHpr(0.000, 0.000, 0.000)
self.setScale(1.000, 1.000, 1.000)
p0 = Particles.Particles('particles-1')
# Particles parameters
p0.setFactory("PointParticleFactory")
p0.setRenderer("SpriteParticleRenderer")
p0.setEmitter("SphereSurfaceEmitter")
p0.setPoolSize(25)
p0.setBirthRate(0.0010)
p0.setLitterSize(5)
p0.setLitterSpread(0)
p0.setSystemLifespan(2.0000)
p0.setLocalVelocityFlag(0)
p0.setSystemGrowsOlderFlag(0)
p0.setSpawnOnDeathFlag(1)
# Factory parameters
p0.factory.setLifespanBase(0.7500)
p0.factory.setLifespanSpread(0.0000)
p0.factory.setMassBase(1.0000)
p0.factory.setMassSpread(0.0000)
p0.factory.setTerminalVelocityBase(400.0000)
p0.factory.setTerminalVelocitySpread(0.0000)
# Point factory parameters
# Renderer parameters
p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAINOUT)
p0.renderer.setUserAlpha(0.50)
# Sprite parameters
p0.renderer.addTextureFromFile('smoke.png')
p0.renderer.setColor(Vec4(1.00, 1.00, 1.00, 1.00))
p0.renderer.setXScaleFlag(1)
p0.renderer.setYScaleFlag(1)
p0.renderer.setAnimAngleFlag(0)
p0.renderer.setInitialXScale(0.1220)
p0.renderer.setFinalXScale(0.1400)
p0.renderer.setInitialYScale(0.1220)
p0.renderer.setFinalYScale(0.1400)
p0.renderer.setNonanimatedTheta(0.0000)
p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPNOBLEND)
p0.renderer.setAlphaDisable(0)
p0.renderer.setColorBlendMode(ColorBlendAttrib.MAdd, ColorBlendAttrib.OIncomingAlpha, ColorBlendAttrib.OOneMinusIncomingAlpha)
# Emitter parameters
p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
p0.emitter.setAmplitude(1.0000)
p0.emitter.setAmplitudeSpread(0.5000)
p0.emitter.setOffsetForce(Vec3(0.0000, 0.0000, 1.2000))
p0.emitter.setExplicitLaunchVector(Vec3(1.0000, 0.0000, 0.0000))
p0.emitter.setRadiateOrigin(Point3(0.0000, 0.0000, 0.0000))
# Sphere Surface parameters
p0.emitter.setRadius(0.1000)
self.addParticles(p0)
f0 = ForceGroup.ForceGroup('vertex')
# Force parameters
force0 = LinearVectorForce(Vec3(0.0000, 0.0000, 1.0000), 0.0500, 0)
force0.setVectorMasks(1, 1, 1)
force0.setActive(1)
f0.addForce(force0)
self.addForceGroup(f0)

I have submitted a patch for review, which should fix the crash.
The issue was that you didn’t assign spawn templates. When you set spawnOnDeath to True, it will respawn
one of the assigned spawn templates. However, if you set no spawn templates, there will be a modulo by zero, causing a crash.

However, for your case, if you don’t want the particle system to respawn, you should use set_spawn_on_death_flag(False) I believe.

Ok, what are spawn templates ?

Just wondering.

Btw: Using set_spawn_on_death(False) instead does make the system respawn, setting it to True will crash it as well.

Where does the version with underscores come from ? I just found the setSpawnOnDeathFlag instead in the API.

I’m not sure what spawn templates are, I couldn’t find any api reference to it, I just found them in the code.
From what I understood, when the particle system stops, it will select a random template from the list of assigned spawn templates, and spawn that. (You can add them with add_spawn_template).

Hm, I’d expect set_spawn_on_death(False) to not make it respawn. I’m not too sure what the method does tho, it might be worth experiencing with the other api methods.

The version with underscores is valid since Panda3D 1.9 (or earlier?). Interrogate (the python wrapper generator) generates a version with underscores, and a camel case version, however only the camelCase version is shown in the API.

The PEP 0008 suggests not using camelCase, and instead using lower_case, so the preferred way is using lower_case (although its not used in many places in panda yet).

About the fix, well you’d need to get a development version of Panda3D to get the fix. Maybe rdb will backport it to 1.9 tho.

EDIT:

Looked a bit through the source, it looks like set_spawn_on_death controls whether to spawn a new particle when an existing particle dies.