# 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.

# 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 | 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

# TouchDesigner | Stoner Tricks

There was a great question that recently popped up on the Forum about using the Stoner component from the Palette.

Every time I use the stoner tool I delete these ops first thing.

For some reason the locked TOP doesn’t seem to have anything to do with the real output, yet it saves that data with the TOE and it’s easy for the toe to be huge for no reason.

I had 3 4K stoners and the file was 150 mb. Remove these ops and the toe goes to 140KB

Stoner throws errors when moving points after deleting these, however doesn’t seem to impact the functionality of the stoner, it still outputs the correct UV and warp texture. It still persists the data after a save.

Can someone explain why those ops are even there? it looks only like it’s saving the demo image before you start using it.

Read the whole thread

Long story short, what looks like a ramp is actually a displacement map. The idea here is that you can actually get all of the benefits of the stoner’s displacement, without running the whole component. Unless your mapping is changing dynamically, you can instead use this texture to drive a remap TOP which in turn handles your distortion. Richard Burns wrote a lovely little piece about this on Visualesque.

I wrote about what these ops are good for in a post a few years ago when working on a short installation that was in Argentina – Building a Calibration UI. Sadly, I never got to the second part of that post to dig into how we could actually use this feature of the stoner. Fast forward a few years and when collaborating with Zoe Sandoval on their thesis project (which featured four channels of projection) – { remnants } of a { ritual } – I used a very similar approach to leveraging Stoner’s flexibility to use a single UI for multiple displacement maps.

So… how do we actually use it?!

Well, I finally had some time to knock out a walk through of how to make this work in your projects, some python to help you get it moving and organized quickly, and ways to keep your calibration data out of your project file. Hope this sheds some light on some of the ways you can better take advantage of the Stoner.

Check out a sample project here.

# TouchDesigner | Previs for Moving Lights

I got an interesting question a few weeks ago about how to use tracking data to control moving lights. If you work with Touch long enough, at some point you’ll almost always end up wanting to drive some object in the real world with information derived from calculations in Touch. Where / how can you get started with that process?! It’s often temping to straight away jump into just driving the physical object. After all you know what you’re trying to do, and you might have a sense of how to get there – so what’s the harm?

That’s not a bad instinct, but I almost always like to start with some form of previs. You won’t always have access to all the equipment you want to use, and more importantly it’s often better to make sure you understand the problem you’re trying to solve before you start driving motors. The additional bonus here is that solid previs will create some opportunities for testing, and planning that you might not other wise have.

This post is going to look at:

• Planning / mapping out some simple previs for using tracking data on some moving lights
• Using the Object CHOP to calculate bearings for rotation information
• Some simple use of custom parameters and bindings
• Pattern matching for renaming
• Using the CHOP export method Channel Name is Path:Parameter

As a disclaimer, this is not a great approach for LOTS of lights – but is a great way to get started and make sure you understand what you’re trying to accomplish.

## Workspace Setup

I like a workspace where it’s easy to see multiple perspectives at the same time. In this case I’d like to see the the network editor (your typical Touch workspace), the geometry viewer, and a rendered view. We can split our workspace with the icon that looks like a little down arrow in the upper right corner of the pane bar:

In this case I’m going to split the workspace left/right, and then once top/bottom. In the pane on the top right I’m going to change the Pane Type to geometry.

On the Bottom I’m going to change the pane type to Panel. If we’re working with container COMPs this can be very handy as it lets us see the panel for the container. If you’ve created an interface, that means you can also interact with your controls from here, without having to open a floating window.

A clean project set up like this looks like:

Our last step here is going to be adding a Container COMP to our network. We also need to rename it, and make sure our two new windows on the right correctly reference our net container. Our network bar is path based, so we just need to make sure both of them have the address: `/project1/container_previs`

## Custom Parameters

I want to control the elements of my previs container with a set of custom parameters. This will help me reduce the places I have to look for making changes, and helps save me the step of building a UI to control this visualization. I already happen to know what pieces I want to add here:

• Transform controls for a tracked object
• Transform Controls for 3 lights
• Color Controls for 3 lights
• Dimmer Controls for 3 lights
• All of these should live on a page called “Settings”

In the end, our custom parameters should look like this:

I’m not going to go into huge detail here about how to set up the custom parameters here, but you can learn more about using custom parameters:

There is, however, one quick thing to point out. The addition of bindings in the Spring update comes along with a handy way to take quick advantage of them. We can use the drag and drop trick to add a set of custom parameters from another operator, and we can also auto assign all of our bindings in the process. Let’s take a look at that process.

Inside of `/project1/conatiner_previs` I’m going to add a light COMP. I’m going to first open the custom parameters dialog, and then add a page to my previs container called `Settings`. Next I’m going to grab the parameter `Translate` from my light comp, and drag it right onto the Parameter column in the customize dialogue. Here’s where the magic happens. Next I’ll select `Bind New Par as Master` from the drop-down, and ta-da – now your bindings are already set up for you:

## Scene Set-up

To set up this scene I’m going to use a few simple tricks. First I’m going to use a camera and a few pieces of geometry to get started. For something like the stage, I like a single top level Geo, with separate pieces nested inside. The benefit here is that if we scale or transform our top level Geo those changes will propagate to our nested elements:

You’ll notice a separate wire-frame version of the stage inside of our stage – this is to give us some nice grid lines. `geo2` is also transformed ever so slightly above `geo` as well, so we’ve got some nice clean rendering. Looking at the phong material for the primary stage, it’s got a slight gray emit color – so we can see it even when there’s no light on it’s surface.

We’ll also use a little trick with our camera. I’ve set my camera to look at a null COMP in our scene. This gives us some better handles for adjusting where our camera is looking without needing to manually set the rotation and transformation values. This is often a very helpful and easy way to get better camera controls by thinking spatially, rather than as transformation values.

Finally, I’m going to add another geo and change it to be a sphere. In this case I want some object to represent a moving object on my stage. I’m going to bind this object to my parent’s custom parameters that I already set-up for transformation. This means I can change the position of this geo either from the parent’s custom ops, or from the parameters on the geo.

## Light Set-up

Depending on the order you’ve done this, you may have already created lights to set-up your custom parameters. If you haven’t done that yet, now’s a good time to add some lights to your scene. I’m going to use three for now. I’m also going to use a table to hold the transformation information for our lights. I’m using a table here because I’m thinking of a situation where my lights aren’t going to move – theatrical lights are usually transformationaly stable, and instead just rotate. If you’re lights are going to move in xyz position, this isn’t the most optimal set-up. Next I’m going to convert my table of positions to CHOP data. In my `Dat to CHOP` I want to make sure that I’m using a channel per column, and that my fist row and first column are marked as names.

Next I’m going to use an object CHOP to find the position data for my target (that sphere we set-up in our scene). I’m going to plug my `datto1` into that a second object CHOP, and my first object CHOP into the second input. Next I’ll make sure that I’m computing `bearing`, and on the channel page I want to change the output range to be start/end. I want to change that to samples instead of seconds, and then make sure that I start at sample 0 and end at sample 2.

So what’s this all about? What we’re doing with this second object CHOP is calculating the rotational values that will tell us the how to look at our target with each of our lights. We can then use this to set the rotation of our lights so they follow our target object. We could also do this with a chain of Math CHOPs… but having done it both ways, they’re almost computationally identical, and this you can do with fewer operators. So now we know the rotation values we need to set our on lights to make sure they’re looking at our target.

Now, we could certainly write some complex references for these values, but we can also learn a handy trick that I don’t see used too many places. Here we’re going to look at another CHOP export configuration. Before we get there, we need to flatten out our CHOP data. To do this we can use a shuffle CHOP set to split all samples:

This is swell, but if we don’t want to go through the process of exporting these one by one to our lights, what can we do? Well, it’s handy to know that there’s another way to use CHOP exports. There happens to be an export method called `Channel Name is Path:Parameter`. What that means is that if we change the name of our channel to be formatted so it’s the path to the operator followed by a colon and ending with the target parameter the exports will happen for us without any extra work.

Let’s take a quick detour to see how that works in isolation first. Let’s first add a constant CHOP, connected to a null CHOP. Finally let’s add another geo COMP to see how this works.

Next let’s name some channels in our constant. I want to add the following:

• `geo1:tx`
• `geo1:ty`
• `geo1:tz`

Next on my null CHOP I’m going to go the common page and change the export method to `Channel Name is Path:Parameter`. Finally, I’m going to turn on the export flag for the null CHOP, and ta-da. You’ve not exported values for tx, ty, and tz to your geo.

Okay, now if we go back to our flattened rotation info, we can imagine that if we just change the names of our channels we won’t have to do lots of dragging and dropping to get our exports sorted.

Let’s add a rename CHOP, and we can use some fancy pattern matching to do the renaming for us. In the `From` parameter we want `r*[0-2]`. What on earth does that mean? Well, any channel that starts with r, then has any character next, and then has a value of 0, 1, then 2. This happens sequentially, so we do all the `r0`s first, then the `r1`s and so on. That matters because our shuffled data is all rx values, then ry, and finally rz. We have to make a pattern matching schema that works works with that pattern.

Okay, so in our `To` parameter we want to use the pattern `light[1-3]:r[xyz]`. This means we’ll change our name space to be something like `light1:rx`. Again, this happens sequentially, so we’ll do all the 1s, then 2s, then 3s. What we end up with changes our original names like this:

Next we should be able to connect our null CHOP, set it to export as Channel Name is Path:Parameter, and we should be off to the races with all of our exports set up.

## Our Container

By the end of all of this we should have a handy little container that’s set-up so we can change the position of a target geometry, and have our lights automatically follow it around our stage. If you’ve gotten stuck along the way, check the bottom of the page for a link to a repo where you can download just a tox of the finished Container, or a whole toe file with our workspace setup.

## Other Considerations

What we haven’t talked about is getting your measurements and scaling right, or how to convert our rotational values into pan and tilt measurements, or how to convert that for controlling something with a protocol like DMX. Those are big concerns in their own right, but with a solid visualization you will at least have something to compare the real world against so you can start pulling apart those challenges.

Happy programming!

If you want to download this and look through the set-up you can find it on GitHub here.

# Python in TouchDesigner | Extensions | TouchDesigner

Rounding out some of our work here with Python is to look extensions. If you’ve been following along with other posts you’ve probably already looked over some extensions in this post. If you’re brand new to this idea, check out that example first.

Rather than re-inventing the wheel and setting up a completely new example, let’s instead look at our previous example of making a logger and see how that would be different with extensions as compared to a module on demand.

A warning for those following along at home, we’re now knee deep in Python territory, so what’ we’ll find here is less specific to TouchDesigner and more of a look at using Classes in Python.

In our previous example looking at modules we wrote several functions that we left in a text DAT. We used the mod class in TouchDesigner to treat this text DAT as a python module. That’s a neat trick, and for some applications and situations this might be the right approach to take for a given problem. If we’re working on a stand alone component that we want to use and re-use with a minimal amount of additional effort, then extensions might be a great solution for us to consider.

Wait, what are extensions?! Extensions are way that we can extend a custom component that we make in TouchDesigner. This largely makes several scripting processes much simpler and allows us to treat a component more like an autonomous object rather than a complex set of dependent objects. If you find yourself re-reading that last statement let’s take a moment to see if we can find an analogy to help understand this abstract concept.

When we talked about for loops we used a simple analogy about washing dishes. In that example we didn’t bother to really think deeply about the process of washing dishes – we didn’t think about how much detergent, how much water, water temperature, washing method, et cetera. If we were to approach this problem more programatically we’d probably consider making a whole class to deal with the process of washing dishes:

 class Dishes(): def __init__( self ): return def Full_cycle( self, list_of_dishes ): # what if we want to wash and then dry? self.Wash( list_of_dishes, water_temp ) self.Dry( list_of_dishes ) return def Wash( self, list_of_dishes, water_temp ): # what does washing really entail # all of that would go here number_of_dishes = len( list_of_dishes ) detergent_amt = self.Set_soap( number_of_dishes ) water_temp = self.Set_water_temp( water_temp ) return def Dry( self, list_of_dishes ): # what does washing really entail # all of that would go here number_of_dishes = len( list_of_dishes ) dry_time = self.Set_dry( number_of_dishes ) return def Set_soap( self, number_of_dishes ): return def Set_water_temp( water_temp ): if water_temp == 'hot': temp = 98 elif water_temp == 'medium': temp = 80 if water_temp == 'low': temp = 60 return temp def Set_dry( self, number of dishes ): return

Okay, so the above seems awfully silly… what’s going on here? In our silly example we can see that rather than one long function for Wash() or Dry(), we instead use several smaller helper functions in the process. Why do this? Well, for one thing it lets you debug your code much more easily. It also means that by separating out some of these elements into different processes we can fix a single part of our pipeline without having to do a complete refactor of the entire Wash() or Dry() methods. Why does that matter? Well, what if we decide that there’s a better way to determine how much soap to use. Rather than having to sort through a single long complex method for Wash() we can instead just look at Set_soap(). It makes unit testing easier, and allows us to replace or develop that method outside the context of the larger method. It also means that if we’re collaborating with other programmers we can divide up the process of writing methods.

What’s this self. business? self. allows us to call a method from inside another method in the same class. This is where extensions begin to really shine as opposed to using modules on demand. We an also use things like attributes andinheritance.

Okay, so how can we think about actually using this?

Let’s take a look at what this approach looks like in python:

 import datetime log_file = op( 'text_log_file' ) log_path = "example_extensions/log_files/log.txt" full_text = '''{now} Current Year | {year} Current Month | {month} Current Day | {day} Current Hour | {hour} Current Minute | {minute} Current Second | {second} Current Microsecond | {microsecond} ''' raw_date_time = "On {month}-{day}-{year} at {hour}:{minute}:{second}" verbose_log_message = '''============================ VERBOSE MESSAGE {date_time} —————————- operator || {operator} At Network Location || {path} —————————- Logged {message} ============================ ''' log_message = '''—————————- {now} —————————- {operator} {path} {message} ''' class Ext_example(): def __init__( self ): '''The init function. We're not doing anything with our init function in this example so we'll leave this empty. Notes ————— ''' return def Log_date( self ): year = datetime.datetime.now().year month = datetime.datetime.now().month day = datetime.datetime.now().day hour = datetime.datetime.now().hour minute = datetime.datetime.now().minute second = datetime.datetime.now().second updated_log_date = log_date.format( month = month, day = day, year = year, hours = hour, minutes = minute, seconds = second ) return updated_log_date def Log_date_time( self ): '''Create a formatted time stamp A look at how we might create a formatted time stamp to use with various logging applications. Arguments ————— None Returns ————— formatted_text( str ) – a string time stamp Notes ————— ''' now = datetime.datetime.now() year = datetime.datetime.now().year month = datetime.datetime.now().month day = datetime.datetime.now().day hour = datetime.datetime.now().hour minute = datetime.datetime.now().minute second = datetime.datetime.now().second microsecond = datetime.datetime.now().microsecond date_time = raw_date_time.format( month = month, day = day, year = year, hour = hour, minute = minute, second = second ) return date_time def Log_message( self, operator, message, verbose=False, text_port_print=True, append_log=True ): '''Logging Method. A simple look at how you might start to think about building a logger for a TouchDesigner application. A logger is a great way to build out files with time stamped events. The more complex a project becomes, the more important it can become to have some means of logging the operations of your program. Here's a simple look at what that might look like. Arguments ————— operator( touch object ) – the touch object whose path you'd like incldued in the log message message( str ) – a message to include in the log verbose( bool ) – a toggle for verbose or compact messages text_port_print( bool ) – a toggle to print to the text port, or not append_log( bool ) – a toggle to append to the log file , or not Example ————— target_op = op( 'constant1' ) message = "This operator needs attention" parent().Log_message( target_op, message ) also parent().Log_message( target_op, message, verbose = True ) Returns ————— None Notes ————— You'll notice that some arguments receive default values. This is so you don't have to include them in the call. This means that by default the message will be compact, will print to the text port, and will append the log file. ''' path = operator.path op_name = operator.name # logic tests for verbose or compact if verbose: message = verbose_log_message.format( date_time = self.Log_date_time(), operator = op_name, path = path, message = message ) else: message = log_message.format( now = self.Log_date_time(), operator = op_name, path = path, message = message ) # logic tests for text_port_print if text_port_print: print( message ) else: pass # log tests for appending log if append_log: log_file.write( '\n' + message ) else: pass # save the log file to disk – external from the TouchDesigner project self.Save_log() return def Save_log( self ): '''Saves log to disk. This helper function saves the log file to disk. Notes ————— None ''' op( log_file ).save( log_path ) return def Clear_log( self ): '''Clears Log File. This helper function clears the text dat used to hold the log file. Notes ————— None ''' op( log_file ).clear() return

So getting started we set up a couple of variables that we were going to reuse several times. Next we declared our class, and then nested our methods inside of that class. You’ll notice that different from methods, we need to include the argument self in our method definitions:

```# syntax for just a function
def Clear_log():
return

# syntax for just a method in a class
def Clear_log( self ):
return```

We can also see how we’ve got several helper functions here to get us up and running – ways to add to the log, save the log to an external file, a way to clear the log. We can imagine in the future we might want to add another method that both clears the log, and saves an empty file, like a reset. Since we’ve already broken those functions into their own methods we could simply add this method like this:

```def Reset_log():
self.Clear_log()
self.Save_log()
return```

Now it’s time to experiment.

Over the course of this series we’ve looked at lots of fundamental pieces of working with Python in TouchDesigner. Now it’s your turn to start playing and experimenting.

Happy programming!

# Python in TouchDesigner | Modules | TouchDesigner

There are a number of ways that we might use modules on demand in TouchDesigner. Before we get too far along, however, we might first ask “what is a module on demand?”

According to the TouchDesigner wiki:

``````The MOD class provides access to Module On Demand object,
which allows DATs to be dynamically imported as modules.
It can be accessed with the mod object, found in the automatically
imported td module.

Alternatively, one can use the regular python statement: import.
Use of the import statement is limited to modules in the search path,
where as the mod format allows complete statements in one line,
which is more useful for entering expressions. Also note that DAT modules
cannot be organized into packages as regular file system based
python modules can be.
``````

What does this mean? It’s hard to sum up in just a single sentence, but the big thing to take away is that we can essentially use any text DAT to hold whole functions for us that we can then call whenever we want.

Let’s take a closer look at this process. We’ll start with some simple ideas, then work our way up to something a little more complicated.

First we turn things way down, and just think about storing variables. To be clear, we probably wouldn’t use this in a project, but it can be helpful for us when we’re trying to understand what exactly is going on here.

Let’s create a new text DAT and call it “text_variables”, inside let’s put the following text:

```width       = 1280
height      = 720

budget      = 'small'```

Using the mod class we can access these variables in other operators! To do this we’ll use the following syntax:

```mod( 'text_variables' ).width
mod( 'text_variables' ).height
mod( 'text_variables' ).budget```

Try adding a constant CHOP, and a text TOP to your network and using the expressions above to retrieve these values.

Next try printing these values:

```print( mod( 'text_variables' ).width )
print( mod( 'text_variables' ).height )
print( mod( 'text_variables' ).budget )```

So, it looks like we can access the contents of a module as a means of storing variables. That’s hip. Let’s take a moment and circle back to one of the other use cases that we’ve already seen for a module. More than just a single value, we can also put a whole dictionary in a module and then call it on demand. We’ve already done this in some of our previous examples, but we can take a quick look at that process again to make sure we understand.

Let’s create a new text DAT called “text_dictionary_as_module”, inside of this text DAT let’s define the following dictionary:

```fruit = {
"apple" : 10,
"orange"    : 5,
"kiwi"  : 16
}```

Let’s first print the whole dictionary object:

`print( mod( 'text_dictionary_as_module' ).fruit )`

Alternatively, we can also access individual keys in the dictionary:

```mod( 'text_dictionary_as_module' ).fruit[ 'apple' ]
mod( 'text_dictionary_as_module' ).fruit[ 'orange' ]
mod( 'text_dictionary_as_module' ).fruit[ 'kiwi' ]```

What can you do with this?! Well, you might store your config file in a text DAT that you can call from a module on demand. You might use this to store configuration variables for your UI – colors, fonts, etc. ; you might decide to use this to configure some portion of a network, or to hold on to data that you want to recall later; or really any number of things.

Before we get too excited about storing variables in modules on demand, let’s look at an even more powerful feature that will help us better understand where they really start to shine.

Up next we’re going to look at writing a simple function that we can use as a module on demand. In addition to writing some simple little functions, we’re also going to embrace docstrings – a feature of the python language that makes documenting your work easier. Docstings allow us to leave behind some notes for our future selves, or other programmers. One of the single most powerful changes you can make to how you work is to document your code. It’s a difficult practice to establish, and can be frustrating to maintain – but it is hands down one of the most important changes you can make in how you work.

Alright, I’ll get off my documentation soapbox for now. Let’s write a few methods and see how this works in TouchDesigner.

We can start by creating a new text DAT called “text_simple_reutrn”, inside of this DAT we’ll write out our new functions:

 def multi_by_two( value ): '''Multiplies input value by 2 A simple example function to see how we can use modules on demand. This module takes a single argument which is multiplied by 2 and then returned from the function. Arguments ————— value( int / float ) – numeric value to be multiplied by 2 Returns ————— new_val( int / float ) – value * 2 Notes ————— These are doc strings – they're a feature of the Python language and make documenting your code all easier. This format is based largely on Google's Python documentation format – though not exactly. It's generally good practice to document your work, leaving notes both for your future self, as well as for other programmers who might be using your code in the future. ''' new_val = value * 2 return new_val def logic_test( even_or_odd ): '''Tests if input value is even or odd This is a simple little function to test if an integer is even or odd. Arguments ————— even_or_odd( int ) – an integer to be tested as even or odd Returns ————— test( str ) – string result of the even / odd test Notes ————— These are doc strings – they're a feature of the Python language and make documenting your code all easier. This format is based largely on Google's Python documentation format – though not exactly. It's generally good practice to document your work, leaving notes both for your future self, as well as for other programmers who might be using your code in the future. ''' if even_or_odd % 2: test = "this value is odd" else: test = "this value is even" return test def logic_test_two( value ): '''Silly logit test example Another simple function, this one to see another example of a logic test working in a module on demand. Arguments ————— value( int / float / str / bool ) – a value to be tested Returns ————— test( str ) – a string indicating the status of the test Notes ————— These are doc strings – they're a feature of the Python language and make documenting your code all easier. This format is based largely on Google's Python documentation format – though not exactly. It's generally good practice to document your work, leaving notes both for your future self, as well as for other programmers who might be using your code in the future. ''' if value == "TouchDesigner": test = "Nice work" else: test = "Try again" return test

view raw
module-example
hosted with ❤ by GitHub

Great! But what can we do with these? We can start by using some eval DATs or print statements to see what we’ve got. I’m going to use eval DATs. Let’s add several to our network and try out some calls to our new module on demand. First let’s look at the generic syntax:

mod( name_of_text_dat ).name_of_method

In practice that will look like:

```mod( 'text_simple_reutrn' ).multi_by_two( 5 )
mod( 'text_simple_reutrn' ).multi_by_two( 2.5524 )
mod( 'text_simple_reutrn' ).logic_test( 5 )
mod( 'text_simple_reutrn' ).logic_test( 6 )
mod( 'text_simple_reutrn' ).logic_test_two( "TouchDesigner" )```

Now we can see that we wrote several small functions that we can then call from anywhere, as long as we know the path to the text DAT we’re using as a module on demand! Here’s where we start to really unlock the potential of modules on demand. As we begin to get a better handle on the kind of function we might write / need for a project we can begin to better understand how to take full advantage of this feature in TouchDesiger.

## Doc Strings

Since we took the time to write out all of those doc strings, let’s look at how we might be able to print them out! Part of what’s great about doc strings is that there’s a standard way to retrieve them, and therefore to print them. This means that you can quickly get a some information about your function printed right in the text port. Let’s take a closer look by printing out the doc stings for all of our functions:

 # first let's clear the text port to make sure we're starting fresh clear() # Here we're printing out the doc strings for multi_by_two print( "The Doc Strings for multi_by_two are:" ) print( '\n' ) print( mod( 'text_simple_reutrn' ).multi_by_two.__doc__ ) # Here we're printing out the doc strings for lotic_test print( "The Doc Strings for logic_test:" ) print( '\n' ) print( mod( 'text_simple_reutrn' ).logic_test.__doc__ ) # Here we're printing out the doc strings for logic_test_two print( "The Doc Strings for logic_test_two:" ) print( '\n' ) print( mod( 'text_simple_reutrn' ).logic_test_two.__doc__ )

view raw
td-docstring-example
hosted with ❤ by GitHub

That worked pretty well! But looking back at this it seems like we repeated a lot of work. We just learned about for loops, so let’s look at how we could do the same thing with a loop instead:

 # first let's clear the text port to make sure we're starting fresh clear() # rather than wasting our time writing all the code in the other example, # instead let's write a for loop to automate that process. # We'll start by first making a list of all of the methods we want to print # doc strings for methods = [ "multi_by_two", "logic_test", "logic_test_two" ] # next we'll make a smiple placeholder expression that we can # pass each method into so we can print it out easily doc_string_temp = "mod( 'text_simple_reutrn' ).{target_function}.__doc__" # finally we write a little for loop to go through all items in our list # and pretty print their doc strings to the text port for method in methods: print( "The Doc Strings for {} are:".format( method ) ) temp_doc = doc_string_temp.format( target_function = method ) print( eval( temp_doc ) ) print( "= " * 10 )

## A Practical Example

This is all fun and games, but what can we do with this? There are any number of functions you might write for a project, but part of what’s exciting here is the ability to write something re-usable in Python. What might that look like? Well, let’s look at an example of a logger. There are a number of events we might want to log in TouchDesigner when we have a complex project.

In our case we’ll write out a method that allows a verbose or compact message, a way to print it to the text port or not, and a way to append a file or not. Alright, here goes:

 import datetime log_file = op( 'text_log' ) full_text = '''{now} Current Year | {year} Current Month | {month} Current Day | {day} Current Hour | {hour} Current Minute | {minute} Current Second | {second} Current Microsecond | {microsecond} ''' verbose_log_message = '''============================ VERBOSE MESSAGE On {month}-{day}-{year} at {hour}:{minute}:{second} —————————- operator || {operator} At Network Location || {path} —————————- Logged {message} ============================ ''' log_message = '''—————————- {now} —————————- {operator} {path} {message} ''' def Full_date(): '''Create a formatted time stamp A look at how we might create a formatted time stamp to use with various logging applications. Arguments ————— None Returns ————— formatted_text( str ) – a string time stamp Notes ————— ''' now = datetime.datetime.now() year = datetime.datetime.now().year month = datetime.datetime.now().month day = datetime.datetime.now().day hour = datetime.datetime.now().hour minute = datetime.datetime.now().minute second = datetime.datetime.now().second microsecond = datetime.datetime.now().microsecond formatted_text = full_text.format( now = now, year = year, month = month, day = day, hour = hour, minute = minute, second = second, microsecond = microsecond ) return formatted_text def Log_message( operator, message, verbose=False, text_port_print=True, append_log=True ): '''Logging Method. A simple look at how you might start to think about building a logger for a TouchDesigner application. A logger is a great way to build out files with time stamped events. The more complex a project becomes, the more important it can become to have some means of logging the operations of your program. Here's a simple look at what that might look like. Arguments ————— operator( touch object ) – the touch object whose path you'd like included in the log message message( str ) – a message to include in the log verbose( bool ) – a toggle for verbose or compact messages text_port_print( bool ) – a toggle to print to the text port, or not append_log( bool ) – a toggle to append to the log file , or not Returns ————— None Notes ————— You'll notice that some arguments receive default values. This is so you don't have to include them in the call. This means that by default the message will be compact, will print to the text port, and will append the log file. ''' now = datetime.datetime.now() year = datetime.datetime.now().year month = datetime.datetime.now().month day = datetime.datetime.now().day hour = datetime.datetime.now().hour minute = datetime.datetime.now().minute second = datetime.datetime.now().second microsecond = datetime.datetime.now().microsecond path = op( operator ).path op_name = op( operator ).name if verbose: message = verbose_log_message.format( month = month, day = day, year = year, hour = hour, minute = minute, second = second, operator = op_name, path = path, message = message ) else: message = log_message.format( now = now, operator = op_name, path = path, message = message ) if text_port_print: print( message ) else: pass if append_log: log_file.write( '\n' + message ) else: pass return

So now that we’ve written out the method, what would call for this look like?

```operator = me

message ='''
Just a friendly message from your TouchDesigner Network.
Anything could go here, an error message an init message.

You dream it up, and it'll print
'''

# print and append log file with a verbose log message
mod( 'text_module1' ).Log_message( operator, message, verbose = True )

# print and append log file with a compact log message
# mod( 'text_module1' ).Log_message( operator, message )

# append log file with verbose log message
# mod( 'text_module1' ).Log_message( operator, message, verbose = True, text_port_print = False )

# print a compact log message
# mod( 'text_module1' ).Log_message( operator, message, append_log = False )```

Take a moment to look at the example network and then un-comment a line at a time in the text DAT with the script above. Take note of how things are printed in the text port, or how they’re appended to a file. This is our first generalized function that has some far reaching implications for our work in touch. Here we’ve started with a simple way to log system events, both to a file and to the text port. This is also a very re-usable piece of code. There’s nothing here that’s highly specific to this project, and with a little more thought we could turn this into a module that could be dropped into any project.

## Local Modules

We’ve learned a lot so far about modules on demand, but the one glaring shortcoming here is that we need the path to the text DAT in question. That might not be so bad in some cases, but in complex networks writing a relative path might be complicated, and using an absolute path might be limiting. What can we do to solve this problem. We’re in luck, as there’s one feature of modules we haven’t looked at just yet. We can simplify the calling / locating of modules with a little extra organization.

First we need to add a base and rename it to “local”, inside of this base add another base and rename it to “modules”. Perfect. I’m going to reuse one of our existing code examples so we can see a small change in syntax here. I’ve also changed the name of the text DAT inside of local>modules to “simple_return”.

```mod.simple_return.multi_by_two( 5 )
mod.simple_return.multi_by_two( 2.5524 )
mod.simple_return.logic_test( 5 )
mod.simple_return.logic_test( 6 )
mod.simple_return.logic_test_two( "TouchDesigner" )
mod.simple_return.logic_test_two( 10 )```

Looking at the above, we can see that we were able to remove the parentheses after “mod”. But what else changed? Why is this any better? The benefit to placing this set of functions in local>modules is that as long as you’re inside of this component, you no longer need to use a path to locate the module you’re looking for.

Alright, now it’s time for you to take these ideas out for a test drive and see what you can learn.