pyDeformer mode: ID_MG_BASEEFFECTOR_POSITION works, but ID_MG_BASEEFFECTOR_SCALE doesn't
-
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? -
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 -
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
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!
-
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 callingrandom.seed
inside themain
function means that this is being done potentially millions of times per frame. You should:- either call
random.seed
only once outside themain
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 - either call
-
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? -
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)