Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    How to modify parameters of individual clones of Cloner in Python SDK

    Cinema 4D SDK
    python 2025
    2
    4
    504
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • uogygiuolU
      uogygiuol
      last edited by uogygiuol

      Hi,

      I would like to access the parameters of individual clones of the Cloner object in Python.

      Particularly I would like to modify the offset of each clone individually. I am pretty sure this should be possible, however I can't think of a way that does not include some complicated procedure of using the MoGraph Selection Tool. I do have a hunch that using this tool is the way to go, but I have a feeling it should be easier.

      My rather complicated guess is:

      1. create MoGraph selection per individual clone (don't know how yet)
      2. create equal amount of Plain Effectors
      3. connect selections with effectors
      4. modify effectors parameters

      As a bonus, I would like to be able to specify the reference object for each individual clone.
      So, if I have two reference objects A & B, and I want a cloned linear pattern of AAABBBABAAAABBBABBABBABBBBAAAABBABABAA.
      My current approach is to keep A and B in a separate place and put instances of them as children under the cloner following the pattern. It works, but I am not sure if this is bad practice. After all, Cloner will create then Multiinstances referencing Instances. However, my hunch is that a custom cloning pattern is not possible.
      (I thought to combine both questions in one post, as they both refer to modifying individual clones, and possibly have a very similar solution. If this is not true, I am happy to remove this second question for the sake of clarity.)

      The example code is a minified version of the starting situation, with some hallucinated pseudo code at the last lines. In the end I want to use it in the GetVirtualObjects function of an ObjectData plugin.

      # get the obligatory active document to attach our objects to
      doc = c4d.documents.GetActiveDocument()
      
      # construct a cloner object
      cloner = c4d.BaseObject(c4d.Omgcloner)
      # use linear mode, so we can better see if it works
      cloner[c4d.ID_MG_MOTIONGENERATOR_MODE] = c4d.ID_MG_MOTIONGENERATOR_MODE_LINEAR
      # also here, iterating through the children sounds the most intuitive
      cloner[c4d.MGCLONER_MODE] = c4d.MGCLONER_MODE_ITERATE
      # always use 42, as you might accidentally solve all problems of the world
      cloner[c4d.MG_LINEAR_COUNT] = 42
      # might not really matter for this example
      # but in my particular use case I use multiinstance rendering,
      # defining it therefore just in case there are side effects when accessing
      # individual clones
      cloner[c4d.MGCLONER_VOLUMEINSTANCES_MODE] = (
      	c4d.MGCLONER_VOLUMEINSTANCES_MODE_RENDERMULTIINSTANCE
      )
      # okay, now we're ready to throw the fish in the water!
      doc.InsertObject(cloner)
      # give it some children
      A =  c4d.BaseObject(c4d.Ocube)
      A.InsertUnderLast(cloner)
      B = c4d.BaseObject(c4d.Osphere)
      B.InsertUnderLast(cloner)
      
      # now let's do some wishful thinking
      # to demonstrate in pseudocode what I tried to describe in words
      pattern = "AAABBBABAAAABBBABBABBABBBBAAAABBABABAA"
      for i in range(0, len(pattern)):
      	# adjust offset by some arbitrary value
      	cloner.GetCloneByIndex(index).SetOffset(i * 0.2)
      	# move relative to current position along some vector
      	cloner.GetCloneByIndex(index).SetRelPos(c4d.Vector(i))
      	# bonus: set reference object
      	cloner.GetCloneByIndex(index).SetReferenceObject(A if pattern[i] == "A" else B)
      

      Obviously, the last part will throw errors.

      Thank you already for reading this! I am thankful for any hint towards the right direction.

      uogygiuolU ferdinandF 2 Replies Last reply Reply Quote 0
      • uogygiuolU
        uogygiuol @uogygiuol
        last edited by

        Sidenote while researching my answer:
        Something I found confusing in the Set MoData Selection Example is that it's not clear what to do with the selection.

        Wouldn't it make sense to add a op.MakeTag(c4d.Tmgselection) before calling GeSetMoDataSelection?

        This way, also the Get MoData Selection Example works with it in a tandem.

        Do you agree?
        If not I'd be curious why.
        If you do agree, please check this PR
        Thanks!

        1 Reply Last reply Reply Quote 0
        • ferdinandF
          ferdinand @uogygiuol
          last edited by ferdinand

          Hello @uogygiuol,

          Thank you for reaching out to us. When you want to modify MoGraph particle data, you will need an effector. In Python you can use a Python Effector. You technically could also do this for example via TagData\ObjectData.Execute, but that would sidestep all the effector pipeline which very much is not recommended and could lead to unintended side effects.

          MoGraph is also a particle system, not an array of objects. I.e., you interact with particles, on which Cinema 4D then clones geometry upon scene execution. You do not have control over each object as if it was an object in the Object Manager, nor are particles aware of what their particle generator (e.g., a MoGraph cloner) does in its UI. MoGraph particles and their fields are documented here.

          Find a simple example for what you want to do below.

          Cheers,
          Ferdinand

          Result

          File: letters.c4d

          Code

          """Realizes an effector that drives the y position of clones and  their clone geometry.
          """
          import c4d
          
          op: c4d.BaseObject # The Python Effector object containing this code.
          gen: c4d.BaseObject # The MoGraph Generator executing `op`.
          doc: c4d.documents.BaseDocument # The document `op` and `gen`are contained in.
          
          def main() -> bool:
              """Called by Cinema 4D to evaluate the effector.
              """
              # Get the particle data for the effector #op. Get out when either the data cannot be retrieved.
              data: c4d.modules.mograph.MoData = c4d.modules.mograph.GeGetMoData(op)
              if data is None:
                  return 1.0
          
              # Get all the objects parented to the generator #gen for which this effector is executed, and 
              # then compute the clone weight for each of them, i.e., a float between 0.0 and 1.0 which 
              # corresponds to using that child object as the particle geometry for a clone.
              children: list[c4d.BaseObject] = gen.GetChildren()
              count: int = len(children)
              weights: dict[str, float] = {
                  child.GetName(): float(i)/float(count) for i, child in enumerate(children)
                  # E.g. {"Cube": 0.0, "Sphere": 0.5, "Cylinder": 1.0}. Using the name of the child
                  # as the key is of course a terrible idea, I am just doing this to demonstrate the
                  # 'AAABCCBCAAB' thing in a tangible way.
              }
          
              # Get the particle data from the MoData, here for the matrices (i.e., transforms) and the
              # clone weights (i.e., the geometry to use for each clone).
              matrices: list[c4d.Matrix] = data.GetArray(c4d.MODATA_MATRIX)
              clones: list[float] = data.GetArray(c4d.MODATA_CLONE)
          
              # Define the pattern we want to use for the clones and start iterating over all particles.
              pattern: str = "AAABCBCAAB"
              for i in range(data.GetCount()):
                  # I am not quite sure what you meant with 'offset'. A particle has no 'offset' as you for
                  # example can see in the MoGraph Cloner object. MoGraph is a simple particle system, where
                  # each clone has fields such as matrix, color, weight, clone, time, etc. The matrix describes
                  # position, rotation and scale of the clone, where the position is labeled 'off' for offset
                  # in the matrix. But that is just the position of the clone, not the offset parameter of the
                  # cloner. You cannot drive that value, as MoGraph is just a particle system and each particle
                  # knows nothing about the complexities of its clone generator.
          
                  # We just drive the y position of the clone over its index.
                  p: c4d.Vector = matrices[i].off
                  matrices[i].off = c4d.Vector(p.x, i * 250.0, p.z)
          
                  # We also drive the clone geometry over the index.
                  letter: str = pattern[i % len(pattern)]
                  clones[i] = weights[letter]
          
              # Write the new data back.
              data.SetArray(c4d.MODATA_MATRIX, matrices, op[c4d.FIELDS].HasContent())
              data.SetArray(c4d.MODATA_CLONE, clones, op[c4d.FIELDS].HasContent())
              return True
          

          MAXON SDK Specialist
          developers.maxon.net

          1 Reply Last reply Reply Quote 1
          • uogygiuolU
            uogygiuol
            last edited by

            This is fantastic, thank you so much!

            This works beautifully.

            I see your confusion with the "offset". This is absolutely my fault in formulating the question. Sorry about that. I also wanted the clones to follow a spline with an individual offset, but then thought to remove this part to not overcomplicate the question and accidentally left some traces. However, in your video you gave me valuable insight in how to achieve this as well. I already have a working sketch where I'm daisy chaining your script with a Spline Effector in the right order.

            Giving these thoughtful answers is incredibly valuable, you can't believe how motivating this is to go further.

            1 Reply Last reply Reply Quote 0
            • First post
              Last post