Nowadays many clients wants to burn subtitles straight into video so they can put video to social media etc. so I made two scripts that makes working with After Effects and subtitles a little bit easier.
With the first script you can import srt files and the script makes automatically text layers from that subtitle file. I wanted to make text layers instead of keyframed sourceText since individual layers gives better control and are sometimes also lighter to process.
Make composition or use current one, but there needs to be a open composition.
Run the script (File > Scripts > Run Script File…)
FolderLink is asset made with Xpresso and Python that allows you to select folder which includes specific file types. You can decide which kind of file types you are looking for by typing file extensions (separated with comma). FolderLink will scan selected folder (top level) and collects approved files to dropdown menu.
Then you can link easily and quickly browse between different files with dropdown menu and link selected file’s file path to whatever you like, for example to textures or HDRIs.
import c4d, os # Import necessary libraries
def main():
global path # Output file path
string = search # Get extensions string
string = search.replace(" ","") # Remove spaces
extensions = string.split(",") # Split to list
c = 0 # Initialize iteration variable
names = [] # Initialize list for file names
obj = doc.SearchObject("FolderLink")
cycle = c4d.BaseContainer() # Initialize base container
bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_LONG) # Integer
bc.SetString(c4d.DESC_NAME, "File") # Set user data name
bc.SetInt32(c4d.DESC_CUSTOMGUI, c4d.CUSTOMGUI_CYCLE) # Set user data gui
try: # Try to execute following code
if folder != "": # If there is folder
files = os.listdir(folder) # Get files from folder
for f in files: # Iterate through files
ext = f.rsplit(".",1) # Get file extension
if ext[1] in extensions: # If file extension matches
c += 1 # Increase iteration
cycle.SetString(c, f) # Add item to user data cycle
names.append(f) # Add file name to list
bc.SetContainer(c4d.DESC_CYCLE, cycle) # Set container
obj.SetUserDataContainer([c4d.ID_USERDATA, 5], bc) # Update user data
path = folder+"\\"+names[select-1] # Return file path
else: # Else
path = "" # Return empty string
except IndexError: # If something went wrong
path = "" # Return empty string
If you use Mac OS X you might have to change line 26 “\\” to “/”.
I updated the file so now it’s possible to filter files by filename extension. Thanks to @lasse_lauch for this suggestion.
I wanted to make distributed slices with Voronoi Fracture generator so I made this simple Xpresso rig. This setup requires that the pivot is in the middle of the object and it calculates bounding box automatically. You can change count and angle of slices.
Different Processing libraries depending what you want to do (e.g. Kinect, Leap Motion, The MidiBus)
Processing OSC template Here is basic Processing example that sends OSC data. In this example sketch generates a window where mouse position is captured and then remapped to more nice values for Cinema 4D (y-axis is flipped and sketch middle point is the origin) and then data is send with OSC.
import oscP5.*; // Import oscP5 library
import netP5.*; // Import oscP5's net library
OscP5 osc; // Declare oscP5 object
NetAddress net; // Declare net address object
void setup() { // Sketch basic settings
size(640, 640); // Set sketch window size (width, height)
osc = new OscP5(this, 6449); // Set new OscP5 object
net = new NetAddress("127.0.0.1", 32000); // Set new net address
}
void draw() { // Processing will run this function constantly
clear(); // Clear window every frame
float posX = mouseX; // Get mouse x-position
float posY = mouseY; // Get mouse y-position
circle(posX, posY, 25); // Draw circle to mouse position
float x = map(posX, 0, width, (width/2.0)*-1, width/2.0); // Remap x value
float y = map(posY, 0, height, height/2.0, (height/2.0)*-1); // Remap y value
send(x, y); // Run send function
}
void send(float value_a, float value_b) {
OscMessage message_1 = new OscMessage("Mouse Position"); // Initialize new OSC message
message_1.add(value_a); // 1st x-position
message_1.add(value_b); // 2nd y-position
osc.send(message_1, net); // Send OSC message
}
With fOSC plug-in you want to listen to port 32000.
fOSC makes null object for every individual OSC message and you can assign position and rotation parameters to message using add() method. Then you can use Xpresso to remap those values to whatever you want to.
Super simple setup to count beats per minute in Xpresso with Python node. Counting starts immediately when time is running. You can easily modify code for your needs e.g. starting time.
import c4d
def main():
global out # Output port: 'Out'
bpm = 128 # Beats per minute
spb = 60.0 / int(bpm) # Seconds per beat
if time != 0: # If current frame is not first frame
out = int(time/spb) # Hits count
else: # Otherwise
out = 0 # Hits count is 0
I had some fun trying to recreate @Sholmedal‘s Python Tag Vertex Map tools which he showed at his presentation at IBC 2014. I ended up with some different Python Tags.
N.B. This post is kind of obsolete now since you can do all kind of crazy stuff with fields in R20. I’ll keep this here anyway for learning purposes.
Falloff setup This setup creates weight values to the Vertex Map. Control position with a Null.
import c4d
from c4d import utils as u
def main():
vertexMap = op[c4d.ID_USERDATA,1] # Get Vertex Map tag
mind = op[c4d.ID_USERDATA,2] # User Data: Minumum distance
maxd = op[c4d.ID_USERDATA,3] # User Data: Maximum distance
spline = op[c4d.ID_USERDATA,4] # User Data: Remap spline
obj = vertexMap.GetObject() # Get object that Vertex Map uses
pts = obj.GetAllPoints() # Get object's all points
null = op.GetObject() # Get null object that Python Tag uses
null[c4d.NULLOBJECT_RADIUS] = maxd # Set null's radius
nullPosition = null.GetMg().off # Get null's position vector
array = [0.0] # Initialize list
if len(array) != len(pts): # If array is not same size as object's point count
diff = len(pts) - len(array) # Get difference
array.extend([0.0]*diff) # Extend array
for i in xrange(len(pts)): # Iterate through points
point = pts[i] # Get point
distance = (nullPosition - point).GetLength() # Calculate distance
value = u.RangeMap(distance,mind,maxd,1,0,False,spline) # Remap value
array[i] = u.Boxstep(0,1,value) # Clamp value between zero and one
vertexMap.SetAllHighlevelData(array) # Set the data for the Vertex Map
Math Combine two Vertex Maps together with different math operations, like add, substract and intersect.
import c4d
import operator as o
def main():
# User data
tagA = op[c4d.ID_USERDATA,1] # Get Vertex Map tag A
tagB = op[c4d.ID_USERDATA,2] # Get Vertex Map tag B
tagC = op[c4d.ID_USERDATA,3] # Get Output Vertex Map tag
func = op[c4d.ID_USERDATA,4] # Math operation selection
invert = op[c4d.ID_USERDATA,5] # Invert checkbox
dataA = tagA.GetAllHighlevelData() # Get Vertex Map A's data
dataB = tagB.GetAllHighlevelData() # Get Vertex Map B's data
if func == 0: # If add
dataC = map(o.add, dataA, dataB) # Add A and B
elif func == 1: # If substract B from A
dataC = map(o.sub, dataA, dataB) # Subtract B from A
elif func == 2: # If substract A from B
dataC = map(o.sub, dataB, dataA) # Subtract A from B
elif func == 3: # If intersection
dataC = map(o.mul, dataB, dataA) # Intersection
if invert == True: # If invert checkbox is ticked
for i in range(0, len(dataA)): # Loop through array
dataC[i] = 1-dataC[i] # Invert value
tagC.SetAllHighlevelData(dataC) # Set the data for the Vertex Map
Remap With this you can remap source data with a spline to something different.
import c4d
from c4d import utils as u
def main():
source = op[c4d.ID_USERDATA,1] # User Data: Source Vertex Map tag
target = op[c4d.ID_USERDATA,3] # User Data: Output Vertex Map tag
spline = op[c4d.ID_USERDATA,4] # User Data: Remap spline
sourceData = source.GetAllHighlevelData() # Get Vertex Map data
array = [0.0] # Initialize list
if len(array) != len(sourceData): # If array is not same size as sourceData list
diff = len(sourceData) - len(array) # Get difference
array.extend([0.0]*diff) # Extend array
for i in xrange(0, len(sourceData)): # Iterate through data
value = u.RangeMap(sourceData[i],0,1,0,1,False,spline) # Remap data
array[i] = u.Boxstep(0,1,value) # Clamp value between zero and one
target.SetAllHighlevelData(array) # Set the data for the output Vertex Map
import c4d
array = [0.0] # Initialize global list
def main():
global array # Get access to global list
source = op[c4d.ID_USERDATA,1] # User Data: Source Vertex Map tag
target = op[c4d.ID_USERDATA,3] # Data Data: Output Vertex Map tag
decay = op[c4d.ID_USERDATA,2] # User Data: Amount of decay in percentage
sourceData = source.GetAllHighlevelData() # Get source Vertex Map data
if len(array) != len(sourceData): # If array is not same size as sourceData list
diff = len(sourceData) - len(array) # Get difference
array.extend([0.0]*diff) # Extend array
for i in xrange(0, len(sourceData)): # Iterate through data
array[i] = array[i]+sourceData[i] # Value is prev value + source value
if array[i] > 1: # If value is over '1'
array[i] = 1.0 # Clamp value to '1'
if array[i] > 0: # If value is over '0'
array[i] = array[i]-decay # Decay value
if array[i] < 0: # If value is below '0'
array[i] = 0.0 # Clamp value to '0'
target.SetAllHighlevelData(array) # Set the data for the output Vertex Map
Here is easy Xpresso setup that drives X-Particles with MoGraph data. In this setup the cloner sets the amount of the XpEmitter’s particles. Emitter’s emission mode is set to “shot”. Then Xpresso iterates through the clones and sets position, rotation and color data from every single clone to X-Particle particles.
Now you can use for example XpTrail with your MoGraph setup instead using just old MoGraph Tracer object.
This is Python Generator that connects spheres with lines without line intersecting with spheres. There is many experimental commands in this tool. Python Generator virtually generates Sweep Objects and trims start and end parameters depending sphere’s radius so line does not go through spheres. Line is generated by using MoGraph Tracer object.
You can connect spheres by typing sphere IDs to data input. Syntax is “[first id, second id]”. You can also use experimental commands and generate automatically connections. Be careful, creating huge amount of connections will slow down your project. When you use “Add Connection” or “Remove Connection”, you must have only two spheres selected before you click those buttons.
This tool is made especially for parametric sphere objects. It does not work with anything else. This is updated post, before it was a bit clumsy Xpresso rig but now I have converted it completely to Python Generator.
This Xpresso setup is called SoundFlop. It is hooked to MoGraph’s Sound Effector. SoundFlop counts every hit that goes over certain threshold. I’m a bit confused how Sound Effector doesn’t have this feature out of the box. Fortunately we can fix this unfairness with simple Xpresso rig.
Threshold can be controlled with Sound Effector’s parameters, like “lower cutoff”, “compression” and “filter shape”.
Notice that the sound effector requires a wav file!
I wrote two Python Effectors that changes clone offset. Clone offset picks which child of the cloner gets cloned or the blending between those children. Keep in mind that offsetting clones does not change clone’s id number.
Clone offset
With the first Python Effector you can give clone id and child id to the input field and therefore you can change which child of the Cloner is chosen to that clone.
Syntax is [clone id, child id]. Clone and child indexes starts from 0 (zero). So, if you want that third clone is second child of the Cloner you would put [2,1]. You can modify as many clones as you please.
import c4d
from c4d.modules import mograph as mo
def main():
try: # Try to execute following
md = mo.GeGetMoData(op) # Get MoGraph data
if md is None: return False # If there is no data, quit
data = op[c4d.ID_USERDATA,1] # Get user data input
c = data.replace('\n','') # Remove line breaks
c = c.replace('\r','') # Remove carriage returns
c = c.replace('[','') # Remove '[' characters
c = c.split(']') # Split string to list using ']' character as a delimiter
c.remove('') # Remove empty items from list
clen = len(c) # Get length of the list
gen = md.GetGenerator() # Get generator
cnt = md.GetCount() # Get number of clones
cln = md.GetArray(c4d.MODATA_CLONE) # Get clone array
nchd = float(len(gen.GetChildren())) # Get number of generator's children
fall = md.GetFalloffs() # Get falloffs
for i in range(0, cnt): # Loop
if i < clen: # If 'i' is not greater than length of the 'clen' list
clone_id = int(c[i].split(',')[0]) # Get clone id
child_id = int(c[i].split(',')[1]) # Get child id
cln[clone_id] = (1/nchd) * child_id # Offset clone
except: # If something goes wrong
pass # Do nothing
md.SetArray(c4d.MODATA_CLONE, cln, True) # Set clone array data
return True
With the second Python Effector you can change clones depending on weight transformation. You can choose number of the child and adjust the threshold. I don’t know how useful this tool is but it was fun to make.
import c4d
from c4d.modules import mograph as mo
def main():
md = mo.GeGetMoData(op) # Get MoGraph data
if md is None: return False # If there is no data, quit
thd = op[c4d.ID_USERDATA,2] # Threshold
cid = op[c4d.ID_USERDATA,3] # Chosen child id
gen = md.GetGenerator() # Get generator
cnt = md.GetCount() # Get number of clones
cln = md.GetArray(c4d.MODATA_CLONE) # Get clone array
warr = md.GetArray(c4d.MODATA_WEIGHT) # Get weight array
nchd = float(len(gen.GetChildren())) # Get number of generator's children
fall = md.GetFalloffs() # Get falloffs
for i in reversed(range(0, cnt)): # Loop through clone count
if warr[i] >= thd: # If clone weight is equal or greater than threshold
cln[i] = (1/nchd)*(cid) # Offset clone
md.SetArray(c4d.MODATA_CLONE, cln, True) # Set clone array data
return True