PYTHON - Picking random items from an objects list/array


#1

Hi folks, i’m trying to make a logo reveal, it’s composited of a bunch of cubes that i want to appear randomly.

i thought of turning the visibility of all the cubes off, put them into an array, and then use a python script to pick random objects from said array, take them out of the array and turning their visibility on, so there are progressively less cubes off, but i’m quite clueless as how to achieve this, my python knoledge is rather scarce.

is this even possible?
could you please point me in the right direction?

thanks in advance


#2

Sure, but a script will not suffice, as you need something to execute over time. You may try with a Python tag, check the current frameframe = doc.GetTime().GetFrame(doc.GetFps()), set a global variable with your cube list that you initialize at frame 0, and then compare the actual frame with the previous frame. When the frame has sufficiently progressed (depending on how often you want to make a cube visible, for example every 5 frames), you can select your random cube and make it visible while also removing it from the original cube list.

The basic framework looks something like this:

import c4d
import random

x = 0
lastFrame = 0

def main():
    global x, lastFrame
    select = 0
    frame = doc.GetTime().GetFrame(doc.GetFps())
    if frame == 0:
        x = 0
        lastFrame = 0
        random.seed(12345)
    else:
        x += 1
        if frame > lastFrame:
            lastFrame = frame
            select = random.uniform(0, 1)
    print x, lastFrame, select

Note that I do not access the actual objects here, that depends on your scene structure, so you will have to add that yourself. This provides only a consistent (random seed!) tag to find the proper cube to make visible at the proper time. x is a counter for the calls; you may not need that.


#3

This file makes a random reveal sequence and uses it to reveal one cube per frame

reveal.c4d (430.7 KB)

import c4d

import random

def main():
global reveal_sequence

top = doc.SearchObject('cubes')
cubes = top.GetChildren() # list
count = len(cubes)
    
frame = doc.GetTime().GetFrame(doc.GetFps())


if frame == 0:
    for a in range(count):
        obj = cubes[a]
        obj[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 1 # vis off
        obj[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 1
        
    reveal_sequence = random.sample(range(0, count), count) # make random reveal seq
    
else:
    if frame <= count:
        i = reveal_sequence[frame - 1]
        obj = cubes[i]
        
        obj[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 0 # vis on
        obj[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 0

#4

When I was just about to write a similar script as above, I thought, this should bee doable without any Python at all.

So, here you go, a scene using Fields in order to achieve a random reveal of cubes:
test_random_cube_reveal.c4d (248.6 KB)

I did this in R21, but I don’t think I used any R21 specific features, so I hope it works in R20 (where Fields got introduced) as well. In earlier versions you could probably achieve it similarly with falloffs, but I’m actually no MoGraph expert at all.

I already said, I’m not an expert in MoGraph, so there are surely smarter ways to achieve this. Anyway, it works like this (actually going bottom up in object hierarchy):

  1. As I assumed, your cubes might not originally stem from a MoGraph cloner, I used the good ol’ array to create a bunch of cubes.
  2. The Fracture (mode: Explode Segments and Connect) makes the cubes accessible from MoGraph, so we can influence them with Effectors and thus Fields.
  3. A simple Plain effector is used to hide all the clones/cubes. Therefore it’s set to scale relatively and uniformly to -1 (sounds strange and it is, but it is indeed a way to hide clones with Mograph).
  4. The Plain effector is now influenced by three Fields (really simple, no worries). Basically the Fields will provide values between 0.0 and 1.0, determining the influence of the Plain effector at each point in space. A value of 1.0 means it has full effect (in our case the clones are hidden, fully scaled to -1) and 0.0 means the effector has no influence at all (tthe clones have the original size). Now, letts build these fields:
    a. A random field as a base. The noise provided by this field serves as your random values, basically values between 0.0 and 1.0 scattered in space. I don’t think I changed any parameter in there.
    b. A Time Field, which is added (Blending: Add) on top of the random noise. The Remapping Strength needs to be inverted, so it will start with 1.0 at frame zero and then transition to 0.0 over time (duration is specified by Layer Controls: Rate parameter). So, at frame zero the Time Field “floods” the space with 1.0, which is simply added to the noise value at each point in space. The resulting Field consists of values ranging from 1.0 to 2.0. And as time progresses, the Time Fields value decreases so the value range in the Field will transition to 0.0 to 1.0, as the Time Field will add less and less. Important: The Time Field must not be clamped in this case. Because we need it to produce negative values as well, until the entire Field’s summed value range is between 0.0 and -1.0. So, it takes actually to times the frames set in the Rate parameter. Not sure, I have the feeling my explanation rather over complicates it.
    c. Lastly, I added a Quantize Modifier Field Layer set to quantize into just one single step. It basically takes values of the Field and… well quantizes it, in this case in just two values: 0.0 and 1.0. This will fix all the badness introduced with values ranging from up to 2.0 (at the beginning) down to -1.0 (and even less as time progresses), while MoGrapg actually works in the range 0.0 to 1.0. The Quantify (with Steps set to 1) simply compares each Field value with 0.5. Values below will result in 0.0 and values above, well, you guessed it, will result in 1.0.

I’m pretty sure, if Per would be listening here, he’d be rolling on the floor, seeing my approach. Maybe one of our pros (@noseman? Per?) could show how to do it with one single field.

Anyway, I’m aware you asked for a script. No Python in my solution, sorry! But I thought, it might nevertheless be interesting to see a different approach (which by the way is pretty flexible).

Cheers

Edit: In the first post I wrote “nodeman”, who probably is also a pretty cool super villain, shooting all those nodes and stuff. But here of course I was referencing to our living polygon wrecking ball noseman.


#5

C4D Version please and a sample file…


#6

Since the original post asked about making a list of objects and deleting from list as they are made visible, here’s one method

import c4d
import random

def main():
global cubes

frame = doc.GetTime().GetFrame(doc.GetFps())

if frame == 0:
    top = doc.SearchObject('cubes') # parent null
    cubes = top.GetChildren() # list

    for a in range(len(cubes)):
        obj = cubes[a]
        obj[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 1 # vis off
        obj[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 1

else:
    for b in range(5): # set number of cubes to be made visible each frame
        if len(cubes) != 0:
            i = random.randint(0, len(cubes) - 1) # randint range inclusive
            obj = cubes[i]
            obj[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 0 # vis on
            obj[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 0
            del cubes[i] # remove from list

reveal 2.c4d (422.3 KB)


#7

omg this one worked like a charm! (the one from jed1949)

gonna be learning a lot from that code


#8

Also i’d like to thank everyone for your takes on the issue, i’m gonna be checking that code too :grinning:


#9

great Python example, but you can do this with standard MoGraph tools. How to do it depends on your version.


#10

@noseman two questions:

a) How would you do this before R20, without Fields?
b) Is it possible to do my Fields version above with less layers?


#11

both very valid questions.
It all depends on the EXACT effect the OT was requesting, and the version he’s currently using.

  1. Do we want 1 cube to become visible per frame, or the fill to have a specific duration overall?
  2. Surely there are many ways to go about, including in-between setups with less coding AND fields.

I’m testing this today, as it’s a very interesting request.
I’ll post my findings later today.


#12

Here’s an example with R19.
All it’s doing is using a Random effector with the Visibility Parameter on, and the Min and Max Values Keyframed…

I can think of a dozen ways this is possible, and each way is ideal for a different end goal.

Cheers
cubes Random R19 01A.c4d (159.7 KB)


#13

and a similar approach using Linear Falloff (R19 without Fields)

cubes Random R19 Falloff 01A.c4d (141.2 KB)


#14

Using a Vertical (+Y) falloff moving on the Y Axis
cubes Random R19 Falloff Vertical 01A.c4d (142.0 KB)


#15

thanks for the examples, learning a lot from those.

the thing in here is that i’m not using a mographed object but rather a bunch of individual objects.

so the python solution was the best. also what i like about script solutions, is that, even though they might be a bit harder to control or animate, they have a greater reach.


#16

Although there are cases where scripting can - obviously - do more things, and even better, a setup like yours won’t benefit.
Use a Fracture Object with any objects you wish and the same setup gives you what you want

Also, don’t forget that Mograph is more optimized than Python when dealing with many objects. So unless you write a C++ plugin, you will have slower evaluation.

Objects Random R19 Falloff Vertical 01A.c4d (205.7 KB)


#17

cool!

haha i got a chuckle when i played the animation :smile:

gonna try to wrap my head around it now :thinking:


#18

Anytime!
Let us know if you have specific questions.


#19

Nice examples, thanks a ton! Really appreciated.
I knew I was approaching it way to complicated…


#20

You’re very welcome!