Tag Archives: Python

TouchDesigner | Understanding Extensions

genGeoClassSo you’ve made a killer component that you love using, but you suddenly find yourself wondering how best to re-use it in future projects. You could make a killer control panel for it, or create a more generalized method for passing in values with in CHOPs or DATs. You could just resign yourself to some more complex scripting – reaching deep into your component to set parameters one at a time. You could hard code it, you’ll probably be making some job specific changes to your custom component anyway, so what’s a little more hard coding? The 50000 series now features custom parameters, or you could use variables, or storage. Any one of these options might be right for your component, or maybe they’re just not quite right. Maybe what you really need is a little better reach with Python, but without as much head scratching specificity. If find yourself feeling this way, than extensions are about to make your TouchDesigner programming life so, so much better.

Using extensions requires a bit of leg work on your part as a programmer, it also means that you’ll want to do this for components that you’ll find yourself reusing time and again – after all, if you’re going to take some time to really think about how you want a reusable piece of code to work in a larger system it only makes sense to do this with something you know will be useful again. That is to say, this approach isn’t right for every circumstance, but the circumstances it is right for will really make a difference for you. We’re going to look at a rather impractical example to give us a lay of the land when it comes to using extensions – that’s okay. In fact, as you’re learning how to apply this approach to your workflow it’s worth practicing several times to make sure you have a handle on the ins and outs of the process.

Before we get too much further, what exactly is this extension business? If you’re to the point with TouchDesigner where you’re comfortable using Python to manipulate your networks, you’ll no doubt have come to rely on a number of methods – anything with a . followed by a call. For example:

  • op(‘moviefilein1’).width – returns the width of the file
  • op(‘moviefilein1’).height– returns the heightof the file
  • op(‘table1’).numRows – returns the number of Rows
  • op(‘table1’).numCols – returns the number of Columns

In each of these examples, it’s the .operation that extends how you might think of working with an operator. Custom extensions, means that you, the programmer, are now free to create your own set of extensions for a given component. The classic example for this kind of custom component and custom extension set for TouchDesigner would be  a movie player. Once you build a movie player that cross fades between two videos, wouldn’t it be lovely to use something like op(‘videoPlayer’).PlayNext() or op(‘videoPlayer’).Loop(). The big idea here is that you should be free to do just that when working with a component, and custom extensions are a big part of that puzzle. This keeps us from reaching deep into the component to change parameters, and instead allows us to write modular code with layers of abstraction. If you’re still not convinced that this is a powerful feature, consider that when you start a car you’re not in the business of specifying how precisely the starter motor sequences each electrical signal to help the car turn over, or which spark plugs fire in which order – you issue a command car.start() with the expectation that the start() function holds all of the necessary information about how the vehicle actually starts. While this example might be reductive, it helps to illustrate the importance of abstraction. It’s impractical for you, the driver, to always be caught up in starting sequences in order to drive a car (one might make an argument against this, but for now let’s roll with the fact that many drivers don’t understand the magic behind a car starting when they turn the key), this embedded function allows the driver to focus on higher order operations – navigation, manipulation, etc. At the end of the day, that’s really what we’re after here – how do add a layer of abstraction that simplifies how we manipulate a given component.

That’s all well and good, but let’s get to the practical application of these concepts. In this case, before we actually start to see this in action, we need to have a working component to start working with. We are going to imagine that we want to build a generative component that’s got faceted torus that we use in lots of live sets. We want to be able to change a number of different elements for this Torus – its texture, background, rotation, deformation, to name a few. Let’s begin by putting together a simple render network to build this component, and then we can look at how extensions complement the work we’ve already done.

First let’s add an empty Base COMP to our network.

emptyBase

Inside of our new base let’s add a Camera, Geo, and Light COMP, as well as a Render TOP connected to an Out TOP. We’re building a simple rendering network, which should feel like old hat.

simpleRender

Let’s add a movie file in TOP, and a Composite TOP to our network. We’ll composite our render over our movie file in, so we have a background. In the image below only the changed parameters for the Composite TOP are shown.

simpleRenderWithComposit

Next let’s look inside of our geo COMP, and make a few changes. First let’s change our geo to be a polygon rather than a mesh. We’ll also turn off the display and render flags for the torus (don’t worry, we’ll turn them on further down the chain.

torus

Next we’ll add a noise SOP.

Noise SOP

Next we’ll add a facet SOP, turning on unique points and compute normals.

facetSOP

Finally, let’s add a null SOP. On the null, let’s turn on the display and render flags. When it’s all said and done we should have something that looks like this.

noiseTorusChain

Let’s move up one layer out of our geo, back into the base we started in. Here let’s add a phong Material and apply it to our geo. Let’s also add a movie file in TOP connected to a null TOP, and set it as the color map for our phong.

colorMapAndMaterial

While we’re still thinking about our material, lets make a few changes. Let’s set our diffuse color to white, our specular color to a light gray, and turn up our shininess to 255.

phongNonDefaults

Shown are the non default parameters for the Phong Material.

Let’s also make a few changes to our light COMP. I’m after a kind of shiny faceted torus, so let’s change our light to a cone light, place it overhead and to the right of our geometry, and set it to look at our geo.

Shown are the non default parameters for the Light Component.

Shown are the non default parameters for the Light Component.

I’ve gone ahead and changed file in my movie file in TOP to a different default image so I can see the whole torus. In the end you should have a network that looks something like this.

textureTorus

Thinking ahead, I know that I’m going to want to have the freedom of changing a few parameters for this texture. I’d like to be able to control if it’s monochrome, as well as a few of the levels of the image. Let’s add a monochrome TOP and a level TOP between the movie file in and the null TOP.

postProcess

We’re almost ready to start thinking about extensions, but first we need to build a control network to operate our component. Let’s start by adding a constant CHOP and calling it attrAssign. Here I’m going to start thinking about what I want to control in this component. I want to drive the rotation of the x y and z axis for our geo, I want to control the amplitude of the noise, the saturation of our image, the black level, brightness, and opacity. I’m going to think of those parameters as:

  • rx
  • ry
  • rz
  • noiseAmp
  • monoVal
  • blkLvl
  • bright
  • opacity

I’ll start out my constant CHOP with those channel names, and some starting values.

attrAssign

For this particular component, I want to be able to set values, and have it smartly figure out transitions rather than needing it constantly feed it a set of smoothly changing values. There are a couple of different ways we might set this up, but I’m going to take a rout of using a speed CHOP for one set of operations, and a filter CHOP to smooth everything out. First I want to isolate the rx ry and rz channels, we can do that with a select CHOP. Next we’ll connect that to a speed CHOP. We can merge this changed set of channels back into the stream with a replace CHOP – replacing our static rx ry rz channels with the dynamic ones.

selectSpeedReplace

Finally, we can smooth out our data with a Filter CHOP, and end our chain of operations in a null CHOP.

controlChops

Our last step here is to export or reference to each of our control parameters. Our rotation channels should be referenced by our Geo1 for rx, ry, and rz. The Noise SOP in Geo1 should be connected to the channel noiseAmp, and our image controls should be connected to their respective parameters – Monochrome, Black Level, Brightness, and Opacity. In the end, you should end up with a complete network that looks something like this.

complete BaseCOMP

Alright, we now finally have a basic generative component set up, and we’re ready to start thinking about how we want our extensions to work with this bad boy. Let’s start with the simplest ideas here, and work our way up to something more complex. For starters we need to add a text DAT to our network. I’m going to call mine genGeoClass.

genGeo

Let’s add our first class to our genGeoClass text DAT. Our class is going to contain all of our functions that we want to use with our component. There are a few things we need to keep in mind for this process. First, white space is going to be important – tabs matter, and this is a great place to really learn that the hard way. Namespace also matters. We’re eventually going to promote our extensions (more on that later on down), and in order for that to work correctly our functions need to begin with capital letters. That will make more sense as we see that in practice, but for now it’s important to have that tucked away in your mind.

Let’s begin by adding a simple print command. First we define our class, and place our functions inside of the class. When we’re writing a class in Python we need to explicitly place self in our definitions. There are a number of very good reasons for this, and I’d encourage you to read up on the topic if you’re curious:

Why ‘self’ is used explicitly
Why the explicit self has to stay

For our purposes, let’s get our class started with the following;

class GenGeo:

    def SimplePrint( self ):
 
        print( 'Hello World' )
        
        return

Before we can see this in action, we need to set up our base COMP to use extensions. Let’s back out of our base, and take a look at our component parameters.

baseExtensions

Here I’ve set the module reference to be the operator called genGeoClass inside of base1. We can also see that we’re specifcally referencing the GenGeo() class that we just wrote. I’ve also gone ahead and turned on promote extensions. Make sure you click “Re-Init” Extensions at the bottom of the parameters page, and then we can see our extension in action.

Next let’s add a text DAT to the same directory as our base1. Here we’ll use the following piece of code to call the SimpleText() function we just defined:

op( 'base1' ).SimpleText()

Let’s open our text port, and run our text DAT.

SimpleText

That should feel like a little slice of magic. If you choose not to promote your extensions, the syntax for calling a function looks something like this:

op( 'base1' ).ext.GenGeo.SimplePrint()

Okay, this has been a lot of work so far to only print out “Hello World.” How can we make this a little more interesting? I’m so glad you asked. Here’s  a set of functions that I’ve already written. We can copy and paste these into our genGeoClass text DAT, and now we suddenly have a whole new host of functions we can call that perform some meta operations for us.

class GenGeo:

    def SimplePrint( self ):
        print( 'Hello World' )
        return

    def TorusPar( self , rows , columns ):
        op('geo1/torus1').par.rows = rows
        op('geo1/torus1').par.cols = columns
        return

    def TorusParReset( self ):
        op('geo1/torus1').par.rows = 10
        op('geo1/torus1').par.cols = 20 
        return

    def Texture( self , file ): 
        op('moviefilein1').par.file = file
        return

    def TextureReset( self ):
        op('moviefilein1').par.file = app.samplesFolder + '/Map/TestPattern.jpg'
        return

    def Rot( self , rx , ry , rz ):
        attr = op('attrAssign')
 
        attr.par.value0 = rx
        attr.par.value1 = ry
        attr.par.value2 = rz
        return

    def RotReset( self ):
        attr = op('attrAssign')
        speed = op('speed1')
        filterCHOP = op('filter1')
        attr.par.value0 = 0
        attr.par.value1 = 0
        attr.par.value2 = 0
        speed.par.resetpulse.pulse()
        filterCHOP.par.resetpulse.pulse()
        return

    def TorusNoise( self , noiseAmp ):
        op( 'attrAssign' ).par.value3 = noiseAmp
        return

    def Mono( self , monoVal ):
        op( 'attrAssign' ).par.value4 = monoVal
        return

    def Levels( self , blkLvl , bright , opacity ):
        attr = op('attrAssign')
        attr.par.value5 = blkLvl
        attr.par.value6 = bright
        attr.par.value7 = opacity
        return

    def PostProcessReset( self ):
        attr = op('attrAssign')
        attr.par.value4 = 0
        attr.par.value5 = 0
        attr.par.value6 = 1
        attr.par.value7 = 1
        return

    def Background( self , onOff ):
        op('comp1').bypass = onOff
        return

To better understand what all of these do let’s look at a quick cheat sheet that I made:

# Test Print Statement
op( 'base1' ).SimplePrint()

# Set Rows and Columns
op( 'base1' ).TorusPar( 20 , 20 )

# Reset Rwos and Columns to 10 x 20
op( 'base1' ).TorusParReset()

# Set the texture of a movie file in TOP
op( 'base1' ).Texture( 'https://farm4.staticflickr.com/3696/10353390565_1fa6dbf704_o.jpg' )

# Reset the Texture of movie file in TOP
op( 'base1' ).TextureReset()

# Set the Rotation Speed for the x y and / or z axis
op( 'base1' ).Rot( 10 , 15 , 20 )

# Reset the Rotation speed to 0, and the rotation values to 0
op( 'base1' ).RotReset()

# Set the Amplitude paramater of the Noise SOP for the Torus
op( 'base1' ).TorusNoise( 0.8 )

# Make the texture Monochrome
op( 'base1' ).Mono( 1.0 )

# Control the Black Leve, Brightness, and Opacity of the Texture
# that's applied to the Torus
op( 'base1' ).Levels( 0.25 , 1.5 , 0.8 )

# Reset all post process effects
op( 'base1' ).PostProcessReset()

# Turn off Background Image - 0 will turn the Background back on
op( 'base1' ).Background( 1 )

This is wonderful, but there’s one last thing for us to consider. Wouldn’t it be great if we had some initialization values in here, so at start-up or when we made a new instance of this comp we defaulted to a reliable base state? That would be lovely, and we can set that with an __init__ definition. Let’s add the following to our class:

    def __init__( self ):
 
        print( 'Gen Init' )
        attr = op('attrAssign')

        op('moviefilein1').par.file = app.samplesFolder + '/Map/TestPattern.jpg'

        attr.par.value4 = 0
        attr.par.value5 = 0
        attr.par.value6 = 1
        attr.par.value7 = 1

        return

That means our whole class should now look like this:

class GenGeo:

    def __init__( self ):
        print( 'Gen Init' )
        attr = op('attrAssign')

        op('moviefilein1').par.file = app.samplesFolder + '/Map/TestPattern.jpg'

        attr.par.value4 = 0
        attr.par.value5 = 0
        attr.par.value6 = 1
        attr.par.value7 = 1
        return

    def SimplePrint( self ):
        print( 'Hello World' )
        return

    def TorusPar( self , rows , columns ):
        op('geo1/torus1').par.rows = rows
        op('geo1/torus1').par.cols = columns
        return

    def TorusParReset( self ):
        op('geo1/torus1').par.rows = 10
        op('geo1/torus1').par.cols = 20 
        return

    def Texture( self , file ): 
        op('moviefilein1').par.file = file
        return

    def TextureReset( self ):
        op('moviefilein1').par.file = app.samplesFolder + '/Map/TestPattern.jpg'
        return

    def Rot( self , rx , ry , rz ):
        attr = op('attrAssign')
 
        attr.par.value0 = rx
        attr.par.value1 = ry
        attr.par.value2 = rz
        return

    def RotReset( self ):
        attr = op('attrAssign')
        speed = op('speed1')
        filterCHOP = op('filter1')
        attr.par.value0 = 0
        attr.par.value1 = 0
        attr.par.value2 = 0
        speed.par.resetpulse.pulse()
        filterCHOP.par.resetpulse.pulse()
        return

    def TorusNoise( self , noiseAmp ):
        op( 'attrAssign' ).par.value3 = noiseAmp
        return

    def Mono( self , monoVal ):
        op( 'attrAssign' ).par.value4 = monoVal
        return

    def Levels( self , blkLvl , bright , opacity ):
        attr = op('attrAssign')
        attr.par.value5 = blkLvl
        attr.par.value6 = bright
        attr.par.value7 = opacity
        return

    def PostProcessReset( self ):
        attr = op('attrAssign')
        attr.par.value4 = 0
        attr.par.value5 = 0
        attr.par.value6 = 1
        attr.par.value7 = 1
        return

    def Background( self , onOff ):
        op('comp1').bypass = onOff
        return

Alright, so why do we care? Well, this application of extensions frees us to think differently about this component. Let’s say that I want to make a few changes to this component’s behavior. First I want to set a new image to be the texture for the torus, next I want to change the rotation speed on the x and y axis, and finally I want to turn up the noise SOP. Previously, I might think about this by writing a series of scripts that looked something like:

op( 'base1/attrAssign' ).par.value0 = 20
op( 'base1/attrAssign' ).par.value1 = 30
op( 'base1/attrAssign' ).par.value3 = 0.8
op( 'base1/moviefilein1' ).par.file = 'https://farm4.staticflickr.com/3696/10353390565_1fa6dbf704_o.jpg'

Instead, I can now write that like this:

op( 'base1' ).Texture( 'https://farm4.staticflickr.com/3696/10353390565_1fa6dbf704_o.jpg' )
op( 'base1' ).Rot( 20 , 30 , 0 )
op( 'base1' ).TorusNoise( 0.8 )

That might not seem like a huge difference here in our example network, but as we build larger and more complex components, this suddenly becomes hugely powerful as a working approach.

extensionsInAction

Check out the example file on GitHub if you get stuck along the way, or want to see exactly how I made this work.

TouchDesigner | Getting More out of the CHOP Execute DAT

One of the most challenging elements for me to wrap my heard around when using TouchDesigner was the Execute DATs. When scripting was still new to me, it was hard to understand where to start, or what to write. It was also hard to understand what kind of options I had when dealing with the execute family of DATs. Let’s take a closer look at some of the interesting elements that you can extract from a CHOP Execute DAT, and hopefully learn a few things along the way.
LFO Trail

CHOP Execute DATs allow us to run a script at particular events. The functions that you see in the CHOP Execute specify what kind of event, and what kind of information gets passed into those events that you might take advantage of. Looking at the CHOP Execute DAT we can see the following kinds of events that we might want to take advantage of:

  • Off to On
  • While On
  • On to Off
  • While Off
  • Value Change

Screenshot_061015_113742_PM

That’s all well and good, but what do those things actually mean? Let’s create a simple script to see what we end up with. We’ll work mostly with the Off to On Definition – with what we learn here you can start to explore what the other definitions do (when I say definitions, I’m mean the portions of the Execute DAT that begin with def).

Before we can get started we need a CHOP in our network to associate with our CHOP Execute DAT. Let’s add an LFO CHOP, and a CHOP Execute DAT to our network.

Screenshot_061015_114358_PM

Next we need to associate our CHOP with our DAT. We can do this by dragging the LFO CHOP onto the CHOP Execute DAT.

associate CHOP to DAT

Next we need to make sure that in the parameters of our Execute DAT that the flag which corresponds to our definition is turned on.

Screenshot_061015_114629_PMWith my default values, I can see that something isn’t quite right. Here my “Value Change” Flag is turned on, I want to turn off that flag and turn on the Off to On Flag. The parameters for your CHOP Execute should now look like this:

Screenshot_061015_114822_PM

Alright, now that we’re just about set up let’s see what happens when we write some scripts for our Execute DAT. The simplest place for us to start might be to just print a string. Let’s add this line to inside of our definition:

print('Hello World')

Because this is inside of a defined function, we’ll also need to make sure that it’s indented. The whole function should look like this in your DAT:

def offToOn(channel, sampleIndex, val, prev):
    
    print('Hello World')
    
    return

In TouchDesigner that looks like this:

Screenshot_061015_115603_PM

That’s all well and good, but what does that do? Well, if we open up our text port we should see that ever time we cross the 0 threshold in our LFO that we print “Hello World” to the text port.

Hello World

That in itself isn’t very excited, but we might imagine a situation where we want to run a script at a given regular interval, and this is exactly the kind of technique we might use to make that happen. You might also notice that there are several other parameters for this function that we might be interested in.

Before we get in too deep, lets remember to read what’s already in our DAT. We can see that the header comment reads:

# me is this DAT.
# 
# channel is the Channel object which has changed.
# sampleIndex is the index of the changed sample
# val is the numeric value of the changed sample.
# prev is the previous sample value.
# 
# Make sure the corresponding toggle is enabled in the CHOP Execute DAT.

In general I like to think about any scripting with the following kind of mantra – “When in Doubt, Print it Out.” With that in mind, let’s just print out these parameters to see what they are. To do that, we’ll change our function to read like the following:

def offToOn(channel, sampleIndex, val, prev):
 
    print('channel is ' , channel)
    print('sampleIndex is ', sampleIndex)
    print('val is ', val)
    print('prev is ', prev)
 
 return

The results in our text port should look something like this:

Screenshot_061115_121305_AM

This should give us a sense of the kinds of information that we can pull out of our Panel Execute DAT. Before we stop, let’s push a little harder on one of these ideas. First we need to make some changes to our LFO. Let’s first use some pattern matching to add some more channels. We can do this on the Channel Page of our CHOP by specifying that our Channel Names should be chan[1-5]:

Screenshot_061115_122708_AMNext, lets give these unique all a unique phase with the following python expression:

me.chanIndex/me.numChans

Screenshot_061115_122846_AM

Alright, now we should have an LFO that looks something like this:

LFOCHOP

Let’s change up our Execute DAT to see what kinds of interesting information we might be able to extract from this. First let’s just see if we can retrieve the channel index as our wave passes through zero. We can do this by changing our function to read:

def offToOn(channel, sampleIndex, val, prev):
 
    print(channel.index)
 
    return

In TouchDesigner that might look like this:

Screenshot_061115_123416_AMThat’s pretty interesting, but what if we only want to print out a line of text when a certain set of conditions are met? We might imagine a scenery where we want to first match a logical test, then run a script.

Let’s try writing something like this:

def offToOn(channel, sampleIndex, val, prev):
 
    if channel.index == 0:
        print('This will only appear when index 0 fires')
 
    if channel.index == 1:
        print('This will only appear when index 1 fires')
 
    if channel.index == 2:
        print('This will only appear when index 2 fires')
 
    if channel.index == 3:
        print('This will only appear when index 3 fires')
 
    if channel.index == 4:
        print('This will only appear when index 4 fires')
 
 return

We’ve now set up a series of tests where we’ll only see a particular line of text when the if conditions are met. As a result we should see this in the text port:

textPortText

This may initially seem unimpressive, but when you begin to imagine how you might combine the power of CHOPs with the extensible nature of scripting you soon end up with a world of options that wasn’t present before.

Looking for the Example Files? You can find them here on GitHub.

TouchDesigner | Make a Copy of that Table

When working with DATs there are often a whole host of operations that we want to be able to take advantage of that don’t make sense when we initially look at the documentation on the TouchDesigner Wiki. One simple example of this is how copy operations work. A clear example of this might be how copy operations work. For example, I have a table full of information but I’d like to make a copy of it for another part of my network. If we look at the Table DAT Class wiki page we see this:

copy(DAT)

Copy the text or table from the specified DAT operator.

  • OP – The DAT operator whose contents should be copied into the DAT.

How can we make sense of this?

Let’s start by first setting up a simple table – you can fill it with any information that you’d like. We also want an empty Table DAT, as well as a Text DAT.

Screenshot_060615_065054_PM

In our Text DAT we’re going to write the following script:

source = op('table1')
target = op('table2')

target.copy(source)

Looking at the syntax of the operation we can see what we first defined two variables – target and source, named as such to make it easier to see what’s happening. Next we start with the target and then specify what operator we’d like to copy. Now we can right click on the text DAT, run our script, and voila. A little copy magic.

anatomy of a copy script

Need to see it action? Look through the example files on Git Hub.

TouchDesigner | Drop Scripts

We all love some drag and drop action – we use it all the time, and in fact have often come to expect it to be a feature in nearly every application we interact with. With that in mind, how can we think about integrating some drop actions into our work in TouchDesigner? Well, today is your lucky day! Here we’re going to look at some of the fundamental ideas that will help you get started with much more complicated drag and drop functions.

Anatomy of a Drop Script

Let’s start by setting up a container to perform some actions based on when we drop a file onto it – this will help us get our bearings, and see how dropping works. First we need to set up our container to work with a drop script. First add a new container to a network, next let’s go to the Drag page of the container, and look at the sub page labeled “On Dropping into this Component.”

Screenshot_053015_095734_PM

We’ll start by first changing the drop down menu here to be “Allow Drop.” Next we’ll need to specify what script to run when something is dropped onto this Component. Let’s plan to call our script dropScript, and we’ll place it inside of our container. This means that we can specify it’s relative path and name as

./dropScript

Now we should have something that looks like this:

Screenshot_053015_100204_PM

Now we’re ready to start working. First let’s navigate inside of our container, then split our work-space left and right, and open up a text port on one side of our work-space. Inside of container1 let’s add a new Text DAT and call it dropScript – just like we had planed earlier.

Screenshot_053015_100509_PM

To get started, let’s first write a simple script so we can see this whole thing in action. In our Text DAT let’s use the simple script:

print('Hello World')

Screenshot_053015_100738_PM

To this in action, let’s open the viewer for our container and drop a file onto it – we’ll want to keep an eye on our text port as well, because this is where our print command will show up.

drop

Perfect. Here we can see that when we drop something onto our container, low and behold our script runs. This is great, but wouldn’t it be slick if we could instead get the pathway to our file? In fact, we can get this! Let’s change out drop script to this:

print( args )

Now when we drop something on to our container we get a list that should look something like this:

('E:/Dropbox/Camera Uploads/2015-05-26 10.55.18.jpg', '0', '0', '0', '1', 'jpg', '2015-05-26 10.55.18', '/container1')

The first item in this list is what we’re looking for – it’s the pathway to where our dropped file lives on our computer. Let’s change our drop script so we only get that first item in our list.

print( args[0] )

Now when we drop a file onto our container we only get the pathway to the file.

getAFile

That’s all well and good, but what can we do with that?

Changing a Movie File In TOP

Let’s set up a few other items in our container so we can really see this in action. Let’s add a Movie File In TOP, wire that to a Fit TOP, and finally to a Null TOP.

Screenshot_053015_102336_PM

Set the Fit TOP resolution to match that parent container with following expressions:

parent().par.w
parent().par.h

Screenshot_053015_102639_PM

Finally, let’s name the null bg, and set the parent to display this as the background TOP.

Screenshot_053015_102751_PM

Now we’re ready to return to our drop script. Let’s change our drop script to set the parameter “file” on the Movie File In TOP to be the path from args. In python that looks something like this:

op('moviefilein1').par.file = args[0]

If you’re following along at home, you might want to consider adding some comments and breaking this up slightly. It may make your code a little more verbose, but when you come back to it later on it will be easier to decode what’s actually going on here. That might end up reading like this:

# identify the movie file in 
# operator that I want to change
movTarget = op('moviefilein1')

# make a new variable called newFile
# assign the args[0] string to this variable
newFile = args[0]

# set the file paramater for the target op
# to be newFile - this means that when I drop
# an image onto this container, that file
# will become the background
movTarget.par.file = newFile

Commenting isn’t always the most fun part of programming, but when you come back to a project or script it can make it much easier to remember what you were thinking and how you were working.

Now, when we drop a file onto our container, we should see the background file change as well.

DropAction

Changing a Directory

That’s great, but how can we push this a little farther? Well, we might imagine that we find ourselves in a situation where we’d like to change the target directory for a Folder DAT. While the Choose a Directory example works well, it you might not want the hassle of navigating to your folder – instead you might just want to drop it onto your container. We can use the same principles from above to do just that.

Let’s add a Folder DAT to our network. Now we’ll change our dropScript to be:

folderDAT = op('folder1')
folderDAT.par.rootfolder = args[0]

Now we can drag an entire folder onto our container and see the Folder DAT update.

folderDop

Bada bing! With a few basics under your belt, now you can have fun and and start to play with doing something exciting with drop scripts.

Want to take a look at some examples? You can find them here on GitHub