@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