Solved Python Generator Mimicking Cloner That Can Modify Individual Cloner Parameters (Except PSR)?


I posted a related problem in this thread. With the vanilla cloner, I understand it is not possible.

But I was wondering if there is an existing python generator that can mimick a clone and ACTUALLY modify individual cloner parameters (except PSR).

For example

  1. a cube with a bend object.
  2. cube cloned to 8 objects
  3. each cube will have different bend strength values.

Is this possible? I don't want to have actual 8 objects in the outliner.


Your Python Generator can generate any kind of object hierarchy (under a null), including cubes with bend deformers of course. The deformers can get their current values e.g. from the generator's user data.

But existing? Nope. You will have to write one.

If you want something that is generally usable, less specific than a one-purpose-only generator, you may end up writing a whole cloner system of your own.

Hi @bentraje,

thank you for reaching out to us. As @Cairyn already said, there is little which prevents you from writing your own cloner. Depending on the complexity of the cloner, this can however be quite complex. Just interpolating between two positions or placing objects in a grid array is not too hard. But recreating all the complexity of a MoGraph cloner will be. It is also not necessary. Here are two more straight forward solutions:

  1. As I said in the last thread (the one you did link), MoGraph is perfectly capable of driving arbitrary parameters of an object. I overlooked in the old thread that you did ask what I meant with ""drive the attributes of an object or its material"", so the file cube_bend_mograph.c4d shows you how you can drive the strength parameter of a bend object with MoGraph. The difference I then made was that while you cannot drive such attributes directly via the particle (arrays), but you can let MoGraphs interpolation system take care of it. Please keep in mind that I am everything but an expert for MoGraph. There are probably more elegant solutions for all this, but all this is out of scope for this forum. Please contact technical support or refer to Cineversity for questions about MoGraph.
  2. One problem of MoGraph is that driving more than one arbitrary parameter of an object is a bit tedious. Since MoGraph's sole principle is interpolation and it can only interpolate on a single axis (between parameters), you will then have to start interpolating between two cloners to drive a second, third, fourth, etc. parameter. Here it would make sense to write a custom solution to have less complicated setups. Rather than rewriting a cloner, it would be however more sensible to just modify the cache of a cloner. This will only work when the cloner is in the "Instance Mode" "Instance", as otherwise the cache will only contain the instance wrappers (a bit confusing the naming here ^^). You could do this all inside a GVO, including building the cloner and its cache, but I did provide here a solution which relies on linking a cloner whose cache one wishes to modify. The idea is then simple, walk over all bend objects in the cache and modify their bend strength. You can find a scene file and the code below.


file: cube_bend_python.c4d

"""Example for modifying the cache of a node and returning it as the output
of a generator.

This specific example drives the bend strength of bend objects contained in
a Mograph cloner object. The example is designed for a Python generator 
object with a specific set of user data values. Please use the provided c4d
file if possible.

    This example makes use of the function `CacheIterator()` for cache 
    iteration which has been proposed on other threads for the task of walking
    a cache, looking for specific nodes. One can pass in one or multiple type
    symbols for the node types to be retrieved from the cache. I did not 
    unpack the topic of caches here any further.

    We are aware that robust cache walking can be a complex subject and 
    already did discuss adding such functionality to the SDK toolset in the
    future, but for now users have to do that on their own.

As discussed in:

import c4d

# The cookie cutter cache iterator template, can be treated as a black-box,
# as it has little to do with the threads subject.
def CacheIterator(op, types=None):
    """An iterator for the elements of a BaseObject cache.

    Handles both "normal" and deformed caches and has the capability to 
    filter by node type.

        op (c4d.BaseObject): The node to walk the cache for.
        types (Union[list, tuple, int, None], optional): A collection of type
         IDs from one of which a yielded node has to be derived from. Will
         yield all node types if None. Defaults to None.

        c4d.BaseObject: A cache element of op.

        TypeError: On argument type violations.
    if not isinstance(op, c4d.BaseObject):
        msg = "Expected a BaseObject or derived class, got: {0}"
        raise TypeError(msg.format(op.__class__.__name__))

    if isinstance(types, int):
        types = (types, )
    if not isinstance(types, (tuple, list, type(None))):
        msg = "Expected a tuple, list or None, got: {0}"
        raise TypeError(msg.format(types.__class__.__name__))

    # Try to retrieve the deformed cache of op.
    temp = op.GetDeformCache()
    if temp is not None:
        for obj in CacheIterator(temp, types):
            yield obj

    # Try to retrieve the cache of op.
    temp = op.GetCache()
    if temp is not None:
        for obj in CacheIterator(temp, types):
            yield obj

    # If op is not a control object.
    if not op.GetBit(c4d.BIT_CONTROLOBJECT):
        # Yield op if it is derived from one of the passed type symbols.
        if types is None or any([op.IsInstanceOf(t) for t in types]):
            yield op

    # Walk the hierarchy of the cache.
    temp = op.GetDown()
    while temp:
        for obj in CacheIterator(temp, types):
            yield obj
        temp = temp.GetNext()

def main():
    # The user data.
    node = op[c4d.ID_USERDATA, 1]
    angle = op[c4d.ID_USERDATA, 2]
    fieldList = op[c4d.ID_USERDATA, 3]

    # Lazy parameter validation ;)
    if None in (node, angle, fieldList):
        raise AttributeError("Non-existent or non-populated user data.")

    # Get the cache of the node and clone it (so that we have ownership).
    cache = node.GetDeformCache() or node.GetCache()
    if cache is None:
        return c4d.BaseObject(c4d.Onull)
    clone = cache.GetClone(c4d.COPYFLAGS_NONE)

    # Iterate over all bend objects in the cache ...
    for bend in CacheIterator(clone, c4d.Obend):
        # ..., sample the field list for the bend object position, ...
        fieldInput = c4d.modules.mograph.FieldInput([bend.GetMg().off], 1)
        fieldOutput = fieldList.SampleListSimple(op, fieldInput,
        if (not isinstance(fieldOutput, c4d.modules.mograph.FieldOutput) or
                fieldOutput.GetCount() < 1):
            raise RuntimeError("Error sampling field input.")
        # ... and set the bend strength with that field weight as a multiple 
        # of the angle defined in the user data.
        bend[c4d.DEFORMOBJECT_STRENGTH] = angle * fieldOutput.GetValue(0)

    # Return the clone's cache.
    return clone

MAXON SDK Specialist

@Cairyn @ferdinand

Thanks for the response. The python generator works as expected.

I did try to retrieve the cache earlier for mograph but with a python tag rather than a python generator, but it has some priority delays. Your solution is much more stable.

Now, I don't have to jump to houdini for such simple scenes.

Have a great day ahead!