Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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

    python effector shift clones array

    Scheduled Pinned Locked Moved PYTHON Development
    17 Posts 0 Posters 1.4k Views
    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.
    • H Offline
      Helper
      last edited by

      On 10/03/2017 at 14:07, xxxxxxxx wrote:

      Hello,

      Put briefly: I am trying to understand how to offset clones index in a cloner object using python effector.
      Essentially an array index shift by a set integer.

      As a starting point I've been looking at douwe's python effector example on vimeo:

      I have tried for a few hours to duplicate this (in c4d r18) but have failed,
      I ended up downloading his example file ( https://www.dropbox.com/s/wyvjxpdi86pe0c6/d_ShuffleArrayEffector.c4d )

      It works when you press play, however, if you try and use a different cloner, or even just change the properties of the existing cloner like size properties, it throws index error.

      I've spent a few hours trying different approaches, looking at other examples, and I haven't been able to successfully create a python effector which correctly updates the clones..

      Many thanks for your time!

      1 Reply Last reply Reply Quote 0
      • H Offline
        Helper
        last edited by

        On 13/03/2017 at 03:17, xxxxxxxx wrote:

        Hi,

        Douwe's effector example (d_Shuffle Array Effector) shows how to offset/shuffle clones.
        It simply calls random.shuffle() on the clones matrix array to randomize their indices.

        The index error is given by the call to MoData.SetArray() (line 51 in the effector).
        This will need further investigation to check if it's an issue in the Python API, or if this can be solved in the effector's script.

        1 Reply Last reply Reply Quote 0
        • H Offline
          Helper
          last edited by

          On 13/03/2017 at 05:45, xxxxxxxx wrote:

          Yeah, I can understand almost everything happening in the script, but it breaks the instant you change pretty much anything.
          I noticed Douwe said if you copy the contents of the scene into a new empty scene, it starts working with the new changes. I confirm this works, but what a dirty fix...

          Could there be a more elegant way to offset clones indices?

          I tried a different approach, based on an example python effector from the Python SDK (py_effector)
          In that example there is no "return" present, and when I try my code (seen below) I have to include "return 1.0" otherwise I get an error:
          "TypeError: main expected float or c4d.Vector, not None"

          import c4d
          from c4d.modules import  mograph as mo
          import  random
            
          def my_shuffle(array) :
                  random.shuffle(array)
                  print "I shuffled"
                  return array
              
          def main() :
              md = mo.GeGetMoData(op)
              if md==None: return
              
              cnt = md.GetCount()
              marr = md.GetArray(c4d.MODATA_MATRIX)
              fall = md.GetFalloffs()
              frame = doc.GetTime().GetFrame(doc.GetFps())
              
              try:
                  marr = newmarr
                  
              except NameError:
                  pass
              
              if frame%5==0 and frame != 0:
                  newmarr = my_shuffle(marr)
            
              md.SetArray(c4d.MODATA_MATRIX, marr, True)
              return 1.0
            
          
          

          So, this code doesn't work, it throws no errors either, it does dump about a million "I shuffled" messages to the log every 5th frame. Why that is happening, I have no clue.. I assumed python effector executes once per frame..?

          1 Reply Last reply Reply Quote 0
          • H Offline
            Helper
            last edited by

            On 14/03/2017 at 03:59, xxxxxxxx wrote:

            Originally posted by xxxxxxxx

            I tried a different approach, based on an example python effector from the Python SDK (py_effector)
            In that example there is no "return" present, and when I try my code (seen below) I have to include "return 1.0" otherwise I get an error:
            "TypeError: main expected float or c4d.Vector, not None"

            To not get this error, set the Effector mode to Full Control and return a bool instead (True if successful - usually at the end - and False in error cases). More information on this mode below.

            Originally posted by xxxxxxxx

            So, this code doesn't work, it throws no errors either, it does dump about a million "I shuffled" messages to the log every 5th frame. Why that is happening, I have no clue.. I assumed python effector executes once per frame..?

            A Python Effector is executed 21 times for each frame in Parameter Control mode. This mode means the Effector is value driven.
            main() is then called for each blend value to return its strength (see EffectorDataStruct::strengths). Retrieve the current blend value ID with MoData.GetBlendID(). 
            In Parameter Control mode, a Python Effector main() is called from C++ EffectorData::CalcPointValue().

            In Full Control mode, a Python Effector main() function is executed once per frame.
            In this mode, main() is called from C++ EffectorData::ModifyPoints() to change the MoData returned by GeGetMoData().

            EDIT: Provided accurate information for EffectorData::ModifyPoints() and EffectorData::CalcPointValue().

            1 Reply Last reply Reply Quote 0
            • H Offline
              Helper
              last edited by

              On 14/03/2017 at 13:16, xxxxxxxx wrote:

              Yannick thank you so much for explaining in such depth.

              I had not noticed there was a different control mode available! Now everything works perfectly.

              Here is my refined code for offsetting a cloner array:

              import c4d
              from c4d.modules import mograph as mo
                
              def main() :
                  md = mo.GeGetMoData(op) #get cloner data from user data link field
                  
                  if md==None: return 
                  
                  frame = doc.GetTime().GetFrame(doc.GetFps())
                  
                  offset = op[c4d.ID_USERDATA,3]
                
                  moArray = md.GetArray(c4d.MODATA_MATRIX) #get array of clones
                   
                  newArray = shiftArray(op[c4d.ID_USERDATA,3], moArray)
                      
                  md.SetArray(c4d.MODATA_MATRIX, newArray, True)
                  
                  return True
                  
                  
              def shiftArray(offset, array) :
                  offset = offset % len(array)
                  return array[offset:] + array[:offset]
              
              1 Reply Last reply Reply Quote 0
              • H Offline
                Helper
                last edited by

                On 15/03/2017 at 01:01, xxxxxxxx wrote:

                Glad you wrote an offsetting effector.

                About Douwe's effector example, it's useless and wrong to store the MODATA_MATRIX in a global array.
                This causes the range error when changing the cloner size. Because the previously stored array (with an invalid size for the MoData) was set again.

                In your effector you don't use a global variable and that's right as the latest MoData array can always be retrieved with MoData.GetArray().

                1 Reply Last reply Reply Quote 0
                • H Offline
                  Helper
                  last edited by

                  On 15/03/2017 at 08:11, xxxxxxxx wrote:

                  🙂

                  At last.

                  This issue dates back to november 2012 for me ( https://developers.maxon.net/forum/topic/6744 )

                  So, I want to shuffle clones every 30 frames.
                  This is where I got stuck ( as in,  I keep on getting the same order ),
                  which made me "invent" the global overwrite.

                    
                  import c4d  
                  import random  
                  from c4d.modules import mograph as mo  
                    
                  def my_shuffle(array) :  
                    random.shuffle(array)  
                    return array  
                    
                  def main() :  
                    md = mo.GeGetMoData(op)   
                    if md==None: return   
                    frame = doc.GetTime().GetFrame(doc.GetFps())  
                    marr = md.GetArray(c4d.MODATA_MATRIX)  
                          
                    if frame%30==0:  
                        newmarr = my_shuffle(marr)  
                        md.SetArray(c4d.MODATA_MATRIX, newmarr, True)  
                        return True  
                  

                  Anybody ?

                  1 Reply Last reply Reply Quote 0
                  • H Offline
                    Helper
                    last edited by

                    On 17/03/2017 at 10:48, xxxxxxxx wrote:

                    Hi Douwe,

                    Thanks for joining the discussion and pointing the issue that made you use a global array.
                    You're right, for each frame the same matrix array is retrieved. So the shuffled array is set each 30 frame and reset the next frame.

                    This would need further investigation to check if this is a limitation of effectors/python effectors.

                    1 Reply Last reply Reply Quote 0
                    • H Offline
                      Helper
                      last edited by

                      On 19/03/2017 at 14:21, xxxxxxxx wrote:

                      Hey guys, I'm really new to python, I know my approach doesn't utilise history/memory, just a simple offset translation on the source.

                      I don't know how you could do it without using a global value?

                      1 Reply Last reply Reply Quote 0
                      • H Offline
                        Helper
                        last edited by

                        On 20/03/2017 at 17:28, xxxxxxxx wrote:

                        Originally posted by xxxxxxxx

                        Hi Douwe,

                        Thanks for joining the discussion and pointing the issue that made you use a global array.
                        You're right, for each frame the same matrix array is retrieved. So the shuffled array is set each 30 frame and reset the next frame.

                        This would need further investigation to check if this is a limitation of effectors/python effectors.

                        Thanks for listening, Yannick.
                        Would love to come across a clean way to do this / get this fixed !

                        d

                        1 Reply Last reply Reply Quote 0
                        • H Offline
                          Helper
                          last edited by

                          On 21/03/2017 at 07:42, xxxxxxxx wrote:

                          Hey, just having a look at another way of doing this without global variables. I smashed this together really quickly so needs error handling, tightening up, optimising, minimising etc:

                          import c4d, random
                          from c4d.modules import mograph as mo
                          from c4d import Vector as v, utils as u, Matrix as m
                            
                            
                          def my_shuffle(array) :
                                  random.shuffle(array)
                                  return array
                            
                          def main() :
                              md = mo.GeGetMoData(op)
                              if md is None: return False
                              
                              cnt     = md.GetCount()
                              indarr = md.GetArray(c4d.MODATA_ALT_INDEX) 
                              marr  = md.GetArray(c4d.MODATA_MATRIX)
                              fall     = md.GetFalloffs()
                              
                              
                              frame = doc.GetTime().GetFrame(doc.GetFps()) 
                            
                              if frame == 0 or -1 in indarr : indarr = range(cnt)
                              
                              if frame % 30 == 0:
                                  my_shuffle(indarr)
                                  md.SetArray(c4d.MODATA_ALT_INDEX, indarr, True)
                              
                              positions = []
                            
                              for i in xrange(cnt) :
                                  index = indarr[i]
                                  positions.append(marr[index].off)
                              
                              for i in xrange(cnt) :
                                  marr[i].off = positions[i]
                            
                              md.SetArray(c4d.MODATA_MATRIX, marr, True)
                            
                              return True
                            
                          
                          

                          So it looks like the MODATA_ALT_INDEX array does not get reset each frame so you can use it for all kinds of stuff 🙂

                          Hope this helps,

                          Adam

                          1 Reply Last reply Reply Quote 0
                          • H Offline
                            Helper
                            last edited by

                            On 23/03/2017 at 12:24, xxxxxxxx wrote:

                            Hi Supergeordie,

                            I just tried your code and it doesn't seem to be working for me, it resets to original array on the next frame like the original problem we had. 
                            Does it work for you?

                            1 Reply Last reply Reply Quote 0
                            • H Offline
                              Helper
                              last edited by

                              On 23/03/2017 at 12:50, xxxxxxxx wrote:

                              It works for me. 😕

                              Saying that, I never actually expected it to work considering the behavior of the other mograph arrays When I threw it together the other day, I happily had clones switching position randomly every Nth frame and I was printing to the console the ALT_INDEX array and it looked like it was doing what it should. Maybe there's something funny going on? (Or maybe I'm being stupid and missed something) Either way I'm out of town until Sunday so I'll have a look then. 🙂

                              Cheers,

                              Adam

                              1 Reply Last reply Reply Quote 0
                              • H Offline
                                Helper
                                last edited by

                                On 23/03/2017 at 13:11, xxxxxxxx wrote:

                                Perhaps there's something different about my scene rather than the code, I made a fresh new scene, one cloner, one python effector set to "full control" with your code. would be interesting to see your scenefile, could it be a c4d version difference? I'm using r18.041

                                1 Reply Last reply Reply Quote 0
                                • H Offline
                                  Helper
                                  last edited by

                                  On 27/03/2017 at 01:43, xxxxxxxx wrote:

                                  OK, I think I know why. For me it seems to work on the matrix object but not the cloner. I imagine this is because the matrix object does not use this ALT_INDEX array or something.

                                  In my setup I was already using the matrix object with a cloner mapped onto it.
                                  Here's how it works for me:

                                  password: altind3x

                                  Thanks,

                                  Adam

                                  1 Reply Last reply Reply Quote 0
                                  • H Offline
                                    Helper
                                    last edited by

                                    On 28/03/2017 at 03:01, xxxxxxxx wrote:

                                    Interesting.. I don't know what to say..
                                    Is this just to avoid using global variables?

                                    1 Reply Last reply Reply Quote 0
                                    • H Offline
                                      Helper
                                      last edited by

                                      On 28/03/2017 at 03:14, xxxxxxxx wrote:

                                      Just saw it as a bit of a challenge really, I use global values mainly for this type of thing 🙂

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