SOLVED 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!

@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!
Randy

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)
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,

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 an ObjectData 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 hidden Tparticle 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!
Randy

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)
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 and DescId.

123.gif

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

@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