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

    pyDeformer mode: ID_MG_BASEEFFECTOR_POSITION works, but ID_MG_BASEEFFECTOR_SCALE doesn't

    Cinema 4D SDK
    python r19 r20
    2
    6
    1.1k
    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.
    • intenditoreI
      intenditore
      last edited by

      I stumbled upon a weird thing. I code a Python Effector (parameters control), there I want to check does it affect scale parameter or not:

      md = mo.GeGetMoData(op)
      mode = md.GetBlendID()
      if mode == c4d.ID_MG_BASEEFFECTOR_SCALE:
              rVal = 1
          else:
              rVal = 0
      

      Fairly basic
      And you know, the first if is never true. Despite ID_MG_BASEEFFECTOR_POSITION is working.
      I tried ID_MG_BASEEFFECTOR_SCALE_ACTIVE but nothing changed.
      Than I checked what mode is. Whatever I do it's always returning the same:

      1010
      1011
      1012
      1013
      1013
      1013
      1065
      1066
      1067
      1064
      1068
      1069
      1020
      19
      20
      

      There are all kinds of the modes enumerations (can be found in \resource\modules\objects\description\obaseeffector.h) despite only Scale (1013) is active. So I don't understand what is that.
      Why doesn't it work? And how should it?

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

        Hi,

        I am a bit confused, because the integer value for the symbol in question, is right there in your print out (1011 is integer value for c4d.ID_MG_BASEEFFECTOR_SCALE ). So it is unclear to me, what you would consider not working, since you did not give us the whole script. Anyways, here is a short example:

        """ This snippet is intended for a Python Effector in "Parameter Control"
        mode. Things will change fundamently when in "Full Control" mode.
        """
        import c4d
        from c4d.modules import mograph
        
        def main():
            # Get the MoData or get out when we could not access the data.
            md = mograph.GeGetMoData(op)
            if md is None: 
                return 1.0
            
            # Get the category for which the effector is being polled.
            mode = md.GetBlendID()
            # The effector is being polled for Parameter/Position.
            if mode == c4d.ID_MG_BASEEFFECTOR_POSITION:
                # The component wise product of this returned vector and the vector
                # specified by the user in the interface will be the final value 
                # for the current particle. E.g., if the user specified (0, 50, 0),
                # then the final result would be (0, 50, 0) ^ (2, 2, 2), i.e. 
                # (0, 100, 0).
                return c4d.Vector(2)
            # The effector is being polled for Parameter/Scale.
            elif mode == c4d.ID_MG_BASEEFFECTOR_SCALE:
                # For the scale parameter things work analogously, this would ensure that
                # only half of the value specified by the user will apply.
                return c4d.Vector(.5)
        
            # When we reached this point, we do not want to change anything and return 
            # `1` to indicate that.
            return 1.0
        

        Cheers,
        zipit

        MAXON SDK Specialist
        developers.maxon.net

        intenditoreI 1 Reply Last reply Reply Quote 2
        • intenditoreI
          intenditore @ferdinand
          last edited by

          I am a bit confused, because the integer value for the symbol in question, is right there in your print out (1011 is integer value for c4d.ID_MG_BASEEFFECTOR_SCALE
          Sorry, that's my typo of course.
          My code actually looks like this:

          import c4d
          from c4d.modules import mograph as mo
          import random
          #Welcome to the world of Python
          
          def main():
              md = mo.GeGetMoData(op)
              if md is None: return 1.0
              mode = md.GetBlendID()
              
              index = md.GetCurrentIndex()
              random.seed(md.GetCount() * index * op[c4d.ID_USERDATA,1])
              rIndex = random.randint(0,2)
              if mode == c4d.ID_MG_BASEEFFECTOR_SCALE:
                  rVal = 1
              else:
                  rVal = random.choice([-1, 1])
              weights = md.GetArray(c4d.MODATA_WEIGHT)
              #print rVal
              vector = c4d.Vector()
              vector[rIndex] = rVal * weights[index]
              if mode == c4d.ID_MG_BASEEFFECTOR_POSITION or mode == c4d.ID_MG_BASEEFFECTOR_ROTATION or mode == c4d.ID_MG_BASEEFFECTOR_SCALE:
                  return vector
              else: return 1.0
          

          It's point is to randomly choose the axis which the value will be added to. It can give results as in attached image 2020-07-22_16-58-31.png
          But to avoid negative scale I check if the Scale is active. But weirdly my code with the same check as yours never worked. For some reason I copied your and despite it's the same letter to letter now it works.
          I'm confused...

          But thanks for your answer anyway!

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

            Hi,

            I am glad that it worked out for you, but since you posted your code now, I spotted a few problems around your random number generation, specifically in this line:

            random.seed(md.GetCount() * index * op[c4d.ID_USERDATA,1])
            

            Only a minor detail is that md.GetCount() * index * op[c4d.ID_USERDATA,1] might cause an integer overflow, since this is effectively some large number to the power of three. An overflow is unlikely, but could lead to unexpected behaviour.

            More important is that random.seed is a expensive function, because it will build the permutation table of Python's Mersenne-Twister random number sequence generator each time it is being called. You calling random.seed inside the main function means that this is being done potentially millions of times per frame. You should:

            • either call random.seed only once outside the main function
            • or, if you want your random values seed to be animatable via the user data value, at least safeguard the random.seed so that it is only being called when needed.

            There is also the problem that the state of your PNG permutation table is dependent on the order in which the particles are being queried, due to the fact that you call the global random instance inside a conditional statement (random.choice([-1, 1])) and the general design of using a sequence based PNG here in the first place.

            You could solve all the above by using a custom PNG, the common trigonometric one-liner PNG would be enough. It also would be much more performant.

            Cheers,
            zipit

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 1
            • intenditoreI
              intenditore
              last edited by

              Oh, you're always making a huge work answering the questions! 👏

              I'm slightly confused by the topic you cover, can't find what PNG is.
              What should I do with random.seed than if I rely on unique random number generated for the particular clone? I can't move it out of main() because only there ID is accessible.
              Make some class to store there those random numbers as globals and check should I generate it again or use as before based on user data seed and clone count?

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

                Hi,

                I am sorry, I hate it myself when people talk in acronyms, assuming everyone knows what they are referring to. PNG stands for Pseudo-random Number Generator. Here is an example for a simple trigonometric pseudo random hash function.

                Cheers,
                zipit

                """A simple example for a very simple "one-at-a-time" Pseudo-random Number
                Generator (PNG).
                
                It is basically just one line of code, which you can find on line 32.
                """
                
                import c4d
                import math
                
                def hash_11(x, seed=1234, magic=(1234.4567, 98765.4321)):
                    """Returns a pseudo random floating point hash for a floating point number.
                
                    The hash will lie int the interval [-1, 1] and the function is a very
                    simple generator that exploits the periodicity of the trigonometric 
                    functions. A more sophisticated approach would be to exploit avalanche
                    behavior in bit-shift operations on binary data, like the Jenkins
                    Rotation does for example.
                
                    The postfix in the name (_11) is a common way to denote the inputs and
                    outputs for PNGs. 11 means that it will take one (real) number and return 
                    one (real) number. 13 means that it takes one and returns three, i.e. returns 
                    an (euclidean) vector.  
                
                    Args:
                        x (float): The value to hash into a pseudo random number.
                        seed (int, optional): The seed value. This could also be a float.
                        magic (tuple, optional): The magic numbers. The second one should be 
                         bigger then the first and both should be real numbers.
                    
                    Returns:
                        float: The pseudo random hash for x in the interval [-1, 1].
                    """
                    return math.modf(math.sin(x + seed + magic[0]) * magic[1])[0]
                
                def hash_13(x, seed=1234, magic=(1234.4567, 98765.4321)):
                    """Returns a pseudo random vector hash for a floating point number.
                    
                    Wraps around hash_11.
                    
                    Returns:
                        c4d.Vector: The pseudo random hash for x in the interval [-1, 1].
                    """
                    vx = hash_11(x, seed, magic)
                    vy = hash_11(x + vx, seed, magic)
                    vz = hash_11(x + vy, seed, magic)
                    return c4d.Vector(vx, vy, vz)
                
                def main():
                    """Entry point.
                    """
                    # Some very crude statistics for the hashes.
                    samples = int(1E6)
                    # Generate 1E6 of each
                    numbers = {i: hash_11(i) for i in range(samples)}
                    vectors = {i: hash_13(i) for i in range(samples)}
                    # Compute their arithmetic mean.
                    amean_numbers = sum(numbers.values()) * (1./samples)
                    amean_vectors = sum(vectors.values()) * (1./samples)
                    # Report the results.
                    print "First three random numbers: ", numbers.values()[:3]
                    print "First three random vectors: ", vectors.values()[:3]
                    msg = "Arithmetic mean of all random numbers (should converge to zero): "
                    print msg, amean_numbers
                    msg = "Arithmetic mean of all random vectors (should converge to zero): "
                    print msg, amean_vectors
                
                if __name__ == "__main__":
                    main()
                
                First three random numbers:  [-0.8036933662078809, 0.20401213006516628, 0.6249060598929645]
                First three random vectors:  [Vector(-0.804, -0.022, -0.872), Vector(0.204, 0.541, 0.115), Vector(0.625, 0.782, 0.896)]
                Arithmetic mean of all random numbers (should converge to zero):  -0.000127638074863
                Arithmetic mean of all random vectors (should converge to zero):  Vector(0, 0, 0)
                

                MAXON SDK Specialist
                developers.maxon.net

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