Tag Archives: TouchDesigner

TouchDesigner | SOPs and Stamps

Usually when it comes to working with SOPs I tend to focus on thinking about instances instead of copies – but there are some really excellent use cases for the Copy SOP and one of the most powerful pieces of the copy stop is stamping your copies.

If your shaking your head wondering what Stamps are, you certainly aren’t alone – they’re one of the lesser used features of the Copy SOP (in my opinion) but one that has a wide range of use cases. Recently on the forum there was an excellent question about using the data from CHOPs to set the characteristics of SOPs – Multiple Circle SOPs from CHOP samples. One solution to that particular question is using the ability to Stamp copies, and with that in mind we can pull apart how stamping works.

Let’s start with a simple example. Let’s begin by connecting a Circle SOP to a Copy SOP.

A Circle SOP connected to a Copy SOP.

When we start there’s nothing particularly exciting that happens in this example. In order to make some changes we need to first change our number of copies:

Number of Copies set to 10

This still isn’t particularly interesting just yet, all of the copies of our original circle are all in the same place – all stacked on top of one another. Let’s translate our copies by some small number, and turn on our wireframe view of our geometry so we can better see what’s happening:

Copy Translate X par set to 2

The first copy is moved 2 units to the right, the second copy is moved 4 units to the right, the next 6, etc. This puts all of our copies neatly next to one another.

What if, however, we wanted to change the number of divisions in each copy… we might start with a triangle, and add one division for each copy. There’s no good way to do that out the gate – and it might be easy to think that you need to use a Script SOP to make this work.

Luckily, however, we can make this work with stamping. Let’s start by heading over to the Stamp Page of the Copy SOPs parameters:

The Stamp Page

We’re going to start by first turning on our Stamp inputs parameter. Next let’s figure out what we want to stamp each Copy with… if we want to change the number of divisions in a circle based on what Copy it is, we need a way to attach the copy index to that piece of geometry. Lucky of us there’s a handy python member that let’s us do just that. First let’s name our parameter index, and next we can use a simple python expression to attach our copyIndex to our geometry:

me.copyIndex

me.copyIndex in plain English means – “which copy of the geometry I am.” We can see that our copy index goes up to 9, and like all things programming, we should remember that our indices start at 0 – so our copy index are 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. What’s interesting about using Stamps is that we first Stamp our geometry, but then go back upstream in our network to use that stamp.

So to use our stamp we now need to go to our Circle SOP. The syntax of using Stamps looks like this:

fetchStamp(name, default)

We just used the name index when we stamped our copies, so we might first start with:

fetchStamp('index', 0)

Which means, find the Stamp called index and if you can’t find it, use the value 0. BUT, before we use this expression we need to remember that a circle needs a minimum of 3 divisions – less than 3 and we’ll just end up with a line. Let’s make sure we add 3 to our stamp value since our number of copies starts at 0:

fetchStamp('index', 0) + 3

What does that look like:

Copy Index to define the number of divisions

Okay… that’s pretty cool. But that’s really just the start of our journey here. Where this gets really interesting is if we start to use this new super power to look at other operators – after all we now have a way to index our copies.

Let’s now look at something a little more interesting.

We might instead think about using a Noise TOP to help describe how we’d like to displace the vertices of our copies. The idea in this example is that we’ll start by creating a grid – we’ll use a point SOP to displace our verticies, a tips SOP to fan our one side, and a copy SOP to create a spiral:

Our Point SOP is going to displace the vertices of our SOP by using the point Index to locate the x sample position, and the copyIndex to locate the y sample index:

The resulting Spiral has a noisy feeling that we can adjust with a combination of our noise TOP and copy SOP:

This isn’t a technique that’s well suited for changing in real-time, but it is a great way to think about creating fixed geometry that you might use for any number of environmental elements in your pieces.

TouchDesigner | Instancing Examples

Four years ago I started collecting examples on working with instances. What started as a rough collection of ideas has turned into a collection of over 40 different examples. In this revised set of techniques the format and presentation has also changed.Initially the user had to mouse around networks and look for readmes to learn about the examples. In this new version all examples can be explored through a purpose built UI:

Explorer screen shot

This new starting point allows the developer to quickly jump between examples without needing to open the TouchDesigner network. The interface is re-designed with three primary interaction regions in mind: The Navigator, The Readme, and The Liveview.

The Navigator

The Navigator organizes examples into sensible groupings. These high level containers can be collapsed by clicking on their heading. Each example also includes a button to jump to the example, or to open a TouchDesigner network view of the example. This allows you to both see the example rendered and see how it was built all at the same time.

The Navigator Pane

The Readme

The Readme pane has a short description of what you’re seeing. This set of documentation is intended to go along with each specific example and includes discussion about the particular techniques employed. Using the globe network icon on the Navigator will open the readme in your browser if you’d like a slightly larger window for reading through the documentation.

Right now there’s a small display bug on macOS that prevents the scroll bar from working correctly – that’s okay, you can always use the globe icon to open the readme in your default browser instead. You can follow along with the bug report if you’re a mac user here.

The Readme Pane

The Liveview

The Liveview let’s you see the output render from the network at a glance — making it easy to determine if this technique is one that you’d like to learn more about.

The Liveview Pane

Jump to Network

In addition to exploring the examples through the viewer, there’s also a button to open the network view for the example. This lets you jump immediately into the network and see how the instances were set-up.

The Explorer and an open network view

This project has been a labor of love to collect, organize, and compile materials for other passionate developers and educators. I’m hoping that enthusiasts, instructors, and creative developers alike will find this useful and interesting both as a tool and as a reference. 

Github: Clone the GitHub Repo

Happy Programming!


❤️ Thanks ❤️

…to Zoe Sandoval for their tireless work editing, play testing, and making all of these examples better. If it wasn’t for their handwork, and attention to detail this tool wouldn’t be nearly as complete or well constructed.

… to Ian Shelanskey for his passion and encouragement to keep making examples, and craft collect them into a single unified example.

… to Michael Walczyk for his mentorship, guidance, and constant enthusiasm. I would have been lost in the GLSL wilderness without Mike, and I always appreciate when I can ask him some crazy question about rendering and mathematics.

… to Elburz Sorkhabi and everyone at The Interactive Immersive HQ. Many of you were kind enough to test and look through these examples before posting this. The engagement and hunger for learning TouchDesigner in that community continues to inspire me, and is a reminder about why these kinds of examples are useful and important to share.

… to the Derivative Team for their toolkit that never stops being fun to use, and for their thoughtful feedback and support through building out this collection. From combing through the forum, to late night conversations with Greg and Ben I wouldn’t have gotten this far without them.

TouchDesigner | The Object CHOP

The Object CHOP has long been one of the most challenging CHOPs for me to really wrap my head around. Following along with some conversations on the Facebook Help Group 1, it’s clear that I’m not the only one who has bumped their head against how to take advantage of this operator.

With that in mind, here are a few tricks and techniques that you might find helpful when working with the object CHOP.

Distance Between Many Objects Part 1

At first glance, it seems like the object CHOP can only perform calculations between single objects, but in fact you can use this operator to perform calculations between many objects provided that you format the input data correctly, and set up your object CHOP to account for multiple samples.

In a first example let’s say that we want to find the distance between several green spheres and a blue box:

First let’s collect our position information. I’ve used an object CHOP per sphere to find its distance, but you might also use a script CHOP, or a put positions in a table that you reference for the spheres, or drive them with custom parameters. How you position them doesn’t matter. What we need, however, is a single CHOP with three channels that hold the transformation information of those spheres. My trick in this network is to use object CHOPs to find their positions, then put them in sequence with a join CHOP:

Next we can use a single object CHOP that’s fed reference positions from this join CHOP, and a target Geometry COMP:

Other important pieces here are the start and end parameters on the channel page.

This is where we set how many samples the object CHOP will evaluate. This can be a bit confusing – here especially as the join CHOP has started at a sample index of 1 rather than 0. The devil is in the details, so it’s worth keeping a close eye for these kinds of oddities. Because of this we compensate in our start position by moving back one sample index.

Next make sure to set your object CHOP to output measurements, and distance. What you’ll then end up with is a single channel with a sample for each distance between your box and spheres. We can convert this to a table if we wanted to see the actual values:

Distance Between Many Objects Part 2

We may also want to measure distances between multiple blue boxes. Say, for example, that we had two different blue boxes and we wanted to know the distances of our spheres to both of those boxes?

Similar to our first exercise we’ll start by collecting all of the position information for our spheres. We also need position information for our boxes. In this case, however, we need to stretch a single sample to be 4 samples long – this is part of our data preparation step to ensure we correctly calculate distance.

Here a simple stretch CHOP has been used to make sure we have four samples of data for each box. Next we can join this data so all of our box position information is in a single set of CHOP channels:

Before moving on, we need to take a moment to adjust our sphere position data. In our first example we only collected the four positions… we need to set up the correct extend behavior for this series so that our CHOPs know what values to use when CHOPs of mismatched lengths are combined. We can use an extend CHOP set to cycle to do this trick:

Finally, we can then use an object CHOP to calculate the distance between our box and our spheres:

Distance Between Many Objects plus Bearing

If we also calculate the bearing between our boxes and spheres, we’ll end up with rotation information… what can we do with this? We could use this to calculate the correct rotation for a set of instances. For example:

Here each line is correctly rotated, scaled, and placed based on calculations from the object CHOP.

Bearing

You can also use the object CHOP to just calculate bearing – or rotation from one object to another. Here you can see how this might be used to rotate instances to sit flat on a sphere’s surface, or rotate an arrow to point towards an object:

Bearing and Distance

Or you might use the combination of bearing and distance to make some strange abstract art:

Collision

You can also use the object CHOP to simulate a kind of collision calculation where the distance you’re measuring can help you tell how close an object is to another and if they’re on top of one another:

GitHub

Clone the Repo to Follow Along

TouchDesigner | Reflection and Refraction

I can haz Reflections?! Refractions?

Zoe loves all things reflective and refractive and it was almost a year ago that they started looking into how to achieve compelling illusions of reflection and refraction. Then I went to Macau, then Chicago, then Zoe dove headlong into their thesis project… fast forward to 2019, and it was time for me to finally follow through on a long overdue promise to create some examples of reflection and refraction in TouchDesigner. It didn’t hurt that Zoe gently reminded me that it was time for more refractive rendering in life. Good places to start for these kinds of questions are to look at existing references in the world. 

Reflection 

Reflections are hard. In part because they often mean that we need to see the whole world – even the parts that our virtual camera can’t. We might know this intuitively, but the reach of this is easy to forget. When we point the camera in our smartphone at a mirror we see ourselves, the world behind us, above us, and and and. If we point a virtual camera at a virtual mirror we need the same things. That can be a wobbly bit to wrap your head around, and develop a better sense of this challenge I look a look at a reference book I picked up earlier this year – OpenGL 4 Shading Language Cookbook – Second Edition. This has a great chapter on reflection techniques, specifically generating them by using cube-maps. Cubemaps look like an unfolded box, and have a long history of use in computer graphics. 

One of the primary challenges of using cubemaps is that you need to also know the perspective of the object that’s reflective. In other words, cube maps can be very convincing as long as you move the camera, but not the reflective object. But what if we want the option to both move the camera, and the object? In this quick tutorial, we look at how we can use a cube map to create convincing reflections, as well as what steps we need to consider if want not only the camera to move, but the object itself. 


Refraction

The one and only Carlos Garcia (L05) has a great example posted on the TouchDesigner forum. This great example helps illustrate the part of what we’re after with this kind of work is the sleight of hand that hints at refraction, but isn’t necessarily true to the physics of light. Almost all realtime rendering tricks are somewhere between the Truth (with a capital T) of the world, and the truth (sneaky lower case t) of perception. We’ve all fallen for the perceptual tricks of optical illusions, and many times the real work of the digital alchemist is to fool observers into believing a half truth. Carlos’ example proves just that point, and helps us see that with a little tricksy use of the displacement TOP we can achieve a healthy bit of trickery. 

That’s an excellent start to our adventure, but we can dig-in a little more if we keep searching. Another post on the forum links over to an article on medium that showcases an approach for webGL that leverages the use of a UV map to “pre-compute” the direction of displacement of the light that passes through a transparent object. This is an interesting approach, and in fact there’s a middle ground between the webGL example and Carlos’ TOX that gives us some interesting results. 

In the following tutorial we can see how we remix these two ideas. The big picture perspective here is that we can leverage TouchDesigner’s real-time rendering engine to provide the same “pre-computed” asset that’s utilized in the webGL approach, and then use Carlos’ displacement TOP technique. We can also short-cut Carlos’ use of a rendering a second version of the object as a mask, and instead use a threshold TOP looking at our alpha channel to achieve the same effect. This isn’t a huge change, but it’s a bit faster in some implementations and saves us the wobbles that sometimes come with multiple render passes. Finally, a little post processing can help us achieve some more convincing effects that help sell our illusion as a true to the eye. 


Playlist


Individual Videos

TouchDesigner | Packing up a Tox for Distribution

If you’ve been following along with the workshop materials from the TD Summit 2019, there is one more exciting little tid-bit to dig-into. Thinking about how to create a reusable template for creating toxes is no small feat – especially if you want to include external Python libraries. I’ve been thinking about how to approach this challenge and mapped out a rough framework for approaching this challenge. You can see the working repo for this up on github here. During the last part of the workshop on External Python Libraries we covered this a little sneak peak of this technique, and to complete the workshop videos we added a section covering this approach.

The big picture ideas here are to standardize our approach to handling external libraries and automate their installation. This leans on the same concepts we explored in the workshop, and includes using things like a requirements file, using some automated installation scripts, and making sure the path to our project directory is included in our sys.path. The last major change is to convert our general scripts we wrote previously into an extension.

These days I tends to lean towards Extensions over Modules, though you could approach this challenge with Modules very similarly. This set of tutorials will walk us through taking what we created in the previous workshop videos and creating a portable tox we should be able to drag-and-drop into any project. Hopefully, what you take away from this example are some ideas about how to apply this technique and approach to other toxes that you build and share.

Happy Programming!


TouchDesigner | TD Summit 2019 | External Python Libraries

Overview

Hot off the presses, the workshop video from the 2019 TouchDesigner Summit workshop on External Python Libraries is up. You can follow along with all of the materials and outline from the workshop here – External Python Libraries.

If you just want to jump straight to the videos, you can find a playlist below. Happy Programming!


Python has more and more reach these days – from web services to internet of things objects, scientific and statistical analysis of data, what you can do with Python is ever expanding. While it’s possible to do all of this work from the ground up, it’s often easier (and faster) to use libraries that other people have published. TouchDesigner already comes with a few extra libraries included like OpenCV and Numpy. Once you have a handle on working with Python the world feels like it’s your oyster… but how you work with a magical little external library in TouchDesigner can be very tricksy. Worse yet, if you happen to get it working on your machine, making work on another can be infuriating. Over the course of this workshop we’ll take a look at what you can do to make this process as smooth and painless as possible, as well as some considerations and practices that will help you stay sane when you’re trouble shooting this wild Python roller coaster.


TouchDesigner | TD Summit 2019 | Modular Architectures


Hot off the presses, the workshop video from the 2019 TouchDesigner Summit workshop on Modular Architectures is up. You can follow along with all of the materials and outline from the workshop here – Modular Architectures.

If you just want to jump straight to the videos, you can find a playlist below. Happy Programming!


Overview

How you design, plan, and extend a system is a both an engineering and creative endeavor. The choices you make will have lasting impact in the way you work, what your system can do, what it can’t do, and how easy it will be to maintain and adapt for future work. A little planning and thoughtful organization will help make sure that you’re able to focus on the work that’s the most exciting rather than always remaking all of the same pieces. Take it from someone who has built a lot of systems from the ground up – a good foundation keeps you excited and interested in your work, rather than always repeating the same mistakes.

Over the course of this workshop we’ll take a look a fundamental concepts for this kind of work – What is externalization, why do it in the first place, and how much is too much. We’ll also look at some perspectives about organization that come from building large projects, what it means to build templates / blueprints that you can reuse, how to take advantage of some simple automation, and where you can begin to take advantage of some more advanced ideas.

Major Goals

  • No delay scripts unless there is no other solution
  • Complete control over initialization and load order
  • Text-port confirmation of each step (ultimately you want this to be a log-file)

TouchDesigner | GitHub and External Toxes

git push git paid

Working with GitHub and TouchDesigner can be a real adventure – and what I hear most often is why and how. A bit ago I did a little write-up about working with git and Touch, but realize that’s still a bit intimidating for folks. This year at the TouchDesigner summit Zoe Sandoval and I taught a workshop about Modular Architectures and working with TouchDesigner. One of the pieces we didn’t have time to really pull apart was working with Git, so before we shared the videos from the workshop we thought it might be helpful to give some perspective about working with Git and where it might save you on a project.

Playlist


Individual Videos

TouchDesigner | Python and the Subprocess Module

I can has Subprocess?

At the TouchDesigner Summit in Montreal we’ll be taking some time to talk about working with external Python Modules in TouchDesigner. While we’ll have time to cover lots of information about how to incorporate external modules in Touch, we won’t have a lot of time to talk through the wobbles that you might run into when working with operations that might be slow, or otherwise unwieldy to run in Touch.

“What do you mean Matt?”

The types of pieces that usually fall into this category are blocking operations. For example, let’s say that you want to upload an image to the web somewhere. Many of the libraries that you might find will have an approach that’s probably blocking – as in it will appear as if TouchDesigner has frozen while the whole operation completes. While this is fine outside of Touch, we don’t typically like it when our applications appear to freeze – especially in installations or live performances. You might be able to move that process to another thread, though that might be a little more hassle that you really want it to be in the long run.

Enter the Python Subprocess module.

Subprocess

The subprocess module allows you to run a python script as a parallel execution that doesn’t touch your TouchDesigner application. You could use this for all sorts of interesting an powerful applications – from starting media syncing between machines, running a process that talks to the internet, or any number of solutions that execute outside of TouchDesigner. In the past I’ve used this for things like sending emails, or uploading images to Instagram – there’s lots you can do with this approach, it’s just a matter of wrangling python and the subprocess module.

What exactly is happening when we use the subprocess module?! Well, we can think of this as a situation where we write a python script in a text file, and then ask your operating system to run that file. There are great ways to pass in arguments into those situations, and if you really need data there are ways to get a response before the process quits. This can be a very flexible solution for a number of situations, and worth looking into if you want something that’s non-blocking and can be run outside of TouchDesigner.

Subprocess calls can be infuriating if you’re not familiar with them, so let’s look at some simple anatomy of making this work from Touch.


Scenario 1 – Execute this Script

Let’s start with the most basic of scenarios. Here we have some Python script that normally takes a long time to run, that we just want to kick off from TouchDesigner. For this example let’s just look at something that will print to a shell – nothing fancy, just a place to get our bearings. Our python script might look something like this:

Pure Python

import time
import sys

# a variable a divider that we're going to use
divider = '- ' * 10

# a for loop to print all of the paths in our sys.path
for each in sys.path:
    print(each)

# our divider
print(divider)

# a for loop that prints numbers 
for each in range(10):
    print(each)

# a call to time.sleep to keep our terminal open so we can see what's happening
time.sleep(120)

This works just the way that we might expect if we run it in our OS. But how can we run this script from TouchDesigner?

In TouchDesigner

In TouchDesigner we’d add a DAT that has the following contents. Here we assume that the script above has been saved in a folder called scripts that’s in the same folder as our project file, and the name of the script is cmd_line_python.py.

import subprocess

# point to our script that we're going to execute
cmd_python_script = '{}/scripts/cmd_line_python.py'.format(project.folder)

# quick debug print
print(cmd_python_script)

# call our script with subprocess
subprocess.Popen(['python', cmd_python_script], shell=False)

This is great, but this will actually execute with the version of Python that’s packaged with TouchDesigner. In some cases that’s exactly what we want… in other’s we might want to use a different version of python that’s installed on our OS. How can we do that?


Scenario 2 – Execute this Script with a Specific Python

This is very similar to our first situation, but here we want to run the python that’s installed on our OS, not the python that’s packaged with Touch. In this case we can use the exact same python script we saw above.

Pure Python

import time
import sys

# a variable a divider that we're going to use
divider = '- ' * 10

# a for loop to print all of the paths in our sys.path
for each in sys.path:
    print(each)

# our divider
print(divider)

# a for loop that prints numbers 
for each in range(10):
    print(each)

# a call to time.sleep to keep our terminal open so we can see what's happening
time.sleep(120)

Our real changes come when we’re issuing the subprocess call in TouchDesigner.

In TouchDesigner

Again, we add a DAT that has the following contents. Here we assume that the script above has been saved in a folder called scripts that’s in the same folder as our project file, and the name of the script is cmd_line_python.py. The additional wrinkle this time, is that we also need to specify which python.exe we want to use for this process.

import subprocess

# point to our script that we're going to execute
cmd_python_script = '{}/scripts/cmd_line_python.py'.format(project.folder)

# point to the specific version of python that we want to use
python_exe = 'C:/Program Files/Python35/python.exe'

# quick debug print
print(cmd_python_script)

# call our script with subprocess
subprocess.Popen([python_exe, cmd_python_script], shell=False)

If we look closely at the example above, we can see that we’ve been very specific about where our python_exe lives. This approach runs the same script, only this time with the python.exe that we’ve specifically pointed to.


Scenario 3 – Passing Over Args

There are several ways to approach this challenge. One that will line up with the format of many pure pythonic approaches here would be to use the argparse library. We find this in lots of stand-alone scripts, and it allows us the flexibility of setting default arguments, and creating some relatively clean inputs when calling a script form the command line. In this approach we set up a function that we’ll call, and pass arguments into – in the example below that’s our My_python_method(). By using ArgumentParser we can pass in command line arguments that we can in turn pass through to our function. Notice the syntax at the bottom to see how this works. ArgumentParser returns keyword arguments, which is why we use kwargs as the mechanism for sending args into our simple for loop.

Pure Python

import time
import sys
import time
from argparse import ArgumentParser

def My_python_method(kwargs):

    disp_str = 'key: {} | value: {} | type: {}'
    for each_key, each_value in kwargs.items():
        formatted_str = disp_str.format(each_key, each_value, type(each_value))
        print(formatted_str)

    # keep the shell open so we can debug
    time.sleep(int(kwargs.get('delay')))

# execution order matters -this puppy has to be at the bottom as our functions are defined above
if __name__ == '__main__':
    parser = ArgumentParser(description='A simple argument input example')
    parser.add_argument("-i", "--input", dest="in", help="an input string", required=True)
    parser.add_argument("-i2", "--input2", dest="in2", help="another input", required=True)    
    parser.add_argument("-d", "--delay", dest="delay", help="how long our terminal stays up", required=False, default=10)
    
    args = parser.parse_args()
    My_python_method(vars(args))
    pass

# example
# python .\cmd_line_python_args.py -i="a string" -i2="another string" -d=15

Where this becomes more interesting is when we look at what’s happening on the TouchDesigner side of this equation.

In TouchDesigner

A DAT in TouchDesigner needs to follow the same rules we established so far – we need to know what executable we’re using, which file we’re running, and finally we now need to send along some arguments that will be passed to that file. In Touch, our script this time should look something like this:

import subprocess

# point to our script that we're going to execute
cmd_python_script = '{}/scripts/cmd_line_python_args.py'.format(project.folder)

# construct a list of arguments for out external script
script_args = ['-i', 'Hello', '-i2', 'TouchDesigner']

# join our python instructions with our scirpt args
command_list = ['python', cmd_python_script] + script_args

# call our script with subprocess
subprocess.Popen(command_list, shell=False)

Scenario 4 – I Want a Message Back

Like all things Touch, and all things Python there are LOTS of ways to accomplish this task. One way we might want to consider, however, is using UDP messages. Touch happens to have a handy UDPIn DAT that’s ready to accept messages, and the other benefit here is that we could potentially target another machine on our network as the target for these messages. For this first exploration let’s imagine that we’re only sending a message locally, and that all of our variables are defined in the python script we’re running. We’ll need to use the socket library to help with the communication elements, and you’ll notice that we import that at the top of our script. This silly example just creates a UDP connection, and sends messages at a regular interval.

Pure Python

import time
import sys
import socket

upd_ip = "127.0.0.1"
udp_port = 7000
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

num_iters = 11
sleep_interval = 2

def msg_to_bytes(msg):
    return msg.encode('utf-8')

starting_msg = "Sending messages at an interval of {} seconds".format(sleep_interval)
sock.sendto(msg_to_bytes(starting_msg), (upd_ip, udp_port))

for each in range(num_iters):
    msg = "{} of {}".format(each, num_iters-1)
    sock.sendto(msg_to_bytes(msg), (upd_ip, udp_port))
    time.sleep(sleep_interval)

ending_msg = "All messages sent"
sock.sendto(msg_to_bytes(ending_msg), (upd_ip, udp_port))

In TouchDesigner

For this simple execution in TouchDesigner we only need to worry about kicking off the script. That looks almost exactly like the other pieces we’ve set up so far.

import subprocess

# point to our script that we're going to execute
cmd_python_script = '{}/scripts/cmd_line_python_udp_msg.py'.format(project.folder)

# print our script path - quick debug
print(cmd_python_script)

# clear the last entries from the UDPin DAT
op('udpin1').par.clear.pulse()

# call our script with subprocess
subprocess.Popen(['python', cmd_python_script], shell=True)

Our catch this time is that we need to use a UDPIn DAT to receive those messages. Let’s also make sure the Row/Callback Format parameter is set to One Per Message. With this all set up we should see something like this when we kick off the script.


Scenario 5 – Messages, I can has args?!

That all seems mighty fine… but, what happens when I want to combine what we’ve done with passing along arguments, and messages? I’m so glad you asked. With a little extra work we can make exactly that happen. We do need to do a little more heavy lifting on the python front, but that work gives us some extra flexibility. You’ll notice below that we’re now passing along which port we want to use, how many iterations of our for loop, and the interval between repetitions.

Pure Python

import time
import sys
import socket
from argparse import ArgumentParser

def msg_to_bytes(msg):
    return msg.encode('utf-8')

def msg_loop(port, interval, loop):
    # localhost
    upd_ip = "127.0.0.1"

    # set udp port with input val and initialize socket connection
    udp_port = int(port)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # set additional variables with input vals
    num_iters = int(loop)
    sleep_interval = int(interval)

    # send message that we're starting
    starting_msg = "Sending messages at an interval of {} seconds".format(sleep_interval)
    sock.sendto(msg_to_bytes(starting_msg), (upd_ip, udp_port))

    # run message loop
    for each in range(num_iters):
        msg = "{} of {}".format(each, num_iters-1)
        sock.sendto(msg_to_bytes(msg), (upd_ip, udp_port))
        time.sleep(sleep_interval)

    # send message that we're ending
    ending_msg = "All messages sent"
    sock.sendto(msg_to_bytes(ending_msg), (upd_ip, udp_port))

# execution order matters - this puppy has to be at the bottom as our functions are defined above
if __name__ == '__main__':
    parser = ArgumentParser(description='A simple UDP example')
    parser.add_argument("-p", "--port", dest="port", help="UDP port", required=True, default=1234)
    parser.add_argument("-i", "--interval", dest="interval", help="loop interval", required=True, default=5)
    parser.add_argument("-l", "--loop", dest="loop", help="number of repetitions", required=True, default=10)
    args = parser.parse_args()

    msg_loop(args.port, args.interval, args.loop)
    pass

# example
# python .\cmd_line_python_udp_msg_args.py -p=5000 -i=2 -l=10

In TouchDesigner

Over in TouchDesigner, our subprocess script looks very similar to what we’ve done so far with just a few modifications.

import subprocess

# set up our variables for our subprocess call
port = str(op('udpin2').par.port.val)
interval = '1'
loop = '15'

# point to our script that we're going to execute
cmd_python_script = '{}/scripts/cmd_line_python_udp_msg_args.py'.format(project.folder)

# construct a list of our python args - which python and which script
python_args = ['python', cmd_python_script]

# construct a list of arguments for out external script
script_args = ['-p', port, '-i', interval, '-l', loop]

# join our two lists - python args and scirpt args
cmd_args = python_args + script_args

# quick debug print
print(cmd_args)

# clear the last entries from the UDPin DAT
op('udpin2').par.clear.pulse()

# call our script with subprocess
subprocess.Popen(cmd_args, shell=True)

Here the resulting messages look very similar, only we’ve not gotten to specify all the qualities about the for loop from our script.


What does this Matter?

Well, there are lots of things you might do with this, but especially interesting might be considering how you can use otherwise very costly and slow operations in Python with this approach. For example, a recent set of Style Transfer experiments I was working on used this style of approach to essentially create a TouchDesigner front end / UI for a pytorch style transfer backend. This let me pass along arguments from the UI over to a pure python execution that didn’t block or freeze touch while it was running. There are lots of ways you might get into mischief with this kind of work, and it’s worth pointing out that while all this is focused on python, there’s no reason you couldn’t instead think of other applications you want to run with a particular file and a set of command line arguments.

Happy Programming!


What to follow along? Download the sample code from github