Python Iterators for your Cinema 4D Scene

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 engrained 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.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.

Do you have any question? Drop me a note on Twitter @martinweber