One thing you will do over and over again in scripting Cinema 4D is to go through your scene. Usually you go hunting for certain objects, tags or materials.
This is pretty straight forward using the BaseList2D class methods by
using GetNext()
and GetDown()
but can get old pretty fast as you
are doing the same checking and safeguards over and over again.
Python - like many modern programming languages - has a neat iterator pattern that is ingrained into the language. So instead of using Cinema 4D’s object model you can do it more comfortably by using Python in a more natural way.
Object Iterator
Here is a quick and simple implementation to iterate over objects in your scene. Just pass in a BaseList2D derived object (any object in your scene) and the iterator will go through all it’s siblings and children - depth first.
class ObjectIterator :
def __init__(self, baseObject):
self.baseObject = baseObject
self.currentObject = baseObject
self.objectStack = []
self.depth = 0
self.nextDepth = 0
def __iter__(self):
return self
def next(self):
if self.currentObject == None :
raise StopIteration
obj = self.currentObject
self.depth = self.nextDepth
child = self.currentObject.GetDown()
if child :
self.nextDepth = self.depth + 1
self.objectStack.append(self.currentObject.GetNext())
self.currentObject = child
else :
self.currentObject = self.currentObject.GetNext()
while( self.currentObject == None and len(self.objectStack) > 0 ) :
self.currentObject = self.objectStack.pop()
self.nextDepth = self.nextDepth - 1
return obj
The methods __iter__()
and next()
make this a Python iterator. You can use it
like any other Python iterator in a for
loop like this:
import c4d
from c4d import documents as docs
if __name__ == '__main__':
doc = docs.GetActiveDocument()
obj = doc.GetFirstObject()
scene = ObjectIterator(obj)
for obj in scene:
print scene.depth, scene.depth*' ', obj.GetName()
You can pass any object in your scene as argument to the ObjectIterator
constructor. Then
use it as you would use any iterable object in Python in a for loop to go through all its
siblings and children. Additionally, the ObjectIterator object has an attribute called ‘depth’
that allows you to check how deep into the hierarchy you are.
Tag Iterator
Once you got to the object you were looking for the next step might be to check its tags. The TagIterator provides the same iterable mechanism for tags like the ObjectIterator does for objects.
class TagIterator:
def __init__(self, obj):
currentTag = None
if obj :
self.currentTag = obj.GetFirstTag()
def __iter__(self):
return self
def next(self):
tag = self.currentTag
if tag == None :
raise StopIteration
self.currentTag = tag.GetNext()
return tag
To use the TagIterator just feed the object to the constructor and loop over the objects tags:
tags = TagIterator(obj)
for tag in tags:
print tag.GetTypeName()
Material Iterator
To go through all the materials in your scene you can use the ObjectIterator as materials derived from BaseList2D as well. Materials don’t have child objects you can use a much simpler version of a BaseList2D iterator that simply ignores children and only goes through all its siblings.
class MaterialIterator:
def __init__(self, doc):
self.doc = doc
self.currentMaterial = None
if doc == None : return
self.currentMaterial = doc.GetFirstMaterial()
def __iter__(self):
return self
def next(self):
if self.currentMaterial == None :
raise StopIteration
mat = self.currentMaterial
self.currentMaterial = self.currentMaterial.GetNext()
return mat
Just feed the current scene document to the iterator and off you go:
import c4d
from c4d import documents as docs
if __name__ == '__main__':
doc = docs.GetActiveDocument()
materials = MaterialIterator(doc)
for mat in materials:
print mat.GetName, " - ", mat.GetTypeName()
I hope this helps you get through your scene more comfortably and with less code.