Particles from python SDK
-
I was looking for examples of creating particle systems using the python SDK. The articles I find are about accessing particle information after the system is created.
I need to create the systems programmatically. and adjust properties (emitter rate and color) dynamically. Are there any examples available?
Thanks!
-
Hello @zauhar,
welcome to the Plugin Café and thank you for reaching out to us. Although you did well for your first posting, I would recommend that you read our Forum and Support Guidelines for future questions, as they line out some procedures.
About your question: There are two particle systems in Cinema 4D; the standard particle system and the Thinking Particles system.
Standard Particles
Standard particles are exposed in the Python API with c4d.modules.particles and c4d.plugins.ObjectData.ModifyParticles. There can be multiple standard particle systems in a scene, which are represented by particle emitter objects. The system is inherently multi-threaded (i.e., relatively fast), but you cannot allocate particles for a system programmatically, you can only change the parameters of an emitter (either manually or programmatically). You can however write particle system modifiers, i.e., a force which acts upon particles with
ObjectData.ModifyParticles
. So, to interact with standard particles (in the intended way) you must implement anObjectData
plugin, and you cannot spawn any new particles. There is formally a way to circumvent these problems by reading the raw particle data of the hiddenTparticle
tags attached to an emitter. But for that you will need either an intimate knowledge of our API or be comfortable with reverse engineering data structures in general. Find here an example for that approach.Thinking Particles
Thinking Particles (TP form now on) are exposed in the Python API with c4d.modules.thinkingparticles. There is only one TP system per document, no matter how many Xpresso setups and TP emitter the document contains, the TP master system. In some scripting contexts this master system is already pre-exposed as a module attribute
tp
, check Manuals: Python Scripting Nodes to find out where it is pre-exposed. In all other cases you must retrieve it manually with BaseDocument.GetParticleSystem(). Other than for standard particles, you can allocate and modify particles from everywhere in code, but as a down-side, the TP system is single-threaded. You could for example write a Python Programming Tag which both spawns TP particles each frame as well as modifies the existing ones.We cannot go much further here, as we cannot provide full solutions in support, as lined out in our Forum Guidelines, you must produce a concrete problem case, a code-example, before we write code.
And as a minor warning: Particles are in general a very computationally complex topic, and while I in my time as a user had a lot of fun with writing my own TP stuff in Python, you should be aware that Python is a language that is ill-suited for particle computations of high complexity. You will be better off using C++ when you want a high-throughput solution.
Cheers,
Ferdinand -
@ferdinand
Thanks so much for the detailed reply. At this point I do not need the particles to interact with any forces, this is for display only. I will investigate your links when I want to do something more complex, which I am sure I will.I have gotten things working partly at this point, I don't know if it is appropriate owing to length, but I am posting demo code below. This just creates a particle emitter and programmatically moves it around.
Unfortunately when I render the animation, by default only a fraction of the objects show up, because I don't know the corresponding property name for 'Birthrate Renderer' . I can manually set this in the dialog and all is well but cannot figure out what the correct symbol is.
PARTICLEOBJECT_RENDERINSTANCES does not seen to do it, and if I try to set
PARTICLEOBJECT_BIRTHRAYTRACER it seems to break things.Can you direct me to correct attribute name?
Thanks!
Randyimport c4d import math import random mat = c4d.BaseMaterial(c4d.Mmaterial) mat.SetName('emitter sphere') mat[c4d.MATERIAL_COLOR_COLOR] = c4d.Vector(0.8, 0.0, 0.0) doc.InsertMaterial(mat) sph = c4d.BaseObject(5160) rad = sph.GetRad() particleRad = 2.0 scale = particleRad/rad[0] sph.SetAbsScale((scale,scale,scale)) ttag = c4d.TextureTag() ttag.SetMaterial(mat) sph.InsertTag(ttag) sph.SetBit(c4d.BIT_ACTIVE) emitter = c4d.BaseObject(5109) emitter.SetBit(c4d.BIT_ACTIVE) doc.InsertObject(emitter) sph.InsertUnder(emitter) emitter[c4d.PARTICLEOBJECT_BIRTHEDITOR] = 500 emitter[c4d.PARTICLEOBJECT_RENDERINSTANCES] = 500 emitter[c4d.PARTICLEOBJECT_SIZEX] = 0.2 emitter[c4d.PARTICLEOBJECT_SIZEY] = 0.2 emitter[c4d.PARTICLEOBJECT_TYPE] = c4d.PARTICLEOBJECT_TYPE_PYRAMID emitter[c4d.PARTICLEOBJECT_ANGLEH] = 2 * math.pi emitter[c4d.PARTICLEOBJECT_ANGLEV] = math.pi emitter[c4d.PARTICLEOBJECT_SHOWOBJECTS] = True fps = 24 emitter[c4d.PARTICLEOBJECT_START] = c4d.BaseTime(0, fps) emitter[c4d.PARTICLEOBJECT_STOP] = c4d.BaseTime(500, fps) emitter[c4d.PARTICLEOBJECT_LIFETIME] = c4d.BaseTime(5, fps) ## Animate 500 frames, new position every ten frames frame = 0 pos = c4d.Vector(0,0,0) emitter.SetAbsPos(pos) doc.SetTime(c4d.BaseTime(frame, fps)) c4d.CallCommand(12410) for segment in range(50) : frame = 10 * segment for k in range(3) : pos[k] = -30 + 60*random.random() emitter.SetAbsPos(pos) doc.SetTime(c4d.BaseTime(frame, fps)) c4d.CallCommand(12410) # record
-
Hello @zauhar,
@zauhar said in Particles from python SDK:
I have gotten things working partly at this point, I don't know if it is appropriate owing to length, but I am posting demo code below.
I am not 100% sure how this sentence was meant, but you can post here demo code of any length you want, and yours is relatively tame. When you are encountering a bug, or what you think is a bug, it is however of beneficial to shorten the code when possible. And to be crystal clear here: We love executable code, i.e., what you posted, as this makes it often easier to answer things.
Can you direct me to correct attribute name?
The id you are likely looking for is
PARTICLEOBJECT_BIRTHRAYTRACER
. But I can do you one better and show you how to find out these ids on your own (see screen grab below). There was also recently a posting on this topic if you want a more complete summary on how to find out type andDescId
.I am not quite sure what you mean by
PARTICLEOBJECT_BIRTHRAYTRACER
'breaks things'. Could you please elaborate since you do not use that parameter in your script (or show us a script where stuff breaks)?Cheers,
Ferdinand -
That is great! I did not imagine that working so simply, that's a wonderful feature.
Regarding the 'broken' part, I did 'something' and spheres stopped appearing in the viewport, just the 'tracers'. I cannot reproduce that, I must have screwed up. All is right with the world now.
In case it helps others, the demo code below now does everything I currently need. Programmatically changes position of the emitter, and following this discussion (https://forums.cgsociety.org/t/c4d-animation-via-python/1546556) I also change the color over the course of each 10-frame segment.
Thanks so much for your help, I am really sold on 4D.
import c4d import math import random mat = c4d.BaseMaterial(c4d.Mmaterial) mat.SetName('emitter sphere') mat[c4d.MATERIAL_COLOR_COLOR] = c4d.Vector(0.8, 0.0, 0.0) ## Get RGB tracks for continuous color update redtrack = c4d.CTrack(mat, c4d.DescID(c4d.DescLevel(c4d.MATERIAL_COLOR_COLOR, c4d.DTYPE_COLOR, 0, ), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0))) mat.InsertTrackSorted(redtrack) greentrack = c4d.CTrack(mat, c4d.DescID(c4d.DescLevel(c4d.MATERIAL_COLOR_COLOR, c4d.DTYPE_COLOR, 0, ), c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0))) mat.InsertTrackSorted(greentrack) bluetrack = c4d.CTrack(mat, c4d.DescID(c4d.DescLevel(c4d.MATERIAL_COLOR_COLOR, c4d.DTYPE_COLOR, 0, ), c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0))) mat.InsertTrackSorted(bluetrack) doc.InsertMaterial(mat) sph = c4d.BaseObject(5160) rad = sph.GetRad() particleRad = 2.0 scale = particleRad/rad[0] sph.SetAbsScale((scale,scale,scale)) ttag = c4d.TextureTag() ttag.SetMaterial(mat) sph.InsertTag(ttag) sph.SetBit(c4d.BIT_ACTIVE) emitter = c4d.BaseObject(5109) emitter.SetBit(c4d.BIT_ACTIVE) doc.InsertObject(emitter) sph.InsertUnder(emitter) # emit particles at rate 500 emitter[c4d.PARTICLEOBJECT_BIRTHEDITOR] = 500 emitter[c4d.PARTICLEOBJECT_BIRTHRAYTRACER ] = 500 emitter[c4d.PARTICLEOBJECT_RENDERINSTANCES] = 500 emitter[c4d.PARTICLEOBJECT_SIZEX] = 0.2 emitter[c4d.PARTICLEOBJECT_SIZEY] = 0.2 emitter[c4d.PARTICLEOBJECT_TYPE] = c4d.PARTICLEOBJECT_TYPE_PYRAMID emitter[c4d.PARTICLEOBJECT_ANGLEH] = 2 * math.pi emitter[c4d.PARTICLEOBJECT_ANGLEV] = math.pi emitter[c4d.PARTICLEOBJECT_SHOWOBJECTS] = True fps = 24 emitter[c4d.PARTICLEOBJECT_START] = c4d.BaseTime(0, fps) emitter[c4d.PARTICLEOBJECT_STOP] = c4d.BaseTime(500, fps) emitter[c4d.PARTICLEOBJECT_LIFETIME] = c4d.BaseTime(5, fps) ## Animate 500 frames, new position every ten frames, ## transition to next color (cycle red->green->blue) ## First set key frames for color change nextRGB = [1.,0.,0.] redcurve = redtrack.GetCurve() greencurve = greentrack.GetCurve() bluecurve = bluetrack.GetCurve() for segment in range(50) : frame = 50*segment redkey = redcurve.AddKey(c4d.BaseTime(frame, fps))['key'] redkey.SetValue(redcurve, nextRGB[0]) greenkey = greencurve.AddKey(c4d.BaseTime(frame, fps))['key'] greenkey.SetValue(greencurve, nextRGB[1]) bluekey = bluecurve.AddKey(c4d.BaseTime(frame, fps))['key'] bluekey.SetValue(bluecurve, nextRGB[2]) # # rotate RGB values nextRGB.append(nextRGB.pop(0)) ## run animation frame = 0 pos = c4d.Vector(0,0,0) emitter.SetAbsPos(pos) doc.SetTime(c4d.BaseTime(frame, fps)) c4d.CallCommand(12410) for segment in range(50) : frame = 10 * segment mat.Update(True, True) sph.Message(c4d.MSG_UPDATE) for k in range(3) : pos[k] = -30 + 60*random.random() emitter.SetAbsPos(pos) doc.SetTime(c4d.BaseTime(frame, fps)) c4d.CallCommand(12410) # record