├── 088 ├── .gitignore ├── base_dictionary_sorting.tox ├── example_data_structures │ ├── data_structures_dictionary │ │ ├── data_structures_dictionary.tox │ │ ├── readme.md │ │ ├── text_adding_dictionary_items.py │ │ ├── text_first_dictionary1.py │ │ ├── text_first_dictionary2.py │ │ ├── text_first_dictionary3.py │ │ ├── text_first_dictionary4.py │ │ ├── text_read_me_examples.py │ │ ├── text_top_dictionary.py │ │ ├── text_top_dictionary_presets.py │ │ ├── text_top_dictionary_presets1.py │ │ └── text_top_dictionary_presets2.py │ ├── data_structures_list │ │ ├── container_radio_buttons.tox │ │ ├── data_structures_list.tox │ │ ├── readme.md │ │ ├── text_accessing_a_list.py │ │ ├── text_appending_lists.py │ │ ├── text_channels_and_lists1.py │ │ ├── text_channels_and_lists2.py │ │ ├── text_containers_and_lists1.py │ │ ├── text_grocery_list.py │ │ ├── text_items_in_our_first_list.py │ │ ├── text_lists_in_lsts.py │ │ ├── text_our_first_list.py │ │ └── text_sops_and_lists1.py │ ├── example_data_structures.tox │ └── readme.md ├── example_dictionary_loops │ ├── example_dictionary_loops.tox │ ├── readme.md │ ├── simple_mod_example.py │ ├── text_cue_list.py │ ├── text_cue_list_play.py │ ├── text_data.py │ ├── text_defaults.py │ ├── text_execute_loop.py │ ├── text_execute_loop_pars.py │ ├── text_replicator.py │ ├── text_simple_dictionary_loop0.py │ ├── text_simple_dictionary_loop1.py │ ├── text_simple_dictionary_loop2.py │ ├── text_simple_dictionary_loop3.py │ ├── text_simple_dictionary_loop4.py │ ├── text_simple_json.json │ ├── text_test_dictionary.py │ └── text_using_json.py ├── example_executes │ ├── example_executes.tox │ └── readme.md ├── example_extensions │ ├── example_extensions.tox │ ├── example_extensions_class.py │ ├── log_files │ │ └── log.txt │ ├── readme.md │ ├── text_test_clear.py │ ├── text_test_date_time.py │ ├── text_test_log_compact.py │ ├── text_test_log_verbose.py │ └── text_test_save.py ├── example_for_loop │ ├── example_for_loop.tox │ ├── readme.md │ ├── text_a_first_loop.py │ ├── text_a_first_loop2.py │ ├── text_channels_as_lists.py │ ├── text_create_ops_loop.py │ ├── text_enumerating_lists.py │ ├── text_filling_a_table.py │ ├── text_list_comprehensions.py │ ├── text_pars_loop.py │ ├── text_tex3d_fill.py │ └── text_working_with_table_DATs.py ├── example_intro_to_functions │ ├── example_intro_to_functions.tox │ ├── readme.md │ ├── text_my_first_function1.py │ ├── text_my_first_function2.py │ ├── text_my_first_function3.1.py │ ├── text_my_first_function3.py │ ├── text_my_first_function4.py │ └── text_my_first_function5.py ├── example_logic │ ├── example_logic.tox │ ├── readme.md │ ├── text_comparing_different_CHOPs1.py │ ├── text_comparing_different_CHOPs2.py │ ├── text_comparing_different_CHOPs3.py │ ├── text_if_elif_with_unexpected_result.py │ ├── text_if_with_references.py │ ├── text_nested_logical_structures.py │ ├── text_references_and_printing1.py │ ├── text_references_and_printing2.py │ ├── text_references_and_printing_rounding1.py │ ├── text_references_and_printing_rounding2.py │ ├── text_revised_if_elif_test.py │ ├── text_simple_logic_pass.py │ ├── text_simple_logic_test.py │ ├── text_the_channel_class.py │ ├── text_two_variable_if_elif.py │ ├── text_using_and.py │ ├── text_using_and_in_elif.py │ ├── text_using_not.py │ ├── text_using_not_with_two_variables.py │ └── text_using_or.py ├── example_modules │ ├── example_modules.tox │ ├── readme.md │ ├── text_calling_a_dictionary.py │ ├── text_dictionary_as_module.py │ ├── text_log_example.py │ ├── text_module1.py │ ├── text_module_simple.py │ ├── text_printing_doc_strings_the_easy_way.py │ ├── text_printing_doc_strings_the_hard_way.py │ ├── text_simple_reutrn.py │ ├── text_variables.py │ └── text_variables_in_modules.py ├── example_op_class │ ├── example_op_class.tox │ ├── readme.md │ ├── text_change_op_color.py │ ├── text_create_ops.py │ ├── text_create_ops_loop.txt │ └── text_restore_op_color.py ├── example_print │ ├── example_print.tox │ ├── readme.md │ ├── text_read_me_floats.md │ ├── text_read_me_ints.md │ ├── text_read_me_strings.md │ └── text_read_me_strings_and_nubmers.md ├── example_variables │ ├── example_references │ │ ├── example_references.tox │ │ └── readme.md │ ├── example_variables.tox │ └── readme.md └── python_in_touchdesigner.toe ├── 099 ├── README.md ├── base_inheritance_099.tox └── base_the_channel_class.tox └── README.md /088/.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | 45 | # Touch Files 46 | ###################### 47 | CrashAutoSave.toe 48 | *.dmp -------------------------------------------------------------------------------- /088/base_dictionary_sorting.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/base_dictionary_sorting.tox -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/data_structures_dictionary.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_data_structures/data_structures_dictionary/data_structures_dictionary.tox -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_data_structures_dictioaries ## 8 | 9 | Dictionaries are another type of data structure that we can use in Python. They're similar to lists in that we can store information in them, and retrieve them easily. Dictionaries, however, are distinctly different from lists. Where lists are index based - which is to say that they have a specific order - dictionaries are key based. 10 | 11 | What does that mean, and why do we care?! We think of dictionaries as being a pair of things a key, and a value. We might think of this as a name and its corresponding piece of information. Let's look at something simple to get started. To get started let's go back to our grocery example when we were talking about lists. In making a list for our trip to the grocery store we listed all of the items we needed from the store. We didn't however, make any notes about quantity. Let's quickly make that list again: 12 | 13 | ```python 14 | grocery_list = [ 'eggs' , 'milk' , 'bread' , 'butter' , 'coffee' ] 15 | ``` 16 | 17 | This is great, and it tells us lots of information, but maybe not all of the information we need. If I'm going to the store myself, this is a fine list. If I'm asking someone else to pick up these things for me, well then I need at least one other piece of information - quantity. If we're using lists, we might do something clever, like make a list of lists with two items - the grocery item, and the desired quantity. That might look something like this: 18 | 19 | ```python 20 | grocery_list = [ 21 | ['eggs' , '1 dozen' ] , 22 | ['milk' , '1 pint' ] , 23 | ['bread' , '2 loaves' ] , 24 | ['butter' , '1 lb' ] , 25 | ['coffee', '2 lbs' ] 26 | ] 27 | ``` 28 | 29 | This works fine, and might be a great way to hold onto this information. We can, however, use a dictionary to do this same thing. In this case we're going to think of our grocery items as a keys, and quantities as values. Let's look at what means: 30 | 31 | ```python 32 | grocery_list = { 33 | 'eggs' : '1 dozen' , 34 | 'milk' : '1 pint' , 35 | 'bread' : '2 loaves' , 36 | 'butter' : '1 lb' , 37 | 'coffee': '2 lbs' 38 | } 39 | ``` 40 | 41 | It's important to note that I've used some indenting to make this easier to read, but another perfectly valid way to write this dictionary would be: 42 | 43 | ```python 44 | grocery_list = { 'eggs' : '1 dozen' , 'milk' : '1 pint' , 'bread' : '2 loaves', 'butter' : '1 lb' , 'coffee': '2 lbs' } 45 | ``` 46 | 47 | I just happen to think that anytime you can make something easier to read by a human, the better. 48 | 49 | Okay, let's talk about syntax here for a second. So the first thing we did was declare our dictionary as a variable. Next we used curly brackets to open our dictionary ( {} - these are curly brackets ). Next we wrote out our dictionary as key and value pairs separated by a colon - keys on the left, values on the right. Now in this example all of our values were strings, but they could just as easily have been integers, floats, booleans, lists, or even other dictionaries. 50 | 51 | This is all well and good, but how do we get things out of our dictionary? We know how to retrieve things from a list, but a dictionary is a little different. When retrieving something from a dictionary we typically use a key. Let's consider our first example again for a second. Let's say we want to print out the quantity of eggs that we're supposed to get from the store. We can do that like this: 52 | 53 | ```python 54 | print( grocery_list[ 'eggs' ] ) 55 | ``` 56 | 57 | We can also retrieve the contests of dictionary with .keys() and .values(): 58 | 59 | ```python 60 | print( grocery_list.keys() ) 61 | print( grocery_list.values() ) 62 | ``` 63 | 64 | In both of these cases we get a list of keys or values. 65 | 66 | It's also important to know how to add items to our dictionary. There are a few ways to go about this, but let's just look at one for now. We should start by creating an empty dictionary: 67 | 68 | ```python 69 | my_dictionary = {} 70 | ``` 71 | 72 | Now that we have an empty dictionary, we can add items to it. We do this by starting with the dictionary name, then placing the key in square brackets ([] these things), followed by an equal sign, and then what we want to be placed into the dictionary as the value. Let's look at an example: 73 | 74 | ```python 75 | my_dictionary[ 'new_item1' ] = "cookies" 76 | ``` 77 | 78 | Okay, now that we've added one key value pair, let's print out the keys and values in our list (to practice), and add a few more items: 79 | 80 | ```python 81 | my_dictionary = {} 82 | 83 | my_dictionary[ 'new_item1' ] = "cookies" 84 | 85 | print( my_dictionary.keys() ) 86 | print( my_dictionary.values() ) 87 | 88 | my_dictionary[ 'new_item2' ] = "cell_phones" 89 | 90 | print( my_dictionary.keys() ) 91 | print( my_dictionary.values() ) 92 | 93 | my_dictionary[ 'new_item3' ] = 55 94 | 95 | print( my_dictionary.keys() ) 96 | print( my_dictionary.values() ) 97 | 98 | my_dictionary[ 'new_item4' ] = [ 1 , 2 , 3 ] 99 | 100 | print( my_dictionary.keys() ) 101 | print( my_dictionary.values() ) 102 | ``` 103 | 104 | Wait... what did we just do there with item4?! Most of that should look pretty straightforward, and hopefully that makes sense for the most part. It is not, however, the most exciting part of using dictionaries. Dictionaries become the most exciting when we start to see how we can nest other lists or dictionaries inside of them. 105 | 106 | Let's look at a dictionary of mixed contents: 107 | 108 | ```python 109 | my_dictionary = { 110 | "apple" : "these are delicious" , 111 | "orange" : 12 , 112 | "kiwi" : 55.5 , 113 | "lots_of_things" : [ 114 | "paper" , 115 | "pens" , 116 | 44 , 117 | 10.4 118 | ] 119 | } 120 | ``` 121 | 122 | So we know how to get to the values associated with "apple" , "orange" , and "kiwi" , but how do we get to the contents of that list? Well, we can write something like this: 123 | 124 | ```python 125 | print( my_dictionary[ 'lots_of_things' ][ 0 ] ) 126 | print( my_dictionary[ 'lots_of_things' ][ 1 ] ) 127 | print( my_dictionary[ 'lots_of_things' ][ 2 ] ) 128 | print( my_dictionary[ 'lots_of_things' ][ 3 ] ) 129 | ``` 130 | 131 | Here we see the same syntax that we use when retrieving list items. 132 | 133 | That's wonderful! So what about when we store dictionaries inside of dictionaries? Let's look at a simple example. We can start by creating a dictionary with fruit's as our keys. Each fruit will have a corresponding dictionary of quantity, origin, and if the fruit is organic. Okay, what would that look like: 134 | 135 | ```python 136 | my_dictionary_of_dictionaries = { 137 | "apple" : { 138 | "quantity" : 10 , 139 | "origin" : "Vermont" , 140 | "organic" : True 141 | } , 142 | "orange" : { 143 | "quantity" : 20 , 144 | "origin" : "Califronia" , 145 | "organic" : False 146 | } , 147 | "kiwi" : { 148 | "quantity" : 26 , 149 | "origin" : "Mexico" , 150 | "organic" : False 151 | } , 152 | "grapes" : { 153 | "quantity" : 50 , 154 | "origin" : "Preu" , 155 | "organic" : True 156 | } 157 | } 158 | ``` 159 | 160 | That's pretty snazzy and all, but how do we pull things out of this data structure? We can follow the example we learned with lists, but instead of using index values, we can instead use keys. Let's look at just apple to get started: 161 | 162 | ```python 163 | print( "Let's just look at apple" ) 164 | print( "quanitity -" , my_dictionary_of_dictionaries[ 'apple' ][ 'quantity' ] ) 165 | print( "origin -" , my_dictionary_of_dictionaries[ 'apple' ][ 'origin' ] ) 166 | print( "organic -" , my_dictionary_of_dictionaries[ 'apple' ][ 'organic' ] ) 167 | ``` 168 | 169 | Here we can see that we start with our dictionary, with a key in square brackets, followed by another key in square brackets. Practice retrieving other keys - what about grapes, or oranges? Also practice by adding other keys inside of the fruit dictionaries. Don't forget to pay careful attention to where you've placed your commas and, remember that keys are strings so they need quotation marks. 170 | 171 | Once you've done that, let's consider how we might use something like a dictionary here in TouchDesigner. We're going to look something a little complex, but still relatively simple to help us get our bearings. Dictionaries can be a great help to us when we want to do things like creating save states. Let's first think about what it would mean to set the properties of a text TOP with the contents of a dictionary. 172 | 173 | Let's start by making our dictionary. I'm going to use the same names for our dictionary keys that we find in our parameter names - just to make sure we know exactly where a value is going. 174 | 175 | ```python 176 | top_dictionary = { 177 | "text" : 'monkey' , 178 | "fontsizex" : 15 , 179 | "alignx" : 1 , 180 | "aligny" : 1 , 181 | "fontcolorr" : 1.0 , 182 | "fontcolorg" : 0.0 , 183 | "fontcolorb" : 0.0 , 184 | "fontalpha" : 1.0 , 185 | "bgcolorr" : 0.0 , 186 | "bgcolorg" : 0.0 , 187 | "bgcolorb" : 0.0 , 188 | "bgalpha" : 1.0 189 | } 190 | ``` 191 | 192 | Now let's flesh out our script to change the parameters of our TOP: 193 | 194 | ```python 195 | target_text = op( 'text1' ) 196 | 197 | target_text.par.text = top_dictionary[ 'text' ] 198 | target_text.par.fontsizex = top_dictionary[ 'fontsizex' ] 199 | target_text.par.alignx = top_dictionary[ 'alignx' ] 200 | target_text.par.aligny = top_dictionary[ 'aligny' ] 201 | target_text.par.fontcolorr = top_dictionary[ 'fontcolorr' ] 202 | target_text.par.fontcolorg = top_dictionary[ 'fontcolorg' ] 203 | target_text.par.fontcolorb = top_dictionary[ 'fontcolorb' ] 204 | target_text.par.fontalpha = top_dictionary[ 'fontalpha' ] 205 | target_text.par.bgcolorr = top_dictionary[ 'bgcolorr' ] 206 | target_text.par.bgcolorg = top_dictionary[ 'bgcolorg' ] 207 | target_text.par.bgcolorb = top_dictionary[ 'bgcolorb' ] 208 | target_text.par.bgalpha = top_dictionary[ 'bgalpha' ] 209 | ``` 210 | 211 | That's pretty great - but goodness that's a lot of work just to change some settings. How might we think about using this idea to create a preset system? We're not that far off form this idea at this point, so let's dig in a little deeper. To really make this work, we need to revisit our dictionary. Specifically, we need to encapsulate our presets inside another layer. We need to make them their own dictionary as a set of values for another key. For example, we might want a named structure like "preset1" , "preset2" etc. to be how we retrieve settings. Let's change our dictionary to make that happen: 212 | 213 | ```python 214 | top_dictionary = { 215 | "preset1" : { 216 | "text" : 'monkey' , 217 | "fontsizex" : 15 , 218 | "alignx" : 1 , 219 | "aligny" : 1 , 220 | "fontcolorr" : 1.0 , 221 | "fontcolorg" : 0.0 , 222 | "fontcolorb" : 0.0 , 223 | "fontalpha" : 1.0 , 224 | "bgcolorr" : 0.0 , 225 | "bgcolorg" : 0.0 , 226 | "bgcolorb" : 0.0 , 227 | "bgalpha" : 1.0 228 | } , 229 | "preset2" : { 230 | "text" : 'pig' , 231 | "fontsizex" : 80 , 232 | "alignx" : 1 , 233 | "aligny" : 0 , 234 | "fontcolorr" : 0.0 , 235 | "fontcolorg" : 0.0 , 236 | "fontcolorb" : 1.0 , 237 | "fontalpha" : 1.0 , 238 | "bgcolorr" : 1.0 , 239 | "bgcolorg" : 1.0 , 240 | "bgcolorb" : 1.0 , 241 | "bgalpha" : 1.0 242 | } 243 | } 244 | ``` 245 | 246 | Not bad. Now, how can apply these presets to our top? To do this we're going to do one tricky thing. We're going to write our scripts so that a python variable can stand in our first key. This will mean that we only need to change a single variable before re-running our script. That would look like this: 247 | 248 | ```python 249 | 250 | target_text = op( 'text1' ) 251 | dictionary_preset = 'preset2' 252 | 253 | target_text.par.text = top_dictionary[ dictionary_preset ][ 'text' ] 254 | target_text.par.fontsizex = top_dictionary[ dictionary_preset ][ 'fontsizex' ] 255 | target_text.par.alignx = top_dictionary[ dictionary_preset ][ 'alignx' ] 256 | target_text.par.aligny = top_dictionary[ dictionary_preset ][ 'aligny' ] 257 | target_text.par.fontcolorr = top_dictionary[ dictionary_preset ][ 'fontcolorr' ] 258 | target_text.par.fontcolorg = top_dictionary[ dictionary_preset ][ 'fontcolorg' ] 259 | target_text.par.fontcolorb = top_dictionary[ dictionary_preset ][ 'fontcolorb' ] 260 | target_text.par.fontalpha = top_dictionary[ dictionary_preset ][ 'fontalpha' ] 261 | target_text.par.bgcolorr = top_dictionary[ dictionary_preset ][ 'bgcolorr' ] 262 | target_text.par.bgcolorg = top_dictionary[ dictionary_preset ][ 'bgcolorg' ] 263 | target_text.par.bgcolorb = top_dictionary[ dictionary_preset ][ 'bgcolorb' ] 264 | target_text.par.bgalpha = top_dictionary[ dictionary_preset ][ 'bgalpha' ] 265 | ``` 266 | 267 | Alright. Looking closely at the above, we can see that we need only change the variable "dictionary_preset" in order to fetch a whole different set of values. Not bad, right? 268 | 269 | Take some time to experiment with these ideas. As we head forward we're going to start to look at how we can use executes and for loops to see how we can really start to make headway in using Python. We've laid a lot of ground work so we can really plow ahead. 270 | 271 | [Learn more about Python Data Structures](https://docs.python.org/3.3/tutorial/datastructures.html) 272 | [THP 494 & 598 | Python Dictionaries](http://matthewragan.com/2015/03/31/thp-494-598-python-dictionaries-touchdesigner/) 273 | [TouchDesigner | FB HelpGroup | Presets](http://matthewragan.com/2015/07/29/touchdesigner-fb-helpgroup-presets/) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/text_adding_dictionary_items.py: -------------------------------------------------------------------------------- 1 | my_dictionary = {} 2 | 3 | my_dictionary[ 'new_item1' ] = "cookies" 4 | 5 | print( my_dictionary.keys() ) 6 | print( my_dictionary.values() ) 7 | 8 | my_dictionary[ 'new_item2' ] = "cell_phones" 9 | 10 | print( my_dictionary.keys() ) 11 | print( my_dictionary.values() ) 12 | 13 | my_dictionary[ 'new_item3' ] = 55 14 | 15 | print( my_dictionary.keys() ) 16 | print( my_dictionary.values() ) 17 | 18 | my_dictionary[ 'new_item4' ] = [ 1 , 2 , 3 ] 19 | 20 | print( my_dictionary.keys() ) 21 | print( my_dictionary.values() ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/text_first_dictionary1.py: -------------------------------------------------------------------------------- 1 | # let's make a simple dictionary. 2 | 3 | my_dictionary = { "apple" : 34 , "orange" : 12 , "kiwi" : 12 } 4 | 5 | # now let's print out pieces of our dictionary 6 | print( my_dictionary[ "apple" ] ) 7 | print( my_dictionary[ "orange" ] ) 8 | print( my_dictionary[ "kiwi" ] ) 9 | -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/text_first_dictionary2.py: -------------------------------------------------------------------------------- 1 | my_dictionary = { "apple" : 34 , "orange" : 12 , "kiwi" : 12 } 2 | 3 | print( my_dictionary.keys() ) 4 | print( my_dictionary.values() ) 5 | -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/text_first_dictionary3.py: -------------------------------------------------------------------------------- 1 | my_dictionary = { 2 | "apple" : "these are delicious" , 3 | "orange" : 12 , 4 | "kiwi" : 55.5 , 5 | "lots_of_things" : [ 6 | "paper" , 7 | "pens" , 8 | 44 , 9 | 10.4 10 | ] 11 | } 12 | 13 | print( my_dictionary.keys() ) 14 | print( my_dictionary.values() ) 15 | 16 | print( my_dictionary[ 'lots_of_things' ][ 0 ] ) 17 | print( my_dictionary[ 'lots_of_things' ][ 1 ] ) 18 | print( my_dictionary[ 'lots_of_things' ][ 2 ] ) 19 | print( my_dictionary[ 'lots_of_things' ][ 3 ] ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/text_first_dictionary4.py: -------------------------------------------------------------------------------- 1 | my_dictionary_of_dictionaries = { 2 | "apple" : { 3 | "quantity" : 10 , 4 | "origin" : "Vermont" , 5 | "organic" : True 6 | } , 7 | "orange" : { 8 | "quantity" : 20 , 9 | "origin" : "California" , 10 | "organic" : False 11 | } , 12 | "kiwi" : { 13 | "quantity" : 26 , 14 | "origin" : "Mexico" , 15 | "organic" : False 16 | } , 17 | "grapes" : { 18 | "quantity" : 50 , 19 | "origin" : "Peru" , 20 | "organic" : True 21 | } 22 | } 23 | 24 | print( my_dictionary_of_dictionaries.keys() ) 25 | print( my_dictionary_of_dictionaries.values() ) 26 | 27 | print( '\n' ) 28 | print( "Let's just look at apple" ) 29 | print( "quanitity -" , my_dictionary_of_dictionaries[ 'apple' ][ 'quantity' ] ) 30 | print( "origin -" , my_dictionary_of_dictionaries[ 'apple' ][ 'origin' ] ) 31 | print( "organic -" , my_dictionary_of_dictionaries[ 'apple' ][ 'organic' ] ) 32 | 33 | print( '\n' ) 34 | print( "Okay, what about Orange" ) 35 | print( "quanitity -" , my_dictionary_of_dictionaries[ 'orange' ][ 'quantity' ] ) 36 | print( "origin -" , my_dictionary_of_dictionaries[ 'orange' ][ 'origin' ] ) 37 | print( "organic -" , my_dictionary_of_dictionaries[ 'orange' ][ 'organic' ] ) 38 | 39 | print( '\n' ) 40 | print( "Then there's kiwi too..." ) 41 | print( "quanitity -" , my_dictionary_of_dictionaries[ 'kiwi' ][ 'quantity' ] ) 42 | print( "origin -" , my_dictionary_of_dictionaries[ 'kiwi' ][ 'origin' ] ) 43 | print( "organic -" , my_dictionary_of_dictionaries[ 'kiwi' ][ 'organic' ] ) 44 | 45 | print( '\n' ) 46 | print( "and we can't forget about grapes." ) 47 | print( "quanitity -" , my_dictionary_of_dictionaries[ 'grapes' ][ 'quantity' ] ) 48 | print( "origin -" , my_dictionary_of_dictionaries[ 'grapes' ][ 'origin' ] ) 49 | print( "organic -" , my_dictionary_of_dictionaries[ 'grapes' ][ 'organic' ] ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/text_read_me_examples.py: -------------------------------------------------------------------------------- 1 | grocery_list_org = [ 'eggs' , 'milk' , 'bread' , 'butter' , 'coffee' ] 2 | 3 | print( "This is our original List" ) 4 | print( grocery_list_org ) 5 | 6 | grocery_list_list = [ 7 | ['eggs' , '1 dozen' ] , 8 | ['milk' , '1 pint' ] , 9 | ['bread' , '2 loaves' ] , 10 | ['butter' , '1 lb' ] , 11 | ['coffee', '2 lbs' ] 12 | ] 13 | 14 | print( '\n' ) 15 | print( "This is our list of lists" ) 16 | print( grocery_list_list ) 17 | 18 | grocery_list_dictionary = { 19 | 'eggs' : '1 dozen' , 20 | 'milk' : '1 pint' , 21 | 'bread' : '2 loaves' , 22 | 'butter' : '1 lb' , 23 | 'coffee': '2 lbs' 24 | } 25 | 26 | print( '\n' ) 27 | print( "This is our dictionary" ) 28 | print( grocery_list_dictionary ) 29 | 30 | print( '\n' ) 31 | print( "Let's retrieve all of our values:" ) 32 | print( grocery_list_dictionary[ 'eggs' ] ) 33 | print( grocery_list_dictionary[ 'milk' ] ) 34 | print( grocery_list_dictionary[ 'bread' ] ) 35 | print( grocery_list_dictionary[ 'butter' ] ) 36 | print( grocery_list_dictionary[ 'coffee' ] ) 37 | 38 | print( '\n' ) 39 | print( "Let's imagine we want both the key and the values:" ) 40 | print( 'eggs' , grocery_list_dictionary[ 'eggs' ] ) 41 | print( 'milk' , grocery_list_dictionary[ 'milk' ] ) 42 | print( 'bread' , grocery_list_dictionary[ 'bread' ] ) 43 | print( 'butter' , grocery_list_dictionary[ 'butter' ] ) 44 | print( 'coffee' , grocery_list_dictionary[ 'coffee' ] ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/text_top_dictionary.py: -------------------------------------------------------------------------------- 1 | top_dictionary = { 2 | "text" : 'monkey' , 3 | "fontsizex" : 15 , 4 | "alignx" : 1 , 5 | "aligny" : 1 , 6 | "fontcolorr" : 1.0 , 7 | "fontcolorg" : 0.0 , 8 | "fontcolorb" : 0.0 , 9 | "fontalpha" : 1.0 , 10 | "bgcolorr" : 0.0 , 11 | "bgcolorg" : 0.0 , 12 | "bgcolorb" : 0.0 , 13 | "bgalpha" : 1.0 14 | } 15 | 16 | target_text = op( 'text1' ) 17 | 18 | target_text.par.text = top_dictionary[ 'text' ] 19 | target_text.par.fontsizex = top_dictionary[ 'fontsizex' ] 20 | target_text.par.alignx = top_dictionary[ 'alignx' ] 21 | target_text.par.aligny = top_dictionary[ 'aligny' ] 22 | target_text.par.fontcolorr = top_dictionary[ 'fontcolorr' ] 23 | target_text.par.fontcolorg = top_dictionary[ 'fontcolorg' ] 24 | target_text.par.fontcolorb = top_dictionary[ 'fontcolorb' ] 25 | target_text.par.fontalpha = top_dictionary[ 'fontalpha' ] 26 | target_text.par.bgcolorr = top_dictionary[ 'bgcolorr' ] 27 | target_text.par.bgcolorg = top_dictionary[ 'bgcolorg' ] 28 | target_text.par.bgcolorb = top_dictionary[ 'bgcolorb' ] 29 | target_text.par.bgalpha = top_dictionary[ 'bgalpha' ] -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/text_top_dictionary_presets.py: -------------------------------------------------------------------------------- 1 | top_dictionary = { 2 | "preset1" : { 3 | "text" : 'monkey' , 4 | "fontsizex" : 15 , 5 | "alignx" : 1 , 6 | "aligny" : 1 , 7 | "fontcolorr" : 1.0 , 8 | "fontcolorg" : 0.0 , 9 | "fontcolorb" : 0.0 , 10 | "fontalpha" : 1.0 , 11 | "bgcolorr" : 0.0 , 12 | "bgcolorg" : 0.0 , 13 | "bgcolorb" : 0.0 , 14 | "bgalpha" : 1.0 15 | } , 16 | "preset2" : { 17 | "text" : 'pig' , 18 | "fontsizex" : 80 , 19 | "alignx" : 1 , 20 | "aligny" : 0 , 21 | "fontcolorr" : 0.0 , 22 | "fontcolorg" : 0.0 , 23 | "fontcolorb" : 1.0 , 24 | "fontalpha" : 1.0 , 25 | "bgcolorr" : 1.0 , 26 | "bgcolorg" : 1.0 , 27 | "bgcolorb" : 1.0 , 28 | "bgalpha" : 1.0 29 | } 30 | } 31 | 32 | target_text = op( 'text1' ) 33 | dictionary_preset = 'preset2' 34 | 35 | target_text.par.text = top_dictionary[ dictionary_preset ][ 'text' ] 36 | target_text.par.fontsizex = top_dictionary[ dictionary_preset ][ 'fontsizex' ] 37 | target_text.par.alignx = top_dictionary[ dictionary_preset ][ 'alignx' ] 38 | target_text.par.aligny = top_dictionary[ dictionary_preset ][ 'aligny' ] 39 | target_text.par.fontcolorr = top_dictionary[ dictionary_preset ][ 'fontcolorr' ] 40 | target_text.par.fontcolorg = top_dictionary[ dictionary_preset ][ 'fontcolorg' ] 41 | target_text.par.fontcolorb = top_dictionary[ dictionary_preset ][ 'fontcolorb' ] 42 | target_text.par.fontalpha = top_dictionary[ dictionary_preset ][ 'fontalpha' ] 43 | target_text.par.bgcolorr = top_dictionary[ dictionary_preset ][ 'bgcolorr' ] 44 | target_text.par.bgcolorg = top_dictionary[ dictionary_preset ][ 'bgcolorg' ] 45 | target_text.par.bgcolorb = top_dictionary[ dictionary_preset ][ 'bgcolorb' ] 46 | target_text.par.bgalpha = top_dictionary[ dictionary_preset ][ 'bgalpha' ] -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/text_top_dictionary_presets1.py: -------------------------------------------------------------------------------- 1 | top_dictionary = { 2 | "preset1" : { 3 | "text" : 'monkey' , 4 | "fontsizex" : 15 , 5 | "alignx" : 1 , 6 | "aligny" : 1 , 7 | "fontcolorr" : 1.0 , 8 | "fontcolorg" : 0.0 , 9 | "fontcolorb" : 0.0 , 10 | "fontalpha" : 1.0 , 11 | "bgcolorr" : 0.0 , 12 | "bgcolorg" : 0.0 , 13 | "bgcolorb" : 0.0 , 14 | "bgalpha" : 1.0 15 | } , 16 | "preset2" : { 17 | "text" : 'pig' , 18 | "fontsizex" : 80 , 19 | "alignx" : 1 , 20 | "aligny" : 0 , 21 | "fontcolorr" : 0.0 , 22 | "fontcolorg" : 0.0 , 23 | "fontcolorb" : 1.0 , 24 | "fontalpha" : 1.0 , 25 | "bgcolorr" : 1.0 , 26 | "bgcolorg" : 1.0 , 27 | "bgcolorb" : 1.0 , 28 | "bgalpha" : 1.0 29 | } 30 | } 31 | 32 | target_text = op( 'text1' ) 33 | dictionary_preset = op( 'table_preset_selection' )[ 0 , 0 ].val 34 | 35 | target_text.par.text = top_dictionary[ dictionary_preset ][ 'text' ] 36 | target_text.par.fontsizex = top_dictionary[ dictionary_preset ][ 'fontsizex' ] 37 | target_text.par.alignx = top_dictionary[ dictionary_preset ][ 'alignx' ] 38 | target_text.par.aligny = top_dictionary[ dictionary_preset ][ 'aligny' ] 39 | target_text.par.fontcolorr = top_dictionary[ dictionary_preset ][ 'fontcolorr' ] 40 | target_text.par.fontcolorg = top_dictionary[ dictionary_preset ][ 'fontcolorg' ] 41 | target_text.par.fontcolorb = top_dictionary[ dictionary_preset ][ 'fontcolorb' ] 42 | target_text.par.fontalpha = top_dictionary[ dictionary_preset ][ 'fontalpha' ] 43 | target_text.par.bgcolorr = top_dictionary[ dictionary_preset ][ 'bgcolorr' ] 44 | target_text.par.bgcolorg = top_dictionary[ dictionary_preset ][ 'bgcolorg' ] 45 | target_text.par.bgcolorb = top_dictionary[ dictionary_preset ][ 'bgcolorb' ] 46 | target_text.par.bgalpha = top_dictionary[ dictionary_preset ][ 'bgalpha' ] -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_dictionary/text_top_dictionary_presets2.py: -------------------------------------------------------------------------------- 1 | text_top_dictionary = { 2 | 'preset1' : { 3 | 'text_top' : { 4 | "text" : "CAT!" , 5 | "fontsizex" : 20 , 6 | "alignx" : 1 , 7 | "aligny" : 1 , 8 | "fontcolorr" : 0.0 , 9 | "fontcolorg" : 1.0 , 10 | "fontcolorb" : 0.0 , 11 | "fontalpha" : 1.0 , 12 | "bgcolorr" : 0.0 , 13 | "bgcolorg" : 0.0 , 14 | "bgcolorb" : 0.0 , 15 | "bgalpha" : 0.5 16 | } , 17 | 'level_top' : { 18 | 'invert' : 1.0 19 | } 20 | } , 21 | 'preset2' : { 22 | 'text_top' : { 23 | "text" : "Monkey" , 24 | "fontsizex" : 15 , 25 | "alignx" : 1 , 26 | "aligny" : 1 , 27 | "fontcolorr" : 1.0 , 28 | "fontcolorg" : 0.0 , 29 | "fontcolorb" : 0.0 , 30 | "fontalpha" : 1.0 , 31 | "bgcolorr" : 0.0 , 32 | "bgcolorg" : 0.0 , 33 | "bgcolorb" : 0.0 , 34 | "bgalpha" : 1.0 35 | } , 36 | 'level_top' : { 37 | 'invert' : 0.5 38 | } 39 | } , 40 | 'preset3' : { 41 | 'text_top' : { 42 | "text" : "DOG" , 43 | "fontsizex" : 80 , 44 | "alignx" : 1 , 45 | "aligny" : 1 , 46 | "fontcolorr" : 0.0 , 47 | "fontcolorg" : 0.0 , 48 | "fontcolorb" : 1.0 , 49 | "fontalpha" : 1.0 , 50 | "bgcolorr" : 1.0 , 51 | "bgcolorg" : 1.0 , 52 | "bgcolorb" : 1.0 , 53 | "bgalpha" : 1.0 54 | } , 55 | 'level_top' : { 56 | 'invert' : 0.0 57 | } 58 | } 59 | } 60 | 61 | top_preset = op( 'table_preset_selection1' )[ 0 , 0 ].val 62 | target_text = op( 'text2' ) 63 | target_level = op( 'level1' ) 64 | 65 | target_text.par.text = text_top_dictionary[ top_preset ][ 'text_top' ][ 'text' ] 66 | target_text.par.fontsizex = text_top_dictionary[ top_preset ][ 'text_top' ][ 'fontsizex' ] 67 | target_text.par.alignx = text_top_dictionary[ top_preset ][ 'text_top' ][ 'alignx' ] 68 | target_text.par.aligny = text_top_dictionary[ top_preset ][ 'text_top' ][ 'aligny' ] 69 | target_text.par.fontcolorr = text_top_dictionary[ top_preset ][ 'text_top' ][ 'fontcolorr' ] 70 | target_text.par.fontcolorg = text_top_dictionary[ top_preset ][ 'text_top' ][ 'fontcolorg' ] 71 | target_text.par.fontcolorb = text_top_dictionary[ top_preset ][ 'text_top' ][ 'fontcolorb' ] 72 | target_text.par.fontalpha = text_top_dictionary[ top_preset ][ 'text_top' ][ 'fontalpha' ] 73 | target_text.par.bgcolorr = text_top_dictionary[ top_preset ][ 'text_top' ][ 'bgcolorr' ] 74 | target_text.par.bgcolorg = text_top_dictionary[ top_preset ][ 'text_top' ][ 'bgcolorg' ] 75 | target_text.par.bgcolorb = text_top_dictionary[ top_preset ][ 'text_top' ][ 'bgcolorb' ] 76 | target_text.par.bgalpha = text_top_dictionary[ top_preset ][ 'text_top' ][ 'bgalpha' ] 77 | target_level.par.invert = text_top_dictionary[ top_preset ][ 'level_top' ][ 'invert' ] -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/container_radio_buttons.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_data_structures/data_structures_list/container_radio_buttons.tox -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/data_structures_list.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_data_structures/data_structures_list/data_structures_list.tox -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## data_structures_list ## 8 | 9 | Lists are the bees knees, they're the cat's pajamas, they're almost better than sliced bread. There are a few important things for us to think about before we dive into the Python of lists. Python lists are just like the lists you might make on a piece of paper. They're a sequential ordering of items. A grocery list might be: 10 | * eggs 11 | * milk 12 | * bread 13 | * butter 14 | * coffee 15 | 16 | We often make lists, and while the order of our grocery list might be arbitrary, there are plenty of lists that are not. Frequently a todo list has a specific order: 17 | 1. Have preliminary discussion with collaborators 18 | 2. Check schedule for availability 19 | 3. Block off time for new project 20 | 4. Coordinate schedules 21 | 5. Build a preliminary budget 22 | 6. Draft contracts 23 | 7. Confirm costs 24 | 8. Book space 25 | 9. Purchase equipment 26 | 27 | While this is a silly example, the important consideration is that here you wouldn't purchase equipment before you started a preliminary discussion with your collaborators. Of course that seems obvious - but remember that you have a sense of linearity, a sense of time, a sense of order, and a idiomatic frame that you subconsciously constructed based on the content of the list items. Alright, [semiotics](https://en.wikipedia.org/wiki/Sign_(semiotics)) aside, the more important idea here is that lists have order. Now while that may seem obvious, we'll see later that dictionaries don't necessarily work in the way - and in fact this is an important distinction we need to make early on. 28 | 29 | Let's go back to our grocery list. What might that look like in Python? 30 | 31 | ```python 32 | grocery_list = [ 'eggs' , 'milk' , 'bread' , 'butter', 'coffee' ] 33 | ``` 34 | 35 | You'll notice that our items are enclosed in matching foot or inch marks: '' or "". We can remember back to our first lesson on printing that this helps us see that these are strings. That's wonderful. What if we want to print the whole list? Well we can do this: 36 | 37 | ```python 38 | print( grocery_list ) 39 | ``` 40 | 41 | That prints our whole list. That's pretty swanky, but what if we just want a single item from our list? How can we just print that? Well, we'll remember that 0 is still a number for us here in Python. That means the indexing of our list items looks like: 0 1 2 3 4 42 | 43 | We can print a single item in our list by indicating the index of the item we want: 44 | 45 | ``` python 46 | print( grocery_list[ 0 ] ) 47 | ``` 48 | 49 | Let's look at that a little more closely and print out all of the items we have in our list: 50 | 51 | ``` python 52 | print( grocery_list[ 0 ] ) 53 | print( grocery_list[ 1 ] ) 54 | print( grocery_list[ 2 ] ) 55 | print( grocery_list[ 3 ] ) 56 | print( grocery_list[ 4 ] ) 57 | ``` 58 | 59 | Let's go one step further and really make that as explicit as possible - just to make sure we understand. 60 | 61 | ``` python 62 | print( "The item in the 0 position of our list is %r" % grocery_list[ 0 ] ) 63 | print( "The item in the 1 position of our list is %r" % grocery_list[ 1 ] ) 64 | print( "The item in the 2 position of our list is %r" % grocery_list[ 2 ] ) 65 | print( "The item in the 3 position of our list is %r" % grocery_list[ 3 ] ) 66 | print( "The item in the 4 position of our list is %r" % grocery_list[ 4 ] ) 67 | ``` 68 | 69 | We can make lists out of just about anything. Let's make a list out of all of the data types we've talked about so far: 70 | 71 | ```python 72 | my_int_list = [ 1 , 2 , 3 , 4 ] 73 | my_float_list = [ 1.235 , 1.5679 , 9.454 , 4.23485 ] 74 | my_string_list = [ 'apple' , 'kiwi' , 'orange' , 'pineapple' ] 75 | my_bool_list = [ True , True , False , True, False ] 76 | my_mixed_list = [ 1.234 , 5 , 'apple' , True , 3.45 ] 77 | ``` 78 | 79 | One question we might have is how long is our list? Well, there happens to be an easy way for us to figure that out with len() - as in length. 80 | 81 | ```python 82 | len( my_int_list ) 83 | ``` 84 | 85 | Practice printing the length of all of your lists. 86 | 87 | We can also build lists from scratch. First we need to create an empty list. 88 | 89 | ```python 90 | my_list = [] 91 | 92 | print( 'As we go, we will print our list at each' ) 93 | print( 'step along the way' ) 94 | print( 'My List' , my_list ) 95 | ``` 96 | 97 | Next we can add items to our list with .append( theValueOrStringToBeAddedHere ). 98 | 99 | ```python 100 | my_list.append( 1 ) 101 | 102 | print( '\n' ) 103 | 104 | print( 'So we just added a single number out our list' ) 105 | print( 'what does that look like now?' ) 106 | print( 'My List' , my_list ) 107 | ``` 108 | 109 | We can even add multiple items at once with .extend( aListofItemsHere ): 110 | 111 | ```python 112 | my_list.extend( [ 45 , 2 , 100 , 6 ] ) 113 | 114 | print( '\n' ) 115 | 116 | print( 'Can we add multiple items at once?' ) 117 | print( 'My List' , my_list ) 118 | 119 | print( 'We sure can, we just need to use .extend' ) 120 | print( 'instead of .append' ) 121 | ``` 122 | 123 | That's great... but what does this mean for me in TouchDesigner? Well, in Touch many things are returned as lists. Samples in CHOPs are often a list, as are points in a SOP. Once we have a fundamental understanding of lists as a data structure we can start to really have a lot of fun. 124 | 125 | Let's look at CHOPs first. 126 | First, make sure you add a noise CHOP to your network called noise1. 127 | 128 | ```python 129 | # define some variables 130 | noise1 = op( 'noise1' ) 131 | 132 | # understanding the channel operator make 133 | # a big difference in the way we use TouchDesigner 134 | 135 | # lets start by just printing our variable 136 | 137 | print( 'If we just print our noise1 variable we see this' ) 138 | print( noise1 ) 139 | 140 | print( 'If we print chan1 in noise1 one we see this' ) 141 | print( noise1[ 'chan1' ] ) 142 | 143 | print( 'we can also access this by using .chan( channelIndexHere )' ) 144 | print( noise1.chan( 0 ) ) 145 | 146 | print( 'Finally, we can see the whole list of values if we use' ) 147 | print( '.vals as we... that looks like') 148 | print( 'noise1.( channelIndexHere ).vals' ) 149 | print( noise1.chan( 0 ).vals ) 150 | ``` 151 | 152 | That's pretty fun... but let's take that a step further. 153 | 154 | ```python 155 | # okay, but why do we care? 156 | 157 | # define some variables 158 | noise1 = op( 'noise1' ) 159 | 160 | # we can use what we've learned working with the .chans().vals 161 | # to help us understand a little bit more about our CHOP 162 | # for example, if our channel is a list of values, we can 163 | # access those values just like we might in a list 164 | 165 | print( noise1[ 'chan1' ][ 0 ] ) 166 | print( noise1[ 'chan1' ][ 1 ] ) 167 | print( noise1[ 'chan1' ][ 2 ] ) 168 | 169 | # we can even do the same things we might do in python here 170 | print( len( noise1[ 'chan1' ] ) ) 171 | 172 | # though if we look at the wiki, we'll find that there's already 173 | # a method to do just this called .numSamples 174 | # and a method called numChans - which tells us how many channels 175 | # If we think of our CHOP as a list of lists... then we can both 176 | # see how many lists, and the length of the lists. 177 | 178 | print( noise1.numSamples ) 179 | print( noise1.numChans ) 180 | ``` 181 | 182 | Next let's add a rectangle SOP to our network. 183 | 184 | ```python 185 | # define some variables 186 | rectangle1 = op( 'rectangle1' ) 187 | 188 | # That's great... but what about geometry? 189 | # Let's take a closer look at SOPs 190 | 191 | print( 'Like with a sop we can print the path to rectangle1 operator' ) 192 | print( rectangle1 ) 193 | 194 | print( 'We can also look at the member .points' ) 195 | print( rectangle1.points ) 196 | 197 | print( 'Seeing that it is an object by itself, means we can look closer' ) 198 | print( 'What happens if we just ask for the first item in this object?' ) 199 | print( rectangle1.points[ 0 ] ) 200 | 201 | print( 'What if we ask to make the whole object a list, and the print it out?' ) 202 | print( list( rectangle1.points ) ) 203 | ``` 204 | 205 | While CHOPs and SOPs seem like obvious operators that might have lists, they're certainly not the only ones. The method .findChildren returns a list of operators when dealing with COMPs. Let's take a closer look at that while we're at it. I started by making a container and adding three buttons inside. Make sure that you look at the example file to see what I've done to get started. 206 | 207 | ```python 208 | # define some variables 209 | radio_buttons = op( 'container_radio_buttons' ) 210 | 211 | # Let's take a look at findChildren 212 | # we can see all of the ops inside of our container with: 213 | print( radio_buttons.findChildren() ) 214 | 215 | # What if we only wanted to see the buttons?? 216 | print( radio_buttons.findChildren( depth = 1 ) ) 217 | 218 | # That's fine as long as there aren't any other operators 219 | # inside of our conatiner. If we wanted to make sure we only 220 | # got a list of buttons, we could be even more specific with 221 | 222 | print( radio_buttons.findChildren( type = buttonCOMP , depth = 1 ) ) 223 | 224 | # Okay... so? 225 | # Well, what we get back is a list, so what if we did this? 226 | 227 | print( radio_buttons.findChildren( type = buttonCOMP , depth = 1 )[ 0 ] ) 228 | 229 | # Maybe we don't want to see the whole path, we just want to see it's name 230 | print( radio_buttons.findChildren( type = buttonCOMP , depth = 1 )[ 0 ].name ) 231 | 232 | # Or maybe just its digits 233 | print( radio_buttons.findChildren( type = buttonCOMP , depth = 1 )[ 0 ].digits ) 234 | 235 | # We could even click on one of our buttons this way 236 | radio_buttons.findChildren( type = buttonCOMP , depth = 1 )[ 0 ].click() 237 | ``` 238 | 239 | Lists are powerful and also flexible data structures. And this is only the start of what we can do with them. Practice making some lists, accessing their contents, and printing out pieces of them. 240 | 241 | [Learn more about data structures in Python](https://docs.python.org/3.3/tutorial/datastructures.html) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/text_accessing_a_list.py: -------------------------------------------------------------------------------- 1 | # let's first create our first list 2 | my_int_list = [ 1 , 2 , 3 , 4 ] 3 | 4 | # pulling out just a single item from a list 5 | print( 'This uses the list itself' ) 6 | print( my_int_list[ 0 ] ) 7 | 8 | # we can also assign this to a variable 9 | my_int1 = my_int_list[ 0 ] 10 | print( 'This prints a new variable called my_int1' ) 11 | print( my_int1 ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/text_appending_lists.py: -------------------------------------------------------------------------------- 1 | # let's start by making an empty list 2 | 3 | my_list = [] 4 | 5 | print( 'As we go, we will print our list at each' ) 6 | print( 'step along the way' ) 7 | print( 'My List' , my_list ) 8 | 9 | my_list.append( 1 ) 10 | 11 | print( '\n' ) 12 | 13 | print( 'So we just added a single number out our list' ) 14 | print( 'what does that look like now?' ) 15 | print( 'My List' , my_list ) 16 | 17 | my_list.append( 23 ) 18 | 19 | print( '\n' ) 20 | 21 | print( 'So we just added another number out our list' ) 22 | print( 'what does that look like now?' ) 23 | print( 'My List' , my_list ) 24 | 25 | my_list.extend( [ 45 , 2 , 100 , 6 ] ) 26 | 27 | print( '\n' ) 28 | 29 | print( 'Can we add multiple items at once?' ) 30 | print( 'My List' , my_list ) 31 | 32 | print( 'We sure can, we just need to use .extend' ) 33 | print( 'instead of .append' ) 34 | 35 | print( '\n' ) 36 | 37 | print( 'One last handy trick to know is that ' ) 38 | print( 'we can reverse our list' ) 39 | 40 | my_list.reverse() 41 | 42 | print( my_list ) 43 | -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/text_channels_and_lists1.py: -------------------------------------------------------------------------------- 1 | # define some variables 2 | noise1 = op( 'noise1' ) 3 | 4 | # understanding the channel operator make 5 | # a big difference in the way we use TouchDesigner 6 | 7 | # lets start by just printing our variable 8 | 9 | print( 'If we just print our noise1 variable we see this' ) 10 | print( noise1 ) 11 | 12 | print( 'If we print chan1 in noise1 one we see this' ) 13 | print( noise1[ 'chan1' ] ) 14 | 15 | print( 'we can also access this by using .chan( channelIndexHere )' ) 16 | print( noise1.chan( 0 ) ) 17 | 18 | print( 'Finally, we can see the whole list of values if we use' ) 19 | print( '.vals as we... that looks like') 20 | print( 'noise1.( channelIndexHere ).vals' ) 21 | print( noise1.chan( 0 ).vals ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/text_channels_and_lists2.py: -------------------------------------------------------------------------------- 1 | # okay, but why do we care? 2 | 3 | # define some variables 4 | noise1 = op( 'noise1' ) 5 | 6 | # we can use what we've learned working with the .chans().vals 7 | # to help us understand a little bit more about our CHOP 8 | # for example, if our channel is a list of values, we can 9 | # access those values just like we might in a list 10 | 11 | print( noise1[ 'chan1' ][ 0 ] ) 12 | print( noise1[ 'chan1' ][ 1 ] ) 13 | print( noise1[ 'chan1' ][ 2 ] ) 14 | 15 | # we can even do the same things we might do in python here 16 | print( len( noise1[ 'chan1' ] ) ) 17 | 18 | # though if we look at the wiki, we'll find that there's already 19 | # a method to do just this called .numSamples 20 | # and a method called numChans - which tells us how many channels 21 | # If we think of our CHOP as a list of lists... then we can both 22 | # see how many lists, and the length of the lists. 23 | 24 | print( noise1.numSamples ) 25 | print( noise1.numChans ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/text_containers_and_lists1.py: -------------------------------------------------------------------------------- 1 | # define some variables 2 | radio_buttons = op( 'container_radio_buttons' ) 3 | 4 | # Let's take a look at findChildren 5 | # we can see all of the ops inside of our container with: 6 | print( radio_buttons.findChildren() ) 7 | 8 | # What if we only wanted to see the buttons?? 9 | print( radio_buttons.findChildren( depth = 1 ) ) 10 | 11 | # That's fine as long as there aren't any other operators 12 | # inside of our conatiner. If we wanted to make sure we only 13 | # got a list of buttons, we could be even more specific with 14 | 15 | print( radio_buttons.findChildren( type = buttonCOMP , depth = 1 ) ) 16 | 17 | # Okay... so? 18 | # Well, what we get back is a list, so what if we did this? 19 | 20 | print( radio_buttons.findChildren( type = buttonCOMP , depth = 1 )[ 0 ] ) 21 | 22 | # Maybe we don't want to see the whole path, we just want to see it's name 23 | print( radio_buttons.findChildren( type = buttonCOMP , depth = 1 )[ 0 ].name ) 24 | 25 | # Or maybe just its digits 26 | print( radio_buttons.findChildren( type = buttonCOMP , depth = 1 )[ 0 ].digits ) 27 | 28 | # We could even click on one of our buttons this way 29 | radio_buttons.findChildren( type = buttonCOMP , depth = 1 )[ 0 ].click() -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/text_grocery_list.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | grocery_list = [ 'eggs' , 'milk' , 'bread' , 'butter', 'coffee' ] 3 | 4 | # printing list items 5 | print( grocery_list ) 6 | 7 | print( grocery_list[ 0 ] ) 8 | print( grocery_list[ 1 ] ) 9 | print( grocery_list[ 2 ] ) 10 | print( grocery_list[ 3 ] ) 11 | print( grocery_list[ 4 ] ) 12 | 13 | print( "The item in the 0 position of our list is %r" % grocery_list[ 0 ] ) 14 | print( "The item in the 1 position of our list is %r" % grocery_list[ 1 ] ) 15 | print( "The item in the 2 position of our list is %r" % grocery_list[ 2 ] ) 16 | print( "The item in the 3 position of our list is %r" % grocery_list[ 3 ] ) 17 | print( "The item in the 4 position of our list is %r" % grocery_list[ 4 ] ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/text_items_in_our_first_list.py: -------------------------------------------------------------------------------- 1 | # let's first create our first list 2 | my_int_list = [ 1 , 2 , 3 , 4 ] 3 | my_float_list = [ 1.235 , 1.5679 , 9.454 , 4.23485 ] 4 | my_string_list = [ 'apple' , 'kiwi' , 'orange' , 'pineapple' ] 5 | my_bool_list = [ True , True , False , True, False ] 6 | my_mixed_list = [ 1.234 , 5 , 'apple' , True , 3.45 ] 7 | line_break = '\n' 8 | dotted_break = '\n' + '- ' * 20 + '\n' 9 | 10 | 11 | print( 'We can start with our int_list' ) 12 | print( my_int_list[ 0 ] ) 13 | print( my_int_list[ 1 ] ) 14 | print( my_int_list[ 2 ] ) 15 | print( my_int_list[ 3 ] ) 16 | 17 | print( 'The item in the 0 position of my_int_list is %d' % my_int_list[ 0 ] ) 18 | print( 'The item in the 1 position of my_int_list is %d' % my_int_list[ 1 ] ) 19 | print( 'The item in the 2 position of my_int_list is %d' % my_int_list[ 2 ] ) 20 | print( 'The item in the 3 position of my_int_list is %d' % my_int_list[ 3 ] ) 21 | print( 'The sum of the numbers in list this is %d' % sum( my_int_list ) ) 22 | 23 | print( line_break ) 24 | 25 | print( 'We can also see only items up to a position in a list' , my_int_list[ :2 ] ) 26 | print( 'We can also see only items up to a position in a list' , my_int_list[ :3 ] ) 27 | print( 'We can also see only items up to a position in a list' , my_int_list[ 2: ] ) 28 | print( 'We can also see only items up to a position in a list' , my_int_list[ 3: ] ) 29 | 30 | print( dotted_break ) 31 | 32 | print( 'Next, how about our float list' ) 33 | print( 'The item in the 0 position of my_float_list is %r' % my_float_list[ 0 ] ) 34 | print( 'The item in the 1 position of my_float_list is %r' % my_float_list[ 1 ] ) 35 | print( 'The item in the 2 position of my_float_list is %r' % my_float_list[ 2 ] ) 36 | print( 'The item in the 3 position of my_float_list is %r' % my_float_list[ 3 ] ) 37 | print( 'The sum of the numbers in list this is %r' % sum( my_float_list ) ) 38 | 39 | print( line_break ) 40 | 41 | print( 'We can also see only items up to a position in a list' , my_float_list[ :2 ] ) 42 | print( 'We can also see only items up to a position in a list' , my_float_list[ :3 ] ) 43 | print( 'We can also see only items up to a position in a list' , my_float_list[ 2: ] ) 44 | print( 'We can also see only items up to a position in a list' , my_float_list[ 3: ] ) 45 | 46 | print( dotted_break ) 47 | 48 | print( 'What about that string list?' ) 49 | print( 'The item in the 0 position of my_string_list is %r' % my_string_list[ 0 ] ) 50 | print( 'The item in the 1 position of my_string_list is %r' % my_string_list[ 1 ] ) 51 | print( 'The item in the 2 position of my_string_list is %r' % my_string_list[ 2 ] ) 52 | print( 'The item in the 3 position of my_string_list is %r' % my_string_list[ 3 ] ) 53 | print( 'We can not not sum a list of strings' ) 54 | 55 | print( line_break ) 56 | 57 | print( 'We can also see only items up to a position in a list' , my_string_list[ :2 ] ) 58 | print( 'We can also see only items up to a position in a list' , my_string_list[ :3 ] ) 59 | print( 'We can also see only items up to a position in a list' , my_string_list[ 2: ] ) 60 | print( 'We can also see only items up to a position in a list' , my_string_list[ 3: ] ) 61 | 62 | print( dotted_break ) 63 | 64 | print( 'Okay, so this will work with our bool_list too, right?' ) 65 | print( 'The item in the 0 position of my_bool_list is %r' % my_bool_list[ 0 ] ) 66 | print( 'The item in the 1 position of my_bool_list is %r' % my_bool_list[ 1 ] ) 67 | print( 'The item in the 2 position of my_bool_list is %r' % my_bool_list[ 2 ] ) 68 | print( 'The item in the 3 position of my_bool_list is %r' % my_bool_list[ 3 ] ) 69 | 70 | print( line_break ) 71 | 72 | print( 'We can also see only items up to a position in a list' , my_bool_list[ :2 ] ) 73 | print( 'We can also see only items up to a position in a list' , my_bool_list[ :3 ] ) 74 | print( 'We can also see only items up to a position in a list' , my_bool_list[ 2: ] ) 75 | print( 'We can also see only items up to a position in a list' , my_bool_list[ 3: ] ) 76 | 77 | print( dotted_break ) 78 | 79 | print( 'Last but not least, what about our mixed list?' ) 80 | print( 'The item in the 0 position of my_mixed_list is %r' % my_mixed_list[ 0 ] ) 81 | print( 'The item in the 1 position of my_mixed_list is %r' % my_mixed_list[ 1 ] ) 82 | print( 'The item in the 2 position of my_mixec_list is %r' % my_mixed_list[ 2 ] ) 83 | print( 'The item in the 3 position of my_mixed_list is %r' % my_mixed_list[ 3 ] ) 84 | 85 | print( line_break ) 86 | 87 | print( 'We can also see only items up to a position in a list' , my_mixed_list[ :2 ] ) 88 | print( 'We can also see only items up to a position in a list' , my_mixed_list[ :3 ] ) 89 | print( 'We can also see only items up to a position in a list' , my_mixed_list[ 2: ] ) 90 | print( 'We can also see only items up to a position in a list' , my_mixed_list[ 3: ] ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/text_lists_in_lsts.py: -------------------------------------------------------------------------------- 1 | # let's make a set of lists in a list 2 | inventory = [ 3 | [ 'fruit' , 4 | 'apple' , 5 | 'kiwi' , 6 | 'orange' ] , 7 | [ 'baked-goods' , 8 | 'bread' , 9 | 'bagles' , 10 | 'pies' ] , 11 | [ 'soft-drinks' , 12 | 'sprite' , 13 | 'coke' , 14 | 'dr. Pepper' ] 15 | ] 16 | 17 | print( 'We can see the whole list of lists' ) 18 | print( 'Though, truth be told, that is overwhelming' ) 19 | print( inventory ) 20 | 21 | print( '\n' ) 22 | 23 | print( 'Instead, maybe we just want to look at one list' ) 24 | print( inventory[ 0 ] ) 25 | 26 | print( '\n' ) 27 | 28 | print( 'We planned ahead, and the first item in our list' ) 29 | print( 'is our header. So we could do something like this:' ) 30 | 31 | print( '\n' ) 32 | 33 | print( 'In our %s department we have:' % inventory[ 0 ][ 0 ] ) 34 | print( inventory[ 0 ][ 1: ] ) 35 | 36 | print( '\n' ) 37 | 38 | print( 'We can find just a single item in a list with:' ) 39 | print( inventory[ 0 ][ 0 ] ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/text_our_first_list.py: -------------------------------------------------------------------------------- 1 | # let's first create our first list 2 | my_int_list = [ 1 , 2 , 3 , 4 ] 3 | my_float_list = [ 1.235 , 1.5679 , 9.454 , 4.2345 ] 4 | my_string_list = [ 'apple' , 'kiwi' , 'orange' , 'pineapple' ] 5 | my_bool_list = [ True , True , False , True, False ] 6 | my_mixed_list = [ 1.234 , 5 , 'apple' , True , 3.45 ] 7 | 8 | print( my_int_list ) 9 | print( my_float_list ) 10 | print( my_string_list ) 11 | print( my_bool_list ) 12 | print( my_mixed_list ) 13 | 14 | print( '\n' ) 15 | 16 | print( 'The length of my_int_list is %d' % len( my_int_list ) ) 17 | print( 'The length of my_float_list is %d' % len( my_float_list ) ) 18 | print( 'The length of my_string_list is %d' % len( my_string_list ) ) 19 | print( 'The length of my_bool_list is %d' % len( my_bool_list ) ) 20 | print( 'The length of my_mixed_list is %d' % len( my_mixed_list ) ) -------------------------------------------------------------------------------- /088/example_data_structures/data_structures_list/text_sops_and_lists1.py: -------------------------------------------------------------------------------- 1 | # define some variables 2 | rectangle1 = op( 'rectangle1' ) 3 | 4 | # That's great... but what about geometry? 5 | # Let's take a closer look at SOPs 6 | 7 | print( 'Like with a sop we can print the path to rectangle1 operator' ) 8 | print( rectangle1 ) 9 | 10 | print( 'We can also look at the member .points' ) 11 | print( rectangle1.points ) 12 | 13 | print( 'Seeing that it is an object by itself, means we can look closer' ) 14 | print( 'What happens if we just ask for the first item in this object?' ) 15 | print( rectangle1.points[ 0 ] ) 16 | 17 | print( 'What if we ask to make the whole object a list, and the print it out?' ) 18 | print( list( rectangle1.points ) ) -------------------------------------------------------------------------------- /088/example_data_structures/example_data_structures.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_data_structures/example_data_structures.tox -------------------------------------------------------------------------------- /088/example_data_structures/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_data_structures ## 8 | 9 | Here we'll look at examples of lists and dictionaries. 10 | You can find more information in each respective example base. -------------------------------------------------------------------------------- /088/example_dictionary_loops/example_dictionary_loops.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_dictionary_loops/example_dictionary_loops.tox -------------------------------------------------------------------------------- /088/example_dictionary_loops/simple_mod_example.py: -------------------------------------------------------------------------------- 1 | my_int = 5 2 | my_float = 0.5 3 | my_string = 'Hello there' -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_cue_list.py: -------------------------------------------------------------------------------- 1 | cue_list = { 2 | "cue1" : { 3 | "file" : '/Map/Banana.tif', 4 | "mono" : 0 , 5 | "invert" : 1, 6 | "brightness" : 0.71, 7 | "blacklevel" : 0.51, 8 | "contrast" : 1.93 9 | } , 10 | "cue2" : { 11 | "file" : '/Map/Smiley.tif', 12 | "mono" : 1 , 13 | "invert" : 0, 14 | "brightness" : 1, 15 | "blacklevel" : 0.51, 16 | "contrast" : 1.5 17 | } , 18 | "cue3" : { 19 | "file" : '/Map/OilDrums.jpg', 20 | "mono" : 0, 21 | "invert" : 1, 22 | "brightness" : 0.71, 23 | "blacklevel" : 0.51, 24 | "contrast" : 1.93 25 | } 26 | } -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_cue_list_play.py: -------------------------------------------------------------------------------- 1 | movie_a = op( 'moviefilein_a' ) 2 | mono_a = op( 'mono_a' ) 3 | level_a = op( 'level_a' ) 4 | cue_list = mod( op( 'text_cue_list' ) ).cue_list 5 | 6 | cue = 'cue2' 7 | 8 | movie_a.par.file = app.samplesFolder + cue_list[ cue ][ 'file' ] 9 | mono_a.par.mono = cue_list[ cue ][ 'mono' ] 10 | level_a.par.invert = cue_list[ cue ][ 'invert' ] 11 | level_a.par.brightness1 = cue_list[ cue ][ 'brightness' ] 12 | level_a.par.blacklevel = cue_list[ cue ][ 'blacklevel' ] 13 | level_a.par.contrast = cue_list[ cue ][ 'contrast' ] -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_data.py: -------------------------------------------------------------------------------- 1 | uri = { 2 | "10.0.0.2" : { 3 | "name" : "mission_control" , 4 | "role" : "controller" , 5 | "displays" : [ 6 | "control01" 7 | ] 8 | }, 9 | "10.0.0.3" : { 10 | "name" : "eyes" , 11 | "role" : "node" , 12 | "displays" : [ 13 | "projector01" , 14 | "projector02" 15 | ] 16 | } 17 | } 18 | 19 | displays = { 20 | "control01" : { 21 | "width" : 1920 , 22 | "height" : 1080 , 23 | "orientation" : 0 24 | }, 25 | "projector01" : { 26 | "width" : 1920 , 27 | "height" : 1080 , 28 | "orientation" : 0 29 | }, 30 | "projector02" : { 31 | "width" : 1920 , 32 | "height" : 1080 , 33 | "orientation" : 1 34 | } 35 | } -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_defaults.py: -------------------------------------------------------------------------------- 1 | level_defaults = { 2 | "invert" : 0, 3 | "blacklevel" : 0, 4 | "brightness1" : 1, 5 | "gamma1" : 1, 6 | "contrast" : 1, 7 | "gamma2" : 1, 8 | "opacity" : 1, 9 | "brightness2" : 1 10 | } 11 | 12 | mono_defaults = { 13 | "mono" : 0, 14 | "rgb" : 0, 15 | "alpha" : 4 16 | } -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_execute_loop.py: -------------------------------------------------------------------------------- 1 | mono_defaults = mod( op( 'text_defaults' ) ).mono_defaults 2 | level_defaults = mod( op( 'text_defaults' ) ).level_defaults 3 | 4 | for keys, values in mono_defaults.items(): 5 | 6 | #reset mono defaults for A 7 | exec( "op( 'mono_a' ).par." + keys + " = " + str( values ) ) 8 | 9 | # debut print so you can see what this is acutally doing 10 | # print( "op( 'mono_a.par' )." + keys + " = " + str( values ) ) 11 | 12 | for keys, values in level_defaults.items(): 13 | 14 | #reset mono defaults for A 15 | exec( "op( 'level_a' ).par." + keys + " = " + str( values ) ) -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_execute_loop_pars.py: -------------------------------------------------------------------------------- 1 | mono_defaults = mod( op( 'text_defaults' ) ).mono_defaults 2 | level_defaults = mod( op( 'text_defaults' ) ).level_defaults 3 | 4 | for keys, values in mono_defaults.items(): 5 | 6 | #reset mono defaults for A 7 | op( 'mono_a' ).pars( keys )[ 0 ].val = values 8 | 9 | # debut print so you can see what this is acutally doing 10 | # print( "op( 'mono_a.par' )." + keys + " = " + str( values ) ) 11 | 12 | for keys, values in level_defaults.items(): 13 | 14 | # #reset level defaults for A 15 | op( 'level_a' ).pars( keys )[ 0 ].val = values -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_replicator.py: -------------------------------------------------------------------------------- 1 | # import any necessary modules 2 | 3 | import socket 4 | 5 | # define some variables 6 | 7 | pos_x = 3300 8 | pos_y = 0 9 | replicator_table = op( 'table_replicator_info' ) 10 | system_config = mod( 'text_data' ).uri 11 | displays = mod( 'text_data' ).displays 12 | local_ip = socket.gethostbyname( socket.gethostname() ) 13 | default_ip = op( 'text_default_ip' ).text 14 | local_config = {} 15 | container_COMPs = parent().findChildren( type = COMP , depth = 1 ) 16 | 17 | # clear contents of replicator_table 18 | replicator_table.clear() 19 | 20 | # delete existing continer COMPs 21 | for items in container_COMPs: 22 | if op( items ).name != 'container_presets': 23 | op( items ).destroy() 24 | else: 25 | pass 26 | 27 | # Test for local_ip in system config 28 | in_system_config = local_ip in system_config 29 | 30 | # if our IP matches with a URI in the system config, then we 31 | # fill our dictionary with keys based on the system config file 32 | if in_system_config == True: 33 | local_config[ 'name' ] = system_config[ local_ip ][ 'name' ] 34 | local_config[ 'role' ] = system_config[ local_ip ][ 'role' ] 35 | local_config[ 'displays' ] = system_config[ local_ip ][ 'displays' ] 36 | local_config[ 'uri' ] = local_ip 37 | 38 | # if our IP doesn't match our system config file, then we fill our 39 | # dictionary with keys based on the default IP address, which is 40 | # entered in the text dat called 'text_default_ip' 41 | else: 42 | # assign ip address 43 | local_ip = default_ip 44 | local_config[ 'name' ] = system_config[ local_ip ][ 'name' ] 45 | local_config[ 'role' ] = system_config[ local_ip ][ 'role' ] 46 | local_config[ 'displays' ] = system_config[ local_ip ][ 'displays' ] 47 | local_config[ 'uri' ] = local_ip 48 | 49 | # fill table with display information 50 | for items in local_config[ 'displays' ]: 51 | replicator_table.appendRow( [ 52 | items , 53 | displays[ items ][ 'width' ] , 54 | displays[ items ][ 'height' ] , 55 | displays[ items ][ 'orientation' ] 56 | ] ) 57 | 58 | # check and correct height / width based on orientation flag 59 | for row in range( replicator_table.numRows ): 60 | # create a temporary variable to store the width and height of the display 61 | width_height = [ replicator_table[ row , 1 ].val , replicator_table[ row , 2 ].val ] 62 | 63 | # test for orientation flag 64 | # if true, swap height and width 65 | if replicator_table[ row , 3 ] == 1: 66 | replicator_table[ row , 1 ] = width_height[ 1 ] 67 | replicator_table[ row , 2 ] = width_height[ 0 ] 68 | 69 | # if false, we pass and do nothing 70 | else: 71 | pass 72 | 73 | # create container COMPs based on our replicator table 74 | for row in range( replicator_table.numRows ): 75 | # create our new container COMP, give it a name from our replciator table 76 | new_op = parent().create( containerCOMP , replicator_table[ row , 0 ] ) 77 | 78 | # set the x position of the new container 79 | new_op.nodeX = pos_x 80 | 81 | # set the y position of the new container 82 | new_op.nodeY = pos_y 83 | 84 | # set the width of the new node based on the replicator table 85 | new_op.par.w = replicator_table[ row , 1 ] 86 | 87 | # set the height of the new node based on the replicator table 88 | new_op.par.h = replicator_table[ row , 2 ] 89 | 90 | # turn on the viewer flag 91 | new_op.viewer = True 92 | 93 | # increment the y position for the next loop 94 | pos_y -= 200 -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_simple_dictionary_loop0.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | # using modules means that we can use our dot notation to 3 | # access an object inside of our text DAT 4 | inventory = mod( 'text_test_dictionary' ).inventory 5 | 6 | # list of dictionary keys and their position 7 | dictionary_keys = list( enumerate( inventory ) ) 8 | 9 | # let's get a feeling for how this works before we 10 | # write a sentance 11 | for item in dictionary_keys: 12 | print( item[ 0 ] , item[ 1 ] ) 13 | 14 | # loop through list and print the key and its 15 | # list position 16 | sentence = "The key {key} is in the {position} position of the list" 17 | 18 | for item in dictionary_keys: 19 | print( sentence.format( key = item[ 1 ] , position = item[ 0 ] ) ) -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_simple_dictionary_loop1.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | # using modules means that we can use our dot notation to 3 | # access an object inside of our text DAT 4 | inventory = mod( 'text_test_dictionary' ).inventory 5 | 6 | for dictionary_key , dictionary_value in inventory.items(): 7 | print( "this dictionary key is: " , dictionary_key ) -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_simple_dictionary_loop2.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | # using modules means that we can use our dot notation to 3 | # access an object inside of our text DAT 4 | inventory = mod( 'text_test_dictionary' ).inventory 5 | 6 | for dictionary_key , dictionary_value in inventory.items(): 7 | print( "this dictionary key is: " , dictionary_key ) 8 | print( "the dictionary value for this key is: " , dictionary_value ) -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_simple_dictionary_loop3.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | # using modules means that we can use our dot notation to 3 | # access an object inside of our text DAT 4 | inventory = mod( 'text_test_dictionary' ).inventory 5 | 6 | # loop through the dictionary and print out contents 7 | for dictionary_key , dictionary_value in inventory.items(): 8 | # print out the first key depth in the dictionary 9 | print( "- " * 10 ) 10 | print( "this dictionary key is: " , dictionary_key ) 11 | print( "in this dictionary we have:" ) 12 | 13 | # loop through second dictionary layer 14 | for inventory_key , inventory_quantity in dictionary_value.items(): 15 | # print out the key and value pairs from the second level 16 | print( '\t' , inventory_key , inventory_quantity ) -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_simple_dictionary_loop4.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | # using modules means that we can use our dot notation to 3 | # access an object inside of our text DAT 4 | inventory = mod( 'text_test_dictionary' ).inventory 5 | table = op( 'table_inventory' ) 6 | table_length = [] 7 | place_holder = 1 8 | 9 | # first things first, let's clear out the contents of the table 10 | table.clear() 11 | 12 | # loop through the dictionary and add headers 13 | for dictionary_key , dictionary_value in inventory.items(): 14 | table.appendCol( [ dictionary_key ] ) 15 | # add a blank column for next set of data points 16 | table.appendCol() 17 | 18 | # add to the list so we can determine the max length of the table 19 | table_length.append( len( dictionary_value ) ) 20 | 21 | # set the table length based on the maximum number of dictionary entries 22 | table.setSize( max( table_length ) + 1 , table.numCols ) 23 | 24 | # loop through dictionary again to fill in table 25 | for dictionary_key , dictionary_value in inventory.items(): 26 | 27 | # generate an enumerated list to loop through 28 | value_list = list( enumerate( dictionary_value ) ) 29 | 30 | # use enumerated list to fill in table 31 | for item in value_list: 32 | table[ item[ 0 ] + 1 , dictionary_key ] = item[ 1 ] 33 | table[ item[ 0 ] + 1 , place_holder ] = inventory[ dictionary_key ][ item[ 1 ] ] 34 | 35 | # increment placeholder to ensure that our data goes in the right place 36 | place_holder += 2 -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_simple_json.json: -------------------------------------------------------------------------------- 1 | { 2 | "inventory" : { 3 | "fruit" : { 4 | "apples" : 22, 5 | "kiwis" : 28, 6 | "grapes" : 11, 7 | "oranges" : 65, 8 | "grapefruit" : 75 9 | } , 10 | "vegetables" : { 11 | "carrots" : 10, 12 | "potatoes" : 8, 13 | "onions" : 50, 14 | "kale" : 10 15 | } , 16 | "beer" : { 17 | "ipa" : 32, 18 | "stout" : 46, 19 | "ale" : 56 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_test_dictionary.py: -------------------------------------------------------------------------------- 1 | inventory = { 2 | "fruit" : { 3 | "apples" : 22 , 4 | "kiwis" : 28 , 5 | "grapes" : 11 , 6 | "oranges" : 65 , 7 | "grapefruit" : 75 8 | } , 9 | "vegetables" : { 10 | "carrots" : 10 , 11 | "potatoes" : 8 , 12 | "onions" : 50 , 13 | "kale" : 10 14 | } , 15 | "beer" : { 16 | "ipa" : 32 , 17 | "stout" : 46 , 18 | "ale" : 56 , 19 | } 20 | } -------------------------------------------------------------------------------- /088/example_dictionary_loops/text_using_json.py: -------------------------------------------------------------------------------- 1 | # JSON, as a format is very similar to python dictionaries, 2 | # so similar in fact there's a very simple way to work with it. 3 | 4 | # If you're already working with dictionaries this will feel like an 5 | # easy transition. There are, however, a few things you need to keep in mind. 6 | 7 | # We need to make sure that our JSON is correctly formatted. If you compare 8 | # the JSON to the right, with our first example you can see that we've 9 | # enclosed our entire dictionary in curly brackets {}. You can also see that 10 | # we've replaced our first variable with a key-value pair. 11 | 12 | # inventory = 13 | 14 | # became 15 | 16 | # "inventory" : 17 | 18 | # You can also check your JSON with some simple on line JSON validators. You 19 | # might not need to do this with simple arrays, but with longer and more 20 | # complicated sets it can be best to ensure that you have valid JSON before 21 | # you start working in touch 22 | 23 | # http://jsonlint.com/ 24 | 25 | 26 | # First we need to import the json module. 27 | 28 | import json 29 | 30 | # next we're going to use just the text from our text dat 31 | imported_dict = op( 'text_simple_json' ).text 32 | 33 | # next we'll use json.loads to import that as a python dictionary 34 | dict_from_json = json.loads( imported_dict ) 35 | 36 | # first let's just print the dictionary to make sure things worked out 37 | print( "Here is our whole json object" ) 38 | print( imported_dict ) 39 | 40 | print( '_ ' * 10 ) 41 | 42 | # next we could print just the top tier keys 43 | print( "Here are our top tier keys" ) 44 | for item in dict_from_json.keys(): 45 | print( item ) 46 | 47 | print( '\n' ) 48 | print( '_ ' * 10 ) 49 | # next let's print the keys in our 'inventory' dictionary 50 | print( "Here are the keys in inventory" ) 51 | for item in dict_from_json[ 'inventory' ].keys(): 52 | print( item ) 53 | 54 | print( '\n' ) 55 | print( '_ ' * 10 ) 56 | # since we're on a roll, let's first print our keys, and 57 | # then print their values 58 | print( "Here are the keys and values in inventory" ) 59 | for key, value in dict_from_json[ 'inventory' ].items(): 60 | print( key, 'contains', value ) 61 | 62 | print( '\n' ) 63 | print( '_ ' * 10 ) 64 | # That's still not very pretty, so let's see if we can make 65 | # something that's a little nicer 66 | print( "Pretty printing our keys and values" ) 67 | for key, value in dict_from_json[ 'inventory' ].items(): 68 | print( key, 'contains' ) 69 | 70 | for item, quantity in value.items(): 71 | print( quantity, item ) 72 | 73 | print( '\n' ) 74 | -------------------------------------------------------------------------------- /088/example_executes/example_executes.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_executes/example_executes.tox -------------------------------------------------------------------------------- /088/example_executes/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_executes ## 8 | 9 | Now that we know a little more about how functions work we're ready to dig in a little deeper when it comes to working with execute DATs. There are a number of different executes, and they all work on a similar principle - a target operator is watched for changes, and when a change occurs a function is executed. Let's first look at a very simple example with a CHOP execute. 10 | 11 | ### The CHOP Execute DAT ### 12 | 13 | Lets start by adding a Constant CHOP to our network, and then adding a CHOP Execute DAT. Next we drag the Constant onto the CHOP Execute DAT to specify that it's our target CHOP. Okay... now what? Well right away we should see there are a number of functions in our CHOP Execute (some of this is redundant from the previous tutorial, so bear with me because it's frightfully important). 14 | 15 | As a quick note, you may see the functions in these DATs referred to as Methods. The semantics around function and method are nuanced in Python, and for the sake of clarity I'll mostly refer to them as functions. The important thing to consider is that methods are functions - that happen to belong to a class. If this feels strange, we can remember that all squares are rectangles - but being square carries a particular classification. All of that to say that for right now, we can think of these as functions. 16 | 17 | ```Python 18 | # me - this DAT 19 | # 20 | # channel - the Channel object which has changed 21 | # sampleIndex - the index of the changed sample 22 | # val - the numeric value of the changed sample 23 | # prev - the previous sample value 24 | # 25 | # Make sure the corresponding toggle is enabled in the CHOP Execute DAT. 26 | 27 | def offToOn(channel, sampleIndex, val, prev): 28 | return 29 | 30 | def whileOn(channel, sampleIndex, val, prev): 31 | return 32 | 33 | def onToOff(channel, sampleIndex, val, prev): 34 | return 35 | 36 | def whileOff(channel, sampleIndex, val, prev): 37 | return 38 | 39 | def valueChange(channel, sampleIndex, val, prev): 40 | return 41 | ``` 42 | 43 | Right away we should notice that we have a set of five functions in our DAT: 44 | * offToOn() 45 | * whileOn() 46 | * onToOff() 47 | * whileOff() 48 | * valueChange() 49 | 50 | Okay. Let's start by first unpacking what these functions do. As their names suggest, these are the conditions under which the function is called - that is to say when it will be executed. So what does that mean? Let's take the example of offToOn(). In this case, the function offToOn is called when the CHOP's value moves from the off position to the on position. In TouchDesigner, we can think of on as being any value that's greater than 0. That means that anytime our value crosses the boundary of 0 to a positive number, this particular function will be called. To understand what that means we might make a simple change to the function like this: 51 | 52 | ```Python 53 | def offToOn(channel, sampleIndex, val, prev): 54 | 55 | print( 'Hello there mischief maker' ) 56 | 57 | return 58 | ``` 59 | 60 | Next we need to make sure our text port is open, and then we can move our Constant value from 0 to any positive number. We'll see that every time we cross the 0 boundary, we get line printed to our text port. DAT Execute magic. 61 | 62 | If offToOn() fires in the crossing from 0 into positive numbers, it's not a stretch to realize that onToOff() fires when crossing into negative numbers. valueChange() will run whenever there's a change in the value; whileOn() fires only when the value is above 0; whileOff() fires only when the value is below zero. If this still isn't feeling useful yet, that's okay - hang in there because it's gonna get good. 63 | 64 | That's all well and good, but to better take advantage of our CHOP execute we should take a closer look at what's happening. We'll notice that each function has four arguments: 65 | * channel 66 | * sampleIndex 67 | * val 68 | * prev 69 | 70 | So what are these things? Arguments are passed into the function and can be used in any way we like. In the header portion of our CHOP execute we have the following to give us a little guidance: 71 | 72 | ```Python 73 | # me - this DAT 74 | # 75 | # channel - the Channel object which has changed 76 | # sampleIndex - the index of the changed sample 77 | # val - the numeric value of the changed sample 78 | # prev - the previous sample value 79 | # 80 | # Make sure the corresponding toggle is enabled in the CHOP Execute DAT. 81 | ``` 82 | 83 | Here we can see that this arguments are objects, and values in their own right. Okay, so what can we do with these things? The answer is, of course, nuanced. Before we get too lost in the abstract possibilities of what we might do with these things, we should first return to our roots and do a little bit of printing to figure out what on earth we're actually getting out of these arguments. 84 | 85 | For starters we're going to write out a little chunk of text with placeholders to values: 86 | 87 | ```Python 88 | execute_text = ''' 89 | The channel is {} 90 | The sampleIndex is {} 91 | The val is {} 92 | The prev is {} 93 | ''' 94 | ``` 95 | 96 | We'll remember from earlier that we can use execute_text.format() to pass in some values to see printed out. If this is feeling only vaguely familiar you can [revisit that tutorial here](http://matthewragan.com/2015/10/14/python-in-touchdesigner-printing-touchdesigner/). 97 | 98 | Okay. Next, let's add a one-line print statement to our offToOn() function: 99 | 100 | ```Python 101 | print( execute_text.format( channel, sampleIndex, val, prev ) ) 102 | ``` 103 | 104 | That means we should have a DAT that looks something like this: 105 | 106 | ```Python 107 | # Our first CHOP execute! 108 | 109 | # Here we'll only look at the first function - offToOn. 110 | # We already learned a little bit about functions, so we know 111 | # that we have access to a set of arguments. Let's just print 112 | # those out so we can see what they are. 113 | 114 | # me - this DAT 115 | # 116 | # channel - the Channel object which has changed 117 | # sampleIndex - the index of the changed sample 118 | # val - the numeric value of the changed sample 119 | # prev - the previous sample value 120 | # 121 | # Make sure the corresponding toggle is enabled in the CHOP Execute DAT. 122 | 123 | execute_text = '''\ 124 | The channel is {} 125 | The sampleIndex is {} 126 | The val is {} 127 | The prev is {} 128 | ''' 129 | 130 | def offToOn(channel, sampleIndex, val, prev): 131 | 132 | print( execute_text.format( channel , sampleIndex , val , prev ) ) 133 | 134 | return 135 | 136 | def whileOn(channel, sampleIndex, val, prev): 137 | return 138 | 139 | def onToOff(channel, sampleIndex, val, prev): 140 | return 141 | 142 | def whileOff(channel, sampleIndex, val, prev): 143 | return 144 | 145 | def valueChange(channel, sampleIndex, val, prev): 146 | return 147 | ``` 148 | 149 | Now let's change out constant CHOP a bit. Every time we cross from 0 to a positive number we should see a print statement that looks something like: 150 | 151 | ```Python 152 | python >>> 153 | The channel is 0.05 154 | The sampleIndex is 0 155 | The val is 0.05000000074505806 156 | The prev is 0.0 157 | ``` 158 | 159 | At first glance, channel and val are looking pretty similar. So what gives? Well, if we look back at our DAT we should see one particular line that tells us something very interesting: 160 | 161 | ```Python 162 | # channel - the Channel object which has changed 163 | ``` 164 | 165 | Channel isn't just a value, it's an entire Python object. To better understand what that means we might want to look a little closer at the [Channel Class page on the wiki](http://www.derivative.ca/wiki088/index.php?title=Channel_Class). With a little closer inspection we can see that we can get all sorts of interesting information here: valid, index, name, owner, vals. These are all members of the Channel class, which means we can use dot notation to retrieve these. So, for example, we might change channel to channel.name in our simple example: 166 | 167 | ```Python 168 | print( execute_text.format( channel.name , sampleIndex , val , prev ) ) 169 | ``` 170 | 171 | This means that now when we print we instead get this: 172 | 173 | ```Python 174 | python >>> 175 | The channel is chan1 176 | The sampleIndex is 0 177 | The val is 0.25 178 | The prev is 0.0 179 | ``` 180 | 181 | That might not yet seem important, but it means that we can use conditional statements in our functions to determine what happens. If we had a CHOP with 15 Channels, for example, but only wanted to run a particular portion of our function with 'chan4' changed, we now have a way to separate out this particular event. 182 | 183 | Okay. That's all well and fine, but what can we do with our new found python incantations? Let's pause for a moment and think for about how we build networks in TouchDesigner, and how cooking works. Cooking, the nomenclature for calculation, is pull based instead of push based in Touch. This means that the last element in a chain pulls changes from upstream. This is often much more efficient, but it also means that we need to think carefully about how we program. Let's consider a filter CHOP. The filter CHOP needs to always cook. Why?! Well, the filter CHOP's job is to interpolate between two values, and in order to do that it needs to constantly be calculating changes. The result of this CHOP cooking constant means that any downstream node will also cook, always - even when it isn't changing. In some circumstances this can cause challenges in facing efficiency in a network - especially if your filter CHOP is high up along the chain. Let's look at a simple example. 184 | 185 | Let's add a few ingredients to our network, a buttom COMP, a filter CHOP, and a null CHOP. We can start by connecting these elements. Next let's use the resulting single to drive an opacity change in a level TOP. If we used an expression or an export we can see out our export / expression line is constantly moving (meaning that it's constantly cooking). We can also see that any operator downstream of our level CHOP (go crazy, and a few more TOPs to your network) is also cooking. That means by adding our single filter CHOP we've suddenly ended up with constantly cooking network. You may well throw your hands up in the air, and feel like them's the breaks. But, that's not the case. We can, instead, use a CHOP execute to drive a change in our Level TOP. 186 | 187 | How?! If we remember that we can target parameters in operators, this suddenly becomes very easy. If you're following along with our example network you'll see that I want to target a TOP called 'level2'. Let's drop in a CHOP execute DAT, drag our null CHOP onto our DAT, and get to updating our function. I'm going to make the following change to our function: 188 | 189 | ```Python 190 | 191 | # me - this DAT 192 | # 193 | # channel - the Channel object which has changed 194 | # sampleIndex - the index of the changed sample 195 | # val - the numeric value of the changed sample 196 | # prev - the previous sample value 197 | # 198 | # Make sure the corresponding toggle is enabled in the CHOP Execute DAT. 199 | 200 | def offToOn(channel, sampleIndex, val, prev): 201 | return 202 | 203 | def whileOn(channel, sampleIndex, val, prev): 204 | return 205 | 206 | def onToOff(channel, sampleIndex, val, prev): 207 | return 208 | 209 | def whileOff(channel, sampleIndex, val, prev): 210 | return 211 | 212 | def valueChange(channel, sampleIndex, val, prev): 213 | 214 | op( 'level2' ).par.opacity = val 215 | 216 | return 217 | ``` 218 | 219 | Did you catch it? It's in the valueChange function, and it's just a single line: 220 | 221 | ```Python 222 | op( 'level2' ).par.opacity = val 223 | ``` 224 | 225 | Easy. This means that we're only going to change that target parameter when the value changes from our target CHOP. Doing this we can see that we've now stopped forcing our TOP chain to cook constantly. 226 | 227 | We might also think about using CHOP Executes to do things that simplify our networks. Let's consider the following situation: we have a bank of movies that we want to switch between using a single button. Our button is connected to a count CHOP, and we'd like to cycle back through our four movies / images. One way to tackle this would be with a series of various CHOPs and DATs. In the example TOX I've used a count CHOP to cycle from 1 to 4. Next I converted this to a DAT, then inserted some text to match the format of our moviefile in TOPs, and finally converted to a text DAT before using this to drive a select TOP (that last step is a bit of a stretch, but I can see a situation where I might do something like that in a pinch). The resulting chain of operators all cook constantly. This happens, in part, because of our conversion from CHOP to DAT. Since our chopTo DAT is always cooking, this also means that everything down stream is cooking - always. How else might we solve this problem? Again, we might tackle this with a chop Execute DAT. In this case I'm going the use the integer value from the CHOP to help inform how I change the select TOP. Looking closer you can see the changes I've made to the value change function: 228 | 229 | ```Python 230 | def valueChange(channel, sampleIndex, val, prev): 231 | 232 | target_path_v2 = 'moviefilein_picker_{}' 233 | op( 'select1' ).par.top = target_path_v2.format( int( val ) ) 234 | 235 | return 236 | ``` 237 | 238 | Here I started by write a string with a placeholder for an inserted value: 239 | ```Python 240 | target_path_v2 = 'moviefilein_picker_{}' 241 | ``` 242 | 243 | You'll notice that all of the moviefilein TOPs have been renamed to "moviefilein_picker_1-4". 244 | 245 | The next sets the target select TOP with a path that's formatted with an integer of the value from the chop - channels are floats by default, and we need to specifically convert this to an integer to make sure that our string formatting is correct. 246 | 247 | ```Python 248 | op( 'select1' ).par.top = target_path_v2.format( int( val ) ) 249 | ``` 250 | 251 | ### The Panel Execute DAT ### 252 | 253 | The CHOP execute DAT is excellent, but it's not the only execute option. The Panel Execute is one of my favorites, and an operator that I continually return to for all sorts of situations. Like the CHOP execute it fires based on a change in a target operator, in this case any COMP with a panel component - buttons, sliders, containers, tables, lists, etc. In the Panel Execute DAT we see the following when we plunk it down in a network: 254 | 255 | ```Python 256 | # me - this DAT 257 | # panelValue - the PanelValue object that changed 258 | # 259 | # Make sure the corresponding toggle is enabled in the Panel Execute DAT. 260 | 261 | def offToOn(panelValue): 262 | return 263 | 264 | def whileOn(panelValue): 265 | return 266 | 267 | def onToOff(panelValue): 268 | return 269 | 270 | def whileOff(panelValue): 271 | return 272 | 273 | def valueChange(panelValue): 274 | return 275 | ``` 276 | 277 | This should all look pretty familiar - after all it's almost the same thing we saw in the CHOP execute. There is, of course, something we should pay close attention to right out the gate, our passed argument: 278 | 279 | ```Python 280 | # panelValue - the PanelValue object that changed 281 | ``` 282 | 283 | Again, we can see that we're actually passed an entire object, not just a single value. Looking at the wiki we can see that the [panelValue is a class in it's own right](http://www.derivative.ca/wiki088/index.php?title=PanelValue_Class). This means we have access to the name, owner, and val. 284 | 285 | For starters let's add a button COMP to our network, set it to momentary, and add a panel execute DAT. Next we can write a similar print statement to see what that means for us as we're using the panel execute DAT: 286 | 287 | ```Python 288 | # me - this DAT 289 | # panelValue - the PanelValue object that changed 290 | # 291 | # Make sure the corresponding toggle is enabled in the Panel Execute DAT. 292 | 293 | def offToOn(panelValue): 294 | return 295 | 296 | def whileOn(panelValue): 297 | return 298 | 299 | def onToOff(panelValue): 300 | return 301 | 302 | def whileOff(panelValue): 303 | return 304 | 305 | def valueChange(panelValue): 306 | 307 | exec_text = '''\ 308 | ------------------------------- 309 | The PanelValue object contains: 310 | 311 | panelValue.name = {} 312 | panelValue.owner = {} 313 | panelValue.val = {} 314 | ------------------------------- 315 | ''' 316 | 317 | print( exec_text.format( 318 | panelValue.name, 319 | panelValue.owner, 320 | panelValue.val 321 | ) ) 322 | 323 | return 324 | ``` 325 | 326 | Now when we click our button we should see something like the following printed: 327 | 328 | ```Python 329 | The PanelValue object contains: 330 | 331 | panelValue.name = v 332 | panelValue.owner = /python_in_touchdesigner/example_executes/button3 333 | panelValue.val = 0.3068181818181818 334 | ------------------------------- 335 | ``` 336 | 337 | Fancy! Again, this suddenly gives us access to a whole litany of possible directions to head when using this execute. 338 | 339 | Last but by no means least, let's look at how we might use several of these functions together. In this case I'm going to focus more on what's happening in panel execute than the entire mechanics of what we're up to. To understand what's happening in terms of the idea, let's consider that we want to do the following. I'd like to have a UI element where I can draw a shape that I then turn into a piece of geometry. There are any number of ways I might go about thinking through that problem. In this case I'm going to use a panel execute to set the position of a control element and extract the u and v values and append them to a table. I'm going to use those values to draw a box by converting CHOP data into a SOP. Finally, every time I start a new interaction on my UI, I'd like to clear the old data. 340 | 341 | We start by first adding a container COMP, and then a smaller container COMP inside with a different color. If you've looked at how the slider COMP works, it's the same idea at play here. We also need to add a table DAT where we can append some values. Next we'll use the panel execute to make some magic happen. Let's take a look at what we might write in order to get some interesting results. 342 | 343 | ```Python 344 | # me - this DAT 345 | # panelValue - the PanelValue object that changed 346 | # 347 | # Make sure the corresponding toggle is enabled in the Panel Execute DAT. 348 | 349 | # define some variables 350 | ui = op( 'container1' ) 351 | box = op( 'container1/container_control' ) 352 | table = op( 'table4' ) 353 | 354 | def offToOn(panelValue): 355 | 356 | # clear our table when we start drawing a new shape 357 | table.clear() 358 | 359 | return 360 | 361 | def whileOn(panelValue): 362 | 363 | # change the position of our contorl knob 364 | box.par.x = ui.panel.u * ui.par.w - ( box.par.w * 0.5 ) 365 | box.par.y = ui.panel.v * ui.par.h - ( box.par.h * 0.5 ) 366 | 367 | # add coordinates to a table to be turned into geometry 368 | table.appendRow( [ ui.panel.u , ui.panel.v ] ) 369 | 370 | return 371 | 372 | def onToOff(panelValue): 373 | return 374 | 375 | def whileOff(panelValue): 376 | return 377 | 378 | def valueChange(panelValue): 379 | return 380 | ``` 381 | 382 | The rest of this idea is then just about converting our table to a CHOP, and using a CHOP to SOP to further convert our data into geometry. At it's heart, this is a simple idea, but without a panel execute DAT it might be a bit of headache to get functioning the way we want. 383 | 384 | This, however, is just the tip of the iceberg - as it were. There are also DAT Executes (when tables changes), Parameter Executes (when parameters change), Executes (at start, and close of TouchDesigner), and OP executes (for changes in operator flags, names, etc.). Once you get started doing a little bit of Python scripting, the world is your oyster. Well, at least TouchDesigner is your Oyster. -------------------------------------------------------------------------------- /088/example_extensions/example_extensions.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_extensions/example_extensions.tox -------------------------------------------------------------------------------- /088/example_extensions/example_extensions_class.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | log_file = op( 'text_log_file' ) 4 | log_path = "example_extensions/log_files/log.txt" 5 | 6 | full_text = '''{now} 7 | 8 | Current Year | {year} 9 | Current Month | {month} 10 | Current Day | {day} 11 | Current Hour | {hour} 12 | Current Minute | {minute} 13 | Current Second | {second} 14 | Current Microsecond | {microsecond} 15 | ''' 16 | 17 | raw_date_time = "On {month}-{day}-{year} at {hour}:{minute}:{second}" 18 | 19 | verbose_log_message = '''============================ 20 | VERBOSE MESSAGE 21 | 22 | {date_time} 23 | ---------------------------- 24 | operator || {operator} 25 | At Network Location || {path} 26 | 27 | ---------------------------- 28 | Logged 29 | {message} 30 | ============================ 31 | ''' 32 | 33 | log_message = '''---------------------------- 34 | {now} 35 | ---------------------------- 36 | {operator} 37 | {path} 38 | {message} 39 | ''' 40 | 41 | class Ext_example(): 42 | 43 | def __init__( self ): 44 | '''The init function. 45 | 46 | We're not doing anything with our init function in this example 47 | so we'll leave this empty. 48 | 49 | Notes 50 | --------------- 51 | ''' 52 | return 53 | 54 | def Log_date( self ): 55 | year = datetime.datetime.now().year 56 | month = datetime.datetime.now().month 57 | day = datetime.datetime.now().day 58 | hour = datetime.datetime.now().hour 59 | minute = datetime.datetime.now().minute 60 | second = datetime.datetime.now().second 61 | 62 | updated_log_date = log_date.format( 63 | month = month, 64 | day = day, 65 | year = year, 66 | hours = hour, 67 | minutes = minute, 68 | seconds = second 69 | ) 70 | 71 | return updated_log_date 72 | 73 | def Log_date_time( self ): 74 | '''Create a formatted time stamp 75 | 76 | A look at how we might create a formatted time stamp to use with 77 | various logging applications. 78 | 79 | Arguments 80 | --------------- 81 | None 82 | 83 | Returns 84 | --------------- 85 | formatted_text( str ) - a string time stamp 86 | 87 | Notes 88 | --------------- 89 | ''' 90 | 91 | now = datetime.datetime.now() 92 | year = datetime.datetime.now().year 93 | month = datetime.datetime.now().month 94 | day = datetime.datetime.now().day 95 | hour = datetime.datetime.now().hour 96 | minute = datetime.datetime.now().minute 97 | second = datetime.datetime.now().second 98 | microsecond = datetime.datetime.now().microsecond 99 | 100 | date_time = raw_date_time.format( 101 | month = month, 102 | day = day, 103 | year = year, 104 | hour = hour, 105 | minute = minute, 106 | second = second 107 | ) 108 | 109 | return date_time 110 | 111 | def Log_message( self, operator, message, verbose=False, text_port_print=True, append_log=True ): 112 | '''Logging Method. 113 | 114 | A simple look at how you might start to think about building a logger for a TouchDesigner 115 | application. A logger is a great way to build out files with time stamped events. The 116 | more complex a project becomes, the more important it can become to have some means 117 | of logging the operations of your program. Here's a simple look at what that might 118 | look like. 119 | 120 | Arguments 121 | --------------- 122 | operator( touch object ) - the touch object whose path you'd like incldued in the log message 123 | message( str ) - a message to include in the log 124 | verbose( bool ) - a toggle for verbose or compact messages 125 | text_port_print( bool ) - a toggle to print to the text port, or not 126 | append_log( bool ) - a toggle to append to the log file , or not 127 | 128 | Example 129 | --------------- 130 | target_op = op( 'constant1' ) 131 | message = "This operator needs attention" 132 | 133 | parent().Log_message( target_op, message ) 134 | 135 | also 136 | 137 | parent().Log_message( target_op, message, verbose = True ) 138 | 139 | Returns 140 | --------------- 141 | None 142 | 143 | Notes 144 | --------------- 145 | You'll notice that some arguments receive default values. This is so you don't have 146 | to include them in the call. This means that by default the message will be compact, 147 | will print to the text port, and will append the log file. 148 | ''' 149 | 150 | path = operator.path 151 | op_name = operator.name 152 | 153 | # logic tests for verbose or compact 154 | if verbose: 155 | message = verbose_log_message.format( 156 | date_time = self.Log_date_time(), 157 | operator = op_name, 158 | path = path, 159 | message = message 160 | ) 161 | 162 | else: 163 | message = log_message.format( 164 | now = self.Log_date_time(), 165 | operator = op_name, 166 | path = path, 167 | message = message 168 | ) 169 | 170 | # logic tests for text_port_print 171 | if text_port_print: 172 | print( message ) 173 | 174 | else: 175 | pass 176 | 177 | # log tests for appending log 178 | if append_log: 179 | log_file.write( '\n' + message ) 180 | 181 | else: 182 | pass 183 | 184 | # save the log file to disk - external from the TouchDesigner project 185 | self.Save_log() 186 | 187 | return 188 | 189 | def Save_log( self ): 190 | '''Saves log to disk. 191 | 192 | This helper function saves the log file to disk. 193 | 194 | Notes 195 | --------------- 196 | None 197 | ''' 198 | 199 | op( log_file ).save( log_path ) 200 | 201 | return 202 | 203 | def Clear_log( self ): 204 | '''Clears Log File. 205 | 206 | This helper function clears the text dat used to hold the log file. 207 | 208 | Notes 209 | --------------- 210 | None 211 | ''' 212 | 213 | op( log_file ).clear() 214 | 215 | return -------------------------------------------------------------------------------- /088/example_extensions/log_files/log.txt: -------------------------------------------------------------------------------- 1 | 2 | ============================ 3 | VERBOSE MESSAGE 4 | 5 | On 12-16-2016 at 9:31:36 6 | ---------------------------- 7 | operator || constant1 8 | At Network Location || /python_in_touchdesigner/example_extensions/constant1 9 | 10 | ---------------------------- 11 | Logged 12 | This is operator needs attention 13 | ============================ 14 | -------------------------------------------------------------------------------- /088/example_extensions/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_extensions ## 8 | 9 | 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](https://matthewragan.com/2015/07/05/touchdesigner-understanding-extensions/). If you're brand new to this idea, check out that example first. 10 | 11 | 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. 12 | 13 | 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. 14 | 15 | In our [previous example looking at modules](https://matthewragan.com/2016/07/12/python-in-touchdesigner-modules-touchdesigner/) we wrote several functions that we left in a text DAT. We used the [mod class](http://derivative.ca/wiki088/index.php?title=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. 16 | 17 | 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. 18 | 19 | When we talked about [for loops](https://matthewragan.com/2016/07/06/python-in-touchdesigner-for-loop-touchdesigner/) 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: 20 | 21 | ```python 22 | 23 | class Dishes(): 24 | 25 | def __init__( self ): 26 | return 27 | 28 | def Full_cycle( self, list_of_dishes ): 29 | # what if we want to wash and then dry? 30 | 31 | self.Wash( list_of_dishes, water_temp ) 32 | self.Dry( list_of_dishes ) 33 | 34 | return 35 | 36 | def Wash( self, list_of_dishes, water_temp ): 37 | # what does washing really entail 38 | # all of that would go here 39 | 40 | number_of_dishes = len( list_of_dishes ) 41 | detergent_amt = self.Set_soap( number_of_dishes ) 42 | water_temp = self.Set_water_temp( water_temp ) 43 | 44 | return 45 | 46 | def Dry( self, list_of_dishes ): 47 | # what does washing really entail 48 | # all of that would go here 49 | 50 | number_of_dishes = len( list_of_dishes ) 51 | dry_time = self.Set_dry( number_of_dishes ) 52 | return 53 | 54 | def Set_soap( self, number_of_dishes ): 55 | return 56 | 57 | def Set_water_temp( water_temp ): 58 | if water_temp == 'hot': 59 | temp = 98 60 | 61 | elif water_temp == 'medium': 62 | temp = 80 63 | 64 | if water_temp == 'low': 65 | temp = 60 66 | 67 | return temp 68 | 69 | def Set_dry( self, number of dishes ): 70 | return 71 | ``` 72 | 73 | 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. 74 | 75 | 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](https://www.toptal.com/python/python-class-attributes-an-overly-thorough-guide) and [inheritance](http://www.jesshamrick.com/2011/05/18/an-introduction-to-classes-and-inheritance-in-python/). 76 | 77 | Okay, so how can we think about actually using this? 78 | 79 | Let's take a look at what this approach looks like in python: 80 | 81 | ```python 82 | import datetime 83 | 84 | log_file = op( 'text_log_file' ) 85 | log_path = "example_extensions/log_files/log.txt" 86 | 87 | full_text = '''{now} 88 | 89 | Current Year | {year} 90 | Current Month | {month} 91 | Current Day | {day} 92 | Current Hour | {hour} 93 | Current Minute | {minute} 94 | Current Second | {second} 95 | Current Microsecond | {microsecond} 96 | ''' 97 | 98 | raw_date_time = "On {month}-{day}-{year} at {hour}:{minute}:{second}" 99 | 100 | verbose_log_message = '''============================ 101 | VERBOSE MESSAGE 102 | 103 | {date_time} 104 | ---------------------------- 105 | operator || {operator} 106 | At Network Location || {path} 107 | 108 | ---------------------------- 109 | Logged 110 | {message} 111 | ============================ 112 | ''' 113 | 114 | log_message = '''---------------------------- 115 | {now} 116 | ---------------------------- 117 | {operator} 118 | {path} 119 | {message} 120 | ''' 121 | 122 | class Ext_example(): 123 | 124 | def __init__( self ): 125 | '''The init function. 126 | 127 | We're not doing anything with our init function in this example 128 | so we'll leave this empty. 129 | 130 | Notes 131 | --------------- 132 | ''' 133 | return 134 | 135 | def Log_date( self ): 136 | year = datetime.datetime.now().year 137 | month = datetime.datetime.now().month 138 | day = datetime.datetime.now().day 139 | hour = datetime.datetime.now().hour 140 | minute = datetime.datetime.now().minute 141 | second = datetime.datetime.now().second 142 | 143 | updated_log_date = log_date.format( 144 | month = month, 145 | day = day, 146 | year = year, 147 | hours = hour, 148 | minutes = minute, 149 | seconds = second 150 | ) 151 | 152 | return updated_log_date 153 | 154 | def Log_date_time( self ): 155 | '''Create a formatted time stamp 156 | 157 | A look at how we might create a formatted time stamp to use with 158 | various logging applications. 159 | 160 | Arguments 161 | --------------- 162 | None 163 | 164 | Returns 165 | --------------- 166 | formatted_text( str ) - a string time stamp 167 | 168 | Notes 169 | --------------- 170 | ''' 171 | 172 | now = datetime.datetime.now() 173 | year = datetime.datetime.now().year 174 | month = datetime.datetime.now().month 175 | day = datetime.datetime.now().day 176 | hour = datetime.datetime.now().hour 177 | minute = datetime.datetime.now().minute 178 | second = datetime.datetime.now().second 179 | microsecond = datetime.datetime.now().microsecond 180 | 181 | date_time = raw_date_time.format( 182 | month = month, 183 | day = day, 184 | year = year, 185 | hour = hour, 186 | minute = minute, 187 | second = second 188 | ) 189 | 190 | return date_time 191 | 192 | def Log_message( self, operator, message, verbose=False, text_port_print=True, append_log=True ): 193 | '''Logging Method. 194 | 195 | A simple look at how you might start to think about building a logger for a TouchDesigner 196 | application. A logger is a great way to build out files with time stamped events. The 197 | more complex a project becomes, the more important it can become to have some means 198 | of logging the operations of your program. Here's a simple look at what that might 199 | look like. 200 | 201 | Arguments 202 | --------------- 203 | operator( touch object ) - the touch object whose path you'd like incldued in the log message 204 | message( str ) - a message to include in the log 205 | verbose( bool ) - a toggle for verbose or compact messages 206 | text_port_print( bool ) - a toggle to print to the text port, or not 207 | append_log( bool ) - a toggle to append to the log file , or not 208 | 209 | Example 210 | --------------- 211 | target_op = op( 'constant1' ) 212 | message = "This operator needs attention" 213 | 214 | parent().Log_message( target_op, message ) 215 | 216 | also 217 | 218 | parent().Log_message( target_op, message, verbose = True ) 219 | 220 | Returns 221 | --------------- 222 | None 223 | 224 | Notes 225 | --------------- 226 | You'll notice that some arguments receive default values. This is so you don't have 227 | to include them in the call. This means that by default the message will be compact, 228 | will print to the text port, and will append the log file. 229 | ''' 230 | 231 | path = operator.path 232 | op_name = operator.name 233 | 234 | # logic tests for verbose or compact 235 | if verbose: 236 | message = verbose_log_message.format( 237 | date_time = self.Log_date_time(), 238 | operator = op_name, 239 | path = path, 240 | message = message 241 | ) 242 | 243 | else: 244 | message = log_message.format( 245 | now = self.Log_date_time(), 246 | operator = op_name, 247 | path = path, 248 | message = message 249 | ) 250 | 251 | # logic tests for text_port_print 252 | if text_port_print: 253 | print( message ) 254 | 255 | else: 256 | pass 257 | 258 | # log tests for appending log 259 | if append_log: 260 | log_file.write( '\n' + message ) 261 | 262 | else: 263 | pass 264 | 265 | # save the log file to disk - external from the TouchDesigner project 266 | self.Save_log() 267 | 268 | return 269 | 270 | def Save_log( self ): 271 | '''Saves log to disk. 272 | 273 | This helper function saves the log file to disk. 274 | 275 | Notes 276 | --------------- 277 | None 278 | ''' 279 | 280 | op( log_file ).save( log_path ) 281 | 282 | return 283 | 284 | def Clear_log( self ): 285 | '''Clears Log File. 286 | 287 | This helper function clears the text dat used to hold the log file. 288 | 289 | Notes 290 | --------------- 291 | None 292 | ''' 293 | 294 | op( log_file ).clear() 295 | 296 | return 297 | ``` 298 | 299 | 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: 300 | 301 | ```python 302 | # syntax for just a function 303 | def Clear_log(): 304 | return 305 | 306 | # syntax for just a method in a class 307 | def Clear_log( self ): 308 | return 309 | ``` 310 | 311 | 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: 312 | 313 | ```python 314 | def Reset_log(): 315 | self.Clear_log() 316 | self.Save_log() 317 | return 318 | ``` 319 | 320 | Now it's time to experiment. 321 | 322 | 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. 323 | 324 | Happy programming! -------------------------------------------------------------------------------- /088/example_extensions/text_test_clear.py: -------------------------------------------------------------------------------- 1 | parent().Clear_log() -------------------------------------------------------------------------------- /088/example_extensions/text_test_date_time.py: -------------------------------------------------------------------------------- 1 | date_time_test = parent().Log_date_time() 2 | 3 | print( date_time_test ) -------------------------------------------------------------------------------- /088/example_extensions/text_test_log_compact.py: -------------------------------------------------------------------------------- 1 | operator = op( 'constant1' ) 2 | message = 'This is operator needs attention' 3 | 4 | parent().Log_message( operator, message ) -------------------------------------------------------------------------------- /088/example_extensions/text_test_log_verbose.py: -------------------------------------------------------------------------------- 1 | operator = op( 'constant1' ) 2 | message = 'This is operator needs attention' 3 | 4 | parent().Log_message( operator, message, verbose = True ) -------------------------------------------------------------------------------- /088/example_extensions/text_test_save.py: -------------------------------------------------------------------------------- 1 | parent().Save_log() -------------------------------------------------------------------------------- /088/example_for_loop/example_for_loop.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_for_loop/example_for_loop.tox -------------------------------------------------------------------------------- /088/example_for_loop/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_for_loop ## 8 | 9 | Loops are an indispensable concept when it comes to programming and there are a virtually endless number of uses for them that we might encounter. What exactly is a loop then? A loop is a way of thinking about any kind of process that's repeated. Let's first consider a simple example all of us might have encountered - washing dishes. This processes looks the same to all of us who may have found a sink full of dirty dishes waiting for attention. What do you do in this situation? Let's assume that we don't have dishwasher, and for the sake of simplicity let's imagine that the act of washing the dishes is the same even if the dishes themselves are different. One by one, each dish is washed, rinsed, and dried. Let's imagine that we already have functions for washing and drying dishes. If we were going to do this programatically for four dishes it might look like: 10 | 11 | ```python 12 | wash( dish1 ) 13 | dry( dish1 ) 14 | 15 | wash( dish2 ) 16 | dry( dish2 ) 17 | 18 | wash( dish3 ) 19 | dry( dish3 ) 20 | 21 | wash( dish4 ) 22 | dry( dish4 ) 23 | ``` 24 | 25 | That's not so bad, but what if we had 100 dishes, or 1000, or 10,000? 26 | That's a lot of hard coding. 27 | 28 | What if, instead, we could just make a list of our dishes: 29 | 30 | ```python 31 | [ dish1, dish2, dish3, dish4 ] 32 | ``` 33 | 34 | And then use a function to repeat the same procedure for every item in the list? Well, that's what a loop is for. In this case, this is what a _for loop_ is good for. A _for loop_ will do the same thing, for every item in a list, or for every number in a range. In the case of our dishes let's look at what that would mean for a list of dishes. 35 | 36 | ```python 37 | dishes = [ dish1, dish2, dish3, dish4 ] 38 | 39 | for item in dishes: 40 | wash( item ) 41 | dry( item ) 42 | ``` 43 | 44 | And that's it. If the list is 1 item long, or 10,000 items long, we don't have to write any more code than that. Easy. 45 | 46 | Okay, but what is a for loop?! 47 | 48 | We might think of a for loop is a simple function that simply repeats the same series of actions in an iterative process - each successive item at a time. It's easy for us to start by thinking of loops as being connected to lists, but they don't have to be. They can be attached to a range - a range being a start and end point represented as integers. Instead of having a list of dishes, what if we only knew the number of dishes in the sink? How could we write this loop? 49 | 50 | ```python 51 | for item in range( 10 ): 52 | wash( item ) 53 | dry( item ) 54 | ``` 55 | 56 | Okay. That's all well and good, but how can we start to think about this in a context that will help us? 57 | 58 | Lets first make a simple list so we have something to work with 59 | 60 | ```python 61 | simple_list = [ 'apple', 'kiwi', 'orange', 'grape', 'pineapple' ] 62 | ``` 63 | The anatomy of our for loop can be a little confusing at first but once we get the hang of it, they're very powerful. 64 | 65 | Okay, so what does that syntax look like: 66 | 67 | python 68 | for stand_in_variable in a_list_or_range: 69 | do each of these operations 70 | 71 | let's first imagine that we want to print out each 72 | of the items in our list 73 | 74 | ```python 75 | for item in simple_list: 76 | print( item ) 77 | ``` 78 | 79 | Here we can see that 'item' is our stand in variable. For reach positional item in our simple_list, we print out that item. 80 | 81 | What if we don't have a list, and instead just want to run a loop a set number of times. Surely that's possible, right? 82 | 83 | In fact it is! 84 | 85 | ```python 86 | loop_count = 10 87 | 88 | print( "This is our first loop" ) 89 | for item in range( loop_count ): 90 | print( item ) 91 | ``` 92 | 93 | That works great, but what's a range?! Let's print one out so we can better 94 | see what that's about: 95 | 96 | ```python 97 | print( '\n' ) 98 | print( "This is the range of our loop_count" ) 99 | print( range( loop_count ) ) 100 | ``` 101 | Printing this out we can see what we get a tuple with a starting and ending position in the list this might seem to imply that we can use a range, or list, but start at a position other than the first item. 102 | 103 | ```python 104 | print( '\n' ) 105 | print( "This is loop starting at position 5 going to the end" ) 106 | for item in range( loop_count )[ 5: ]: 107 | print( item ) 108 | 109 | print( '\n' ) 110 | print( "This is loop starting at the beginning and going to postiion 5" ) 111 | for item in range( loop_count )[ :5 ]: 112 | print( item ) 113 | ``` 114 | 115 | Our simple list 116 | 117 | ```python 118 | simple_list = [ 'apple', 'kiwi', 'orange', 'grape', 'pineapple' ] 119 | ``` 120 | 121 | Sometimes we want to use the contents of a list but we also need to know the position of the item in the list. For that we can use the enumerate method that's built into python. Let's see it in action to better understand what we're getting when we use enumerate. 122 | 123 | ```python 124 | print( "Here's our enumerated list" ) 125 | for item in enumerate( simple_list ): 126 | print( item ) 127 | ``` 128 | 129 | Alright, that's great, we can see here that we get a tuple of our list position, and our list item. That's great, but how can we use that? 130 | 131 | ```python 132 | print( '\n' ) 133 | print( "Here's our enumerated list broken up a bit" ) 134 | for item in enumerate( simple_list ): 135 | print( item[ 0 ], item[ 1 ] ) 136 | ``` 137 | 138 | Let's imagine that we want to fill in something like a sentence with the information from our list. 139 | 140 | ```python 141 | message = "Item list position {}, actual item {}" 142 | 143 | print( '\n' ) 144 | print( "Here's our enumerated list used with format" ) 145 | for item in enumerate( simple_list ): 146 | print( message.format( item[ 0 ], item[ 1 ] ) ) 147 | ``` 148 | 149 | table = op( 'table_simple_table' ) 150 | 151 | Lists are all well and good, but how do we use them in TouchDesigner? First lets look at how we might use some tables. 152 | 153 | One way to loop through our table's rows would be to use the number of rows to define a range, then use our references to tables to move through its contents 154 | 155 | ```python 156 | print( "Here's one way to loop through our table" ) 157 | for item in range( table.numRows ): 158 | print( table[ item, 0 ] ) 159 | ``` 160 | 161 | Another way to loop through our table would be to use the .col() method. This returns an object that's a list of rows. To get the content of the cell, we need to use .val. 162 | 163 | ```python 164 | print( '\n' ) 165 | print( "Here's another way to loop through our table" ) 166 | for item in table.col( 0 ): 167 | print( item.val ) 168 | ``` 169 | 170 | If we remember that channels are arrays of numbers, we can quickly see how we might move through one of this arrays with a loop. 171 | 172 | First let's remember that we can access the number of samples in a CHOP with .numSamples. We can use this value to determine our range. 173 | 174 | We should take a quick moment to think about how we can access a single sample in a CHOP: 175 | 176 | ```python 177 | print( "let's look at the value at position 0" ) 178 | print( op( 'pattern1' )[ 'chan1' ][ 0 ] ) 179 | ``` 180 | 181 | Let's start with a simple task like printing out the value of each sample in a pattern CHOP. 182 | 183 | First let's simplify our code by using a variable to reference our pattern CHOP. 184 | 185 | ```python 186 | pattern = op( 'pattern1' ) 187 | ``` 188 | 189 | Next we'll write a simple for loop that runs based on the number of samples in a CHOP. 190 | 191 | ```python 192 | print( '\n' ) 193 | print( "let's print out every sample" ) 194 | for sample in range( pattern.numSamples ): 195 | print( pattern[ 'chan1' ][ sample ] ) 196 | ``` 197 | 198 | Let's go one step further and use .format() one more time to make for print statements that make more sense. 199 | 200 | ```python 201 | message = "{} is the value in the {} position" 202 | noise = op( 'noise1' ) 203 | 204 | print( '\n' ) 205 | print( "let's print out every sample in noise" ) 206 | for sample in range( noise.numSamples ): 207 | print( message.format( 208 | round( noise[ 'chan1' ][ sample ], 3 ) , 209 | sample 210 | ) 211 | ) 212 | ``` 213 | 214 | How else might we use for loops? We've seen how they might work with CHOPs and DATs, but what about TOPs? 215 | 216 | We might imagine a circumstance where we wanted to fill a texture 3D - maybe for instancing, UI building or any number of things. 217 | 218 | We can do this with a for loop easily, with just a little bit of thought. 219 | 220 | ```python 221 | # define some variables 222 | text_DAT = op( 'table_tex3d' ) 223 | text_TOP = op( 'text1' ) 224 | tex3d_TOP = op( 'tex3d1' ) 225 | 226 | for item in range( text_DAT.numRows ): 227 | # change the text 228 | text_TOP.par.text = text_DAT[ item, 0 ] 229 | 230 | # use a random number to set the background color 231 | text_TOP.par.bgcolorr = tdu.rand( item ) 232 | text_TOP.par.bgcolorg = tdu.rand( item + 1 ) 233 | text_TOP.par.bgcolorb = tdu.rand( item + 2 ) 234 | 235 | # set the repalce index to match the row 236 | tex3d_TOP.par.replaceindex = item 237 | 238 | # pulse fill the texture 3D 239 | tex3d_TOP.par.resetsinglepulse.pulse() 240 | ``` 241 | 242 | Loops are useful for any number of processes. Let's imagine that we want to fill up a table with the RGBA values from a TOP. 243 | 244 | We could certainly use a TOP to CHOP, and then a CHOP to DAT, but we might imagine a circumstance where we don't want this operation to happen all the time, only at times that we specify. 245 | 246 | Or we might want to sample an image for colors to use for another process, and we don't need a dedicated series of operators for this. 247 | 248 | At any rate, lets look at how we might do this. 249 | 250 | In this case I've set up a noise TOP to be 20 pixels tall, and 1 pixel wide. We can think of the number of pixels vertically as the range for our loop. 251 | 252 | We'll clear a table, and then append the contents for every pixel in our TOP. Easy. 253 | 254 | First let's use some variables to make writing our loop easeir: 255 | 256 | ```python 257 | nosie_TOP = op( 'noise2' ) 258 | pixel_vals_DAT = op( 'table1' ) 259 | header = [ 'r', 'g', 'b', 'a' ] 260 | ``` 261 | 262 | In case there's anything left in our table, let's clear its contents first. 263 | 264 | ```python 265 | pixel_vals_DAT.clear() 266 | ``` 267 | 268 | Next let's put some header information back into our dat, so we know what each colum is: 269 | 270 | ```python 271 | pixel_vals_DAT.appendRow( header ) 272 | ``` 273 | 274 | Now we can loop through our pixels and append their values to our table. 275 | 276 | ```python 277 | for pixel in range( nosie_TOP.height ): 278 | pixel_vals_DAT.appendRow( nosie_TOP.sample( x = 0, y = pixel ) ) 279 | ``` 280 | 281 | List comprehensions are a powerful means of constructing lists quickly. These might feel familiar from math courses you've taken or they may feel totally unfamiliar. In either case, they're a wonderful tool to be able to use, and can make fast work for building or changing lists. 282 | 283 | Let's make a fast list so we can see the initial mechanics of list comprehensions 284 | 285 | ```python 286 | my_list = [ 5, 10 , 4 , 6 , 20, 13, 7, 31 ] 287 | ``` 288 | 289 | First let's look at how we can print the contents of a list from inside the list. 290 | 291 | ```python 292 | print( "Let's start by just printing out the contents of the list" ) 293 | print( "- - - - - - - - - -" ) 294 | new_list1 = [ print( item ) for item in my_list ] 295 | ``` 296 | 297 | We can also print out the index of our item as we go 298 | 299 | ```python 300 | print( "\n" ) 301 | print( "Now lets look at how we can see the index of the items in our list" ) 302 | print( "- - - - - - - - - -" ) 303 | new_list2 = [ print( my_list.index( item ) ) for item in my_list ] 304 | ``` 305 | 306 | With a little bit of careful writing we can do both at the same time 307 | 308 | ```python 309 | print( "\n" ) 310 | print( "Now let's print both together" ) 311 | print( "- - - - - - - - - -" ) 312 | new_list3 = [ 313 | print( 'the index of this item is: ' + str( my_list.index( item ) ) , 314 | 'the acutal list item is: ' + str( item ) ) for item in my_list 315 | ] 316 | ``` 317 | 318 | We can also construct a list from scratch, in this case we'll 319 | make a list of each number in the list * 2 for the range of 10 320 | If we write that out by hand we can see what we'll expect from 321 | our comprehension: 322 | 0 * 2 = 0 323 | 1 * 2 = 2 324 | 2 * 2 = 4 325 | 3 * 2 = 6 326 | 4 * 2 = 8 327 | 5 * 2 = 10 328 | 6 * 2 = 12 329 | 7 * 2 = 14 330 | 8 * 2 = 16 331 | 9 * 2 = 18 332 | 333 | Now that we know what we're expecting to see, let's see if it works the way we want 334 | 335 | ```python 336 | print( "\n" ) 337 | print( """Let's see how we can construct a list 338 | from scratch with a list comprehension""" ) 339 | print( "- - - - - - - - - -" ) 340 | list_from_scratch = [ item * 2 for item in range( 10 ) ] 341 | print( list_from_scratch ) 342 | ``` 343 | 344 | We can also construct a new list from a previous one. In this case let's see if we can construct a list that's only the even numbers from our first list. 345 | 346 | ```python 347 | print( "\n" ) 348 | print( "Let's see how we can construct a new list from an old one" ) 349 | print( "In this case, let's see if we can build one that's only even numbers" ) 350 | print( "- - - - - - - - - -" ) 351 | evens_only = [ item for item in my_list if item % 2 == 0 ] 352 | print( evens_only ) 353 | ``` 354 | 355 | We can also use loops to do all sorts of exciting things like create and place operators. 356 | 357 | What we'll quickly realize here is that for loops and the replicator COMP look very similar in the way they operate when it comes to creating ops! 358 | 359 | You might feel like the replicator is good enough, so why learn how this work?! Sometimes knowing exactly how a process works can help us better understand another process. 360 | 361 | In this case, setting up our own replicator script can teach us a lot about the replicator. 362 | 363 | ```python 364 | new_ops_list = [ 365 | 'text_newop1' , 366 | 'text_newop2' , 367 | 'text_newop3' 368 | ] 369 | ``` 370 | 371 | We'll use this to determine the space between operators. 372 | 373 | ```python 374 | node_distance = 100 375 | ``` 376 | 377 | Loop through list 378 | 379 | ```python 380 | for item in enumerate( new_ops_list ): 381 | # create and name op 382 | new_op = parent().create( textDAT , item[ 1 ] ) 383 | 384 | # set location of nodes ( x or y ) 385 | new_op.nodeX = me.nodeX 386 | new_op.nodeY = - ( item[ 0 ] * node_distance ) 387 | ``` 388 | 389 | While we haven't yet talked about the pars() we can take advantage of this member of the page class. 390 | 391 | Any given operator with parameters has a page of parameters, with names and values. Knowing this we can find our way to the paramters of an op with some clever programming. 392 | 393 | Let's look at the anatomy of something like: 394 | ```python 395 | op( 'constant_pars_target' ).pars( 'name0' )[ 0 ] 396 | ``` 397 | 398 | pars() returns a list of parameters. For us, we can think of the above in plain english as the first list element in the parameter 'name0' from constant_pars_target. 399 | 400 | Okay, so what can we do with that information? 401 | 402 | Well, we might make a table of par names and values, and set them by looping through our table. Let's take a look at how that might work. 403 | 404 | First we'll make a table called 'table_pars_preset1'. Let's fill that table with par names in one column, and par values in another. 405 | 406 | Next we'll create a stand in table where we can indicate which preset we want to use. Let's call that 'table_preset_selection'. 407 | 408 | In the first cell let's write 'table_pars_preset1'. Alright, we're almost ready to write our for loop. 409 | 410 | Finally let's add a constant CHOP called 'constant_pars_target' 411 | 412 | Now let's write a loop! 413 | 414 | First we'll start with some variable names: 415 | 416 | ```python 417 | preset = op( 'table_preset_selection' )[ 0, 0 ] 418 | target = op( 'constant_pars_target' ) 419 | ``` 420 | 421 | Here in our loop is where things get intersting. 422 | For starters we're going to use the op string name we've defined in our 423 | preset table. 424 | 425 | ```python 426 | for item in range( op( preset ).numRows )[ 1: ]: 427 | 428 | # As we go through the loop we'll use two variables one as our 429 | # targed op_parameter, and another as a our parameter_value 430 | op_par = op( preset )[ item, 0 ] 431 | par_val = op( preset )[ item, 1 ] 432 | 433 | # Finally, we'll use those two varibles to change some varibles. 434 | target.pars( op_par )[ 0 ].val = par_val 435 | 436 | # We can use our preset statement to see how those two work together: 437 | print( 'target parameter: ', op_par ) 438 | print( 'target value: ', par_val ) 439 | 440 | # Let's also look at what that script would be if we were to write it 441 | # out by hand: 442 | script = 'op( "{op}" ).{par} = {par_val}' 443 | 444 | print( 'The script we run each loop:' ) 445 | print( script.format( op = target.name, par = op_par, par_val = par_val ) ) 446 | 447 | print( '\n' ) 448 | ``` 449 | 450 | [Learn more about loops from Learn Python the Hard Way(http://learnpythonthehardway.org/book/ex32.html)] 451 | 452 | [Learn more about lists](https://docs.python.org/3.3/tutorial/datastructures.html) -------------------------------------------------------------------------------- /088/example_for_loop/text_a_first_loop.py: -------------------------------------------------------------------------------- 1 | # lets first make a simple list so we have something to work with 2 | simple_list = [ 'apple', 'kiwi', 'orange', 'grape', 'pineapple' ] 3 | 4 | # the anatomy of our for loop can be a little confusing at first 5 | # but once we get the hang of it, they're very powerful. 6 | 7 | # okay, so what does that syntax look like 8 | 9 | # for stand_in_variable in a_list_or_range: 10 | # do each of these operations 11 | 12 | # let's first imagine that we want to print out each 13 | # of the items in our list 14 | 15 | for item in simple_list: 16 | print( item ) 17 | 18 | # here we can see that 'item' is our stand in 19 | # variable. For reach positial item in our 20 | # simple_list, we print out that item. -------------------------------------------------------------------------------- /088/example_for_loop/text_a_first_loop2.py: -------------------------------------------------------------------------------- 1 | # what if we don't have a list, and instead 2 | # just want to run a loop a set number of 3 | # times. Surely that's possible, right? 4 | 5 | # In fact it is! 6 | 7 | loop_count = 10 8 | 9 | print( "This is our first loop" ) 10 | for item in range( loop_count ): 11 | print( item ) 12 | 13 | # that works great, but what's a range?! 14 | # let's print one out so we can better 15 | # see what that's about: 16 | 17 | print( '\n' ) 18 | print( "This is the range of our loop_count" ) 19 | print( range( loop_count ) ) 20 | 21 | # printing this out we can see what we 22 | # get a tuple with a starting and 23 | # ending position in the list 24 | # this might seem to imply that we can 25 | # use a range, or list, but start at a 26 | # position other than the first item. 27 | 28 | print( '\n' ) 29 | print( "This is loop starting at position 5 going to the end" ) 30 | for item in range( loop_count )[ 5: ]: 31 | print( item ) 32 | 33 | print( '\n' ) 34 | print( "This is loop starting at the beginning and going to postiion 5" ) 35 | for item in range( loop_count )[ :5 ]: 36 | print( item ) -------------------------------------------------------------------------------- /088/example_for_loop/text_channels_as_lists.py: -------------------------------------------------------------------------------- 1 | # if we remember that channels are arrays of numbers, 2 | # we can quickly see how we might move through one 3 | # of this arrays with a loop. 4 | 5 | # first let's remember that we can access the number 6 | # of samples in a CHOP with .numSamples. We can use 7 | # this value to determine our range 8 | 9 | # we should take a quick moment to think about how 10 | # we can access a single sample in a CHOP: 11 | 12 | print( "let's look at the value at position 0" ) 13 | print( op( 'pattern1' )[ 'chan1' ][ 0 ] ) 14 | 15 | # let's start with a simple task like printing out 16 | # the value of each sample in a pattern CHOP. 17 | 18 | # first let's simplify our code by using a variable 19 | # to reference our pattern CHOP. 20 | pattern = op( 'pattern1' ) 21 | 22 | # next we'll write a simple for loop that runs 23 | # based on the number of samples in a CHOP. 24 | 25 | print( '\n' ) 26 | print( "let's print out every sample" ) 27 | for sample in range( pattern.numSamples ): 28 | print( pattern[ 'chan1' ][ sample ] ) 29 | 30 | # let's go one step further and use .format() 31 | # one more time to make for print statements 32 | # that make more sense. 33 | 34 | message = "{} is the value in the {} position" 35 | noise = op( 'noise1' ) 36 | 37 | print( '\n' ) 38 | print( "let's print out every sample in noise" ) 39 | for sample in range( noise.numSamples ): 40 | print( message.format( 41 | round( noise[ 'chan1' ][ sample ], 3 ) , 42 | sample 43 | ) 44 | ) -------------------------------------------------------------------------------- /088/example_for_loop/text_create_ops_loop.py: -------------------------------------------------------------------------------- 1 | # we can also use loops to do all sorts of exciting things 2 | # like creat and place operators. 3 | 4 | # What we'll quikcly realize here is that for loops and 5 | # the replicator COMP look very similar in the way they 6 | # operate when it comes to creating ops! 7 | 8 | # You might feel like the replicator is good enough, so why 9 | # learn how this work?! Sometimes knowing exactly how a process 10 | # works can help us better understand another process. 11 | 12 | # In this case, setting up our own replicator script can 13 | # teach us a lot about the replicator. 14 | 15 | # set some variables 16 | new_ops_list = [ 17 | 'text_newop1' , 18 | 'text_newop2' , 19 | 'text_newop3' 20 | ] 21 | 22 | # We'll use this to determine the space between operators. 23 | node_distance = 100 24 | 25 | # loop through list 26 | for item in enumerate( new_ops_list ): 27 | # create and name op 28 | new_op = parent().create( textDAT , item[ 1 ] ) 29 | 30 | # set location of nodes ( x or y ) 31 | new_op.nodeX = me.nodeX 32 | new_op.nodeY = - ( item[ 0 ] * node_distance ) -------------------------------------------------------------------------------- /088/example_for_loop/text_enumerating_lists.py: -------------------------------------------------------------------------------- 1 | # our simple list 2 | simple_list = [ 'apple', 'kiwi', 'orange', 'grape', 'pineapple' ] 3 | 4 | # sometimes we want to use the contents of a list 5 | # but we also need to know the position of the item 6 | # in the list. For that we can use the enumerate method 7 | # that's built into python. 8 | 9 | # let's see it in action to better understand 10 | # what we're getting when we use enumerate 11 | 12 | print( "Here's our enumerated list" ) 13 | for item in enumerate( simple_list ): 14 | print( item ) 15 | 16 | # alright, that's great, we can see here that we 17 | # get a tuple of our list position, and our list item. 18 | # that's great, but how can we use that? 19 | 20 | print( '\n' ) 21 | print( "Here's our enumerated list broken up a bit" ) 22 | for item in enumerate( simple_list ): 23 | print( item[ 0 ], item[ 1 ] ) 24 | 25 | # let's imagine that we want to fill in something 26 | # like a sentance with the information from 27 | # our list. 28 | 29 | message = "Item list position {}, actual item {}" 30 | 31 | print( '\n' ) 32 | print( "Here's our enumerated list used with format" ) 33 | for item in enumerate( simple_list ): 34 | print( message.format( item[ 0 ], item[ 1 ] ) ) -------------------------------------------------------------------------------- /088/example_for_loop/text_filling_a_table.py: -------------------------------------------------------------------------------- 1 | # Loops are useful for any number of processes. Let's imagine 2 | # that we want to fill up a table with the RGBA values from 3 | # a TOP. 4 | 5 | # We could certainly use a TOP to CHOP, and then a CHOP to DAT, 6 | # but we might imagine a circumstance where we don't want this 7 | # operation to happen all the time, only at times that we specify. 8 | 9 | # Or we might want to sample an image for colors to use for another 10 | # process, and we don't need a dedicated series of operators for this. 11 | 12 | # At any rate, lets look at how we might do this. 13 | 14 | # In this case I've set up a noise TOP to be 20 pixels tall, and 1 pixel 15 | # wide. We can think of the number of pixels vertically as the range 16 | # for our loop. 17 | 18 | # We'll clear a table, and then append the contents for every pixel 19 | # in our TOP. Easy. 20 | 21 | # First let's use some variables to make writing our loop easeir: 22 | 23 | nosie_TOP = op( 'noise2' ) 24 | pixel_vals_DAT = op( 'table1' ) 25 | header = [ 'r', 'g', 'b', 'a' ] 26 | 27 | # In case there's anything left in our table, let's clear its 28 | # contents first. 29 | pixel_vals_DAT.clear() 30 | 31 | # Next let's put some header information back into our dat, 32 | # so we know what each colum is: 33 | pixel_vals_DAT.appendRow( header ) 34 | 35 | # Now we can loop through our pixels and append their values 36 | # to our table. 37 | for pixel in range( nosie_TOP.height ): 38 | pixel_vals_DAT.appendRow( nosie_TOP.sample( x = 0, y = pixel ) ) -------------------------------------------------------------------------------- /088/example_for_loop/text_list_comprehensions.py: -------------------------------------------------------------------------------- 1 | # List comprehensions are a powerful means of constructing lists 2 | # quickly. These might feel familar from math courses you've taken 3 | # or they may feel totally unfamiliar. In either case, they're 4 | # a wonderful tool to be able to use, and can make fast work 5 | # for building or changing lists. 6 | 7 | # Let's make a fast list so we can see the initial mechaics of list 8 | # comprehensions 9 | my_list = [ 5, 10 , 4 , 6 , 20, 13, 7, 31 ] 10 | 11 | # First let's look at how we can print the contents of a list 12 | # from inside the list 13 | print( "Let's start by just printing out the contents of the list" ) 14 | print( "- - - - - - - - - -" ) 15 | new_list1 = [ print( item ) for item in my_list ] 16 | 17 | # We can also print out the index of our item as we go 18 | print( "\n" ) 19 | print( "Now lets look at how we can see the index of the items in our list" ) 20 | print( "- - - - - - - - - -" ) 21 | new_list2 = [ print( my_list.index( item ) ) for item in my_list ] 22 | 23 | # With a little bit of careful writing we can do both at the same time 24 | print( "\n" ) 25 | print( "Now let's print both together" ) 26 | print( "- - - - - - - - - -" ) 27 | new_list3 = [ 28 | print( 'the index of this item is: ' + str( my_list.index( item ) ) , 29 | 'the acutal list item is: ' + str( item ) ) for item in my_list 30 | ] 31 | 32 | # We can also construct a list from scratch, in this case we'll 33 | # make a list of each number in the list * 2 for the range of 10 34 | # If we write that out by hand we can see what we'll expect from 35 | # our comprehension: 36 | # 0 * 2 = 0 37 | # 1 * 2 = 2 38 | # 2 * 2 = 4 39 | # 3 * 2 = 6 40 | # 4 * 2 = 8 41 | # 5 * 2 = 10 42 | # 6 * 2 = 12 43 | # 7 * 2 = 14 44 | # 8 * 2 = 16 45 | # 9 * 2 = 18 46 | 47 | # now that we know what we're expecting to see, let's see if it 48 | # works the way we want 49 | print( "\n" ) 50 | print( """Let's see how we can construct a list 51 | from scratch with a list comprehension""" ) 52 | print( "- - - - - - - - - -" ) 53 | list_from_scratch = [ item * 2 for item in range( 10 ) ] 54 | print( list_from_scratch ) 55 | 56 | # We can also construct a new list from a previous one. In this case 57 | # Let's see if we can construct a list that's only the even nubmers 58 | # from our first list. 59 | print( "\n" ) 60 | print( "Let's see how we can construct a new list from an old one" ) 61 | print( "In this case, let's see if we can build one that's only even numbers" ) 62 | print( "- - - - - - - - - -" ) 63 | evens_only = [ item for item in my_list if item % 2 == 0 ] 64 | print( evens_only ) -------------------------------------------------------------------------------- /088/example_for_loop/text_pars_loop.py: -------------------------------------------------------------------------------- 1 | # while we haven't yet talked about the pars() we can take advantage of this 2 | # member of the page class. 3 | 4 | # Any given operator with parameters has a page of parameters, with names 5 | # and values. Knowing this we can find our way to the paramters of an 6 | # op with some clever programming. 7 | 8 | # Let's look at the anatomy of something like: 9 | # op( 'constant_pars_target' ).pars( 'name0' )[ 0 ] 10 | 11 | # pars() returns a list of parameters. For us, we can think of the above 12 | # in plain english as the first list element in the 13 | # parameter 'name0' from constant_pars_target. 14 | 15 | # Okay, so what can we do with that information? 16 | 17 | # Well, we might make a table of par names and values, and set them by 18 | # looping through our table. Let's take a look at how that might work. 19 | 20 | # First we'll make a table called 'table_pars_preset1' 21 | # Let's fill that table with par names in one column, and par values 22 | # in another. 23 | 24 | # Next we'll create a stand in table where we can indicate which preset 25 | # we want to use. Let's call that 'table_preset_selection'. 26 | 27 | # In the first cell let's write 'table_pars_preset1' 28 | # Alright, we're almost ready to write our for loop. 29 | 30 | # Finally let's add a constant CHOP called 'constant_pars_target' 31 | 32 | # Now let's write a loop! 33 | 34 | # First we'll start with some variable names: 35 | 36 | preset = op( 'table_preset_selection' )[ 0, 0 ] 37 | target = op( 'constant_pars_target' ) 38 | 39 | # Here in our loop is where things get intersting. 40 | # For starters we're going to use the op string name we've defined in our 41 | # preset table. 42 | 43 | for item in range( op( preset ).numRows )[ 1: ]: 44 | 45 | # As we go through the loop we'll use two variables one as our 46 | # targed op_parameter, and another as a our parameter_value 47 | op_par = op( preset )[ item, 0 ] 48 | par_val = op( preset )[ item, 1 ] 49 | 50 | # Finally, we'll use those two varibles to change some varibles. 51 | target.pars( op_par )[ 0 ].val = par_val 52 | 53 | # We can use our preset statement to see how those two work together: 54 | print( 'target parameter: ', op_par ) 55 | print( 'target value: ', par_val ) 56 | 57 | # Let's also look at what that script would be if we were to write it 58 | # out by hand: 59 | script = 'op( "{op}" ).{par} = {par_val}' 60 | 61 | print( 'The script we run each loop:' ) 62 | print( script.format( op = target.name, par = op_par, par_val = par_val ) ) 63 | 64 | print( '\n' ) 65 | 66 | -------------------------------------------------------------------------------- /088/example_for_loop/text_tex3d_fill.py: -------------------------------------------------------------------------------- 1 | # how else might we use for loops? We've seen how they 2 | # might work with CHOPs and DATs, but what about TOPs? 3 | 4 | # We might imagine a circumstance where we wanted to 5 | # fill a texture 3D - maybe for instancing, UI building 6 | # or any number of things. 7 | 8 | # We can do this with a for loop easily, with just a 9 | # little bit of thought. 10 | 11 | # define some variables 12 | text_DAT = op( 'table_tex3d' ) 13 | text_TOP = op( 'text1' ) 14 | tex3d_TOP = op( 'tex3d1' ) 15 | 16 | for item in range( text_DAT.numRows ): 17 | # change the text 18 | text_TOP.par.text = text_DAT[ item, 0 ] 19 | 20 | # use a random number to set the background color 21 | text_TOP.par.bgcolorr = tdu.rand( item ) 22 | text_TOP.par.bgcolorg = tdu.rand( item + 1 ) 23 | text_TOP.par.bgcolorb = tdu.rand( item + 2 ) 24 | 25 | # set the repalce index to match the row 26 | tex3d_TOP.par.replaceindex = item 27 | 28 | # pulse fill the texture 3D 29 | tex3d_TOP.par.resetsinglepulse.pulse() -------------------------------------------------------------------------------- /088/example_for_loop/text_working_with_table_DATs.py: -------------------------------------------------------------------------------- 1 | table = op( 'table_simple_table' ) 2 | 3 | # lists are all well and good, but how do we use 4 | # them in TouchDesigner? 5 | # first lets look at how we might use some tables. 6 | 7 | # looping through table rows 8 | 9 | # one way to loop through our table's rows would be 10 | # to use the number of rows to define a range, 11 | # then use our references to tables to move through 12 | # its contents 13 | 14 | print( "Here's one way to loop through our table" ) 15 | for item in range( table.numRows ): 16 | print( table[ item, 0 ] ) 17 | 18 | 19 | # another way to loop through our table would be to use 20 | # the .col() method. This returns an object that's a 21 | # list of rows. To get the content of the cell, we 22 | # need to use .val. 23 | 24 | print( '\n' ) 25 | print( "Here's another way to loop through our table" ) 26 | for item in table.col( 0 ): 27 | print( item.val ) -------------------------------------------------------------------------------- /088/example_intro_to_functions/example_intro_to_functions.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_intro_to_functions/example_intro_to_functions.tox -------------------------------------------------------------------------------- /088/example_intro_to_functions/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_intro_to_functions ## 8 | 9 | Before we can tackle CHOP executes we need to take a moment to learn about functions. There's a lot to learn about with functions, so we're not going to dive too deep just yet... yet. We are, however, going to peer into this idea so we can better understand part of what we'll see next as we move into the exciting world of executes. 10 | 11 | Let's start by looking at what a function actually is: 12 | 13 | "A function is a block of organized, reusable code that is used to perform a single, related action. Functions provide better modularity for your application and a high degree of code reusing. 14 | 15 | As you already know, Python gives you many built-in functions like print(), etc. but you can also create your own functions." 16 | -- [Tutorials Point](http://www.tutorialspoint.com/python/python_functions.htm) 17 | 18 | Great! But... how can we better understand that? For a moment let's first appreciate that we have a wide variety of functions that we do on a regular basis... we just don't think of them as functions. Most of us know how to calculate a tip, or gas mileage, or estimate travel time, or pack a suitcase, or make lunch, or or or, and and and. We don't think of these as functions, but if we had to write out very specific instructions about how to complete one of these tasks we'd actually be close to starting to wrestle with the idea of what a function is - it's okay if that doesn't make sense yet. Hang on tight, because we're gonna get there. 19 | 20 | Let's first look at a simple example that examines the anatomy of a function. Next we'll write a few simple functions. Then we'll look at why that's important when it comes to thinking about CHOP executes. 21 | 22 | Starting with Anatomy. 23 | 24 | Here we go, we're going to write a dead simple function: 25 | 26 | ```python 27 | def first_function(): 28 | 29 | print( 'Hello World' ) 30 | 31 | return 32 | ``` 33 | 34 | There we go. We did it. Now, if we were to run this in TouchDesigner, nothing would happen... so at first glance it would seem like we didn't really write a function after all. That might be a good guess, but the reason nothing happened is because we never actually called our function, we just defined it - we wrote out all of the instructions, but we never asked TouchDesigner to actually run the function. To see anything happen, we need to actually call the function - we need to tell TouchDesigner that we need to run it. Let's modify our example to see what that would look like. 35 | 36 | ```python 37 | def first_function(): 38 | 39 | print( 'Hello World' ) 40 | 41 | return 42 | 43 | first_function() 44 | ``` 45 | 46 | Okay... time to take this all apart and see what makes it tick. 47 | * We're started out by indicating that we were going to define a function... that's really what we meant when we wrote "def." 48 | * Next we gave that function a name, in our case we called it "first_function." 49 | * Next we specified that we weren't going to pass in any arguments or parameters by writing "()" - don't worry, we're going to learn more about that in a second. 50 | * Then we indicated that we were going to outline what was in the function with our ":" 51 | * The next line is indented one tab space and here we print out "Hello World" 52 | * We ended the function with a return statement, which in this case didn't return anything. 53 | * Finally, we summoned our function into action by saying its name... well, writing its name "first_function()" 54 | 55 | At this point we've written a very simple function that just prints out "Hello World." We started with this simple example so we could just talk about its anatomy. Before we can move on to something a little more interesting, we need to unpack a few things. Specifically, we need to talk more about what it means to *return* something, and what an argument or parameter is when it comes to functions. 56 | 57 | Let's start with *return*. Like it's name suggests, to return something is to give it back, or deliver something. Seems straightforward enough, right? We might imagine that sometimes we don't want to print out the result of a function, but we do want to get something out the other side to use in another process. In this case, we want something returned to us after the function has run. Let's look at that in a concrete way. 58 | 59 | We're going to use our same example first function, but make a few changes. 60 | 61 | ```python 62 | def first_function(): 63 | 64 | text = 'Hello World' 65 | 66 | return text 67 | 68 | first_function() 69 | ``` 70 | 71 | Okay, here we can see that we changed our function so we don't actually print out "Hello World" anymore, instead we return it at the end. If we run our function, we encounter our same problem that we saw earlier... it would seem as if nothing happened. What gives. Let's change our function in one small way and see what we end up with: 72 | 73 | ```python 74 | def first_function(): 75 | 76 | text = 'Hello World' 77 | 78 | return text 79 | 80 | print( first_function() ) 81 | ``` 82 | 83 | The small change to print out first_function() means that we're now printing out what's returned from this function. It might feel like a small difference, but it means that we're able to control what comes out of our function when we summon it into action. That's actually a very important thing, and we'll see why shortly. 84 | 85 | If we can control what comes out of our functions, surely we can control what goes into them... right? In fact, you are right. 86 | 87 | Now that we now how to get something out of our function, let's pass it some information do to something with. We're going to write another simple function, this time to do some simple math. 88 | 89 | ```python 90 | def percent( val1 ): 91 | 92 | calculation = val1 * 0.01 93 | 94 | return calculation 95 | 96 | print( percent( 50 ) ) 97 | ``` 98 | 99 | Alright, what do we have here? Let's imagine we want to change an integer into it's float equivalent as a percentage. 50% as becomes 0.5, 10% would be 0.1, and so on and so on. Here we've written a function to do just that. In this case we've specified that our function accepts one argument which is named val1. We later see in our function that "calculation" is val1 * 0.01. Finally, we return calculation. This means we can give percent any number, and get a float value in return. Not bad. 100 | 101 | Okay, let's look at two more examples. Next we'll write a simple function to calculate a tip based on a total bill. At the end of this we want to see our tip and our total bill - using our new found lingo, we're going to return these values. 102 | 103 | Okay, let's make some Python magic happen. If you're playing along at home, trying writing this yourself before you look at how I did it. 104 | 105 | ```python 106 | def tip_calculator( total , tip_percentage ): 107 | 108 | tip = total * ( tip_percentage / 100 ) 109 | total_bill = total + tip 110 | 111 | return tip , total_bill 112 | 113 | print( tip_calculator( 50 , 15 ) ) 114 | ``` 115 | 116 | Here we want two things back, our tip and our total_bill. We start by calculating the tip, and then by adding that to our total. Finally we return these two values. 117 | 118 | Let's try one more idea on for size. This next time around you're challenge is to use the function we just wrote, and to write another function as a compliment. This second function is going to print out these values to our text port so we can see them. By writing this as two separate functions we decide when we want to print out our results, and when we want to just return our tip and total_bill. As an extra challenge, see if you can write your new function to accept only a single argument. 119 | 120 | Okay, let's look at how you might solve this problem: 121 | 122 | ```python 123 | def tip_calculator( total , tip_percentage ): 124 | 125 | tip = total * ( tip_percentage / 100 ) 126 | total_bill = total + tip 127 | 128 | return tip , total_bill 129 | 130 | def display_total( tip_and_total_bill ): 131 | 132 | dotted_line = '- ' * 10 133 | tip_text = "Your total tip is {}" 134 | total_bill_text = "Your total bill is {}" 135 | 136 | print( dotted_line ) 137 | print( tip_text.format( tip_and_total_bill[ 0 ] ) ) 138 | print( total_bill_text.format( tip_and_total_bill[ 1 ] ) ) 139 | print( dotted_line ) 140 | 141 | return 142 | 143 | total = 100 144 | tip_percentage = 20 145 | 146 | print( tip_calculator( total , tip_percentage ) ) 147 | 148 | display_total( tip_calculator( total , tip_percentage ) ) 149 | ``` 150 | 151 | How did you do? We can see that our first function stayed the same. Our second function accepts a single argument - tip_and_total_bill. This tuple ([a series of values](https://en.wikipedia.org/wiki/Tuple)) is then used by our second function when printing out to our textport. This probably isn't the best way to solve this problem... but for the sake of a simple example our chances of getting into trouble are pretty slim. 152 | 153 | Okay, so why do all of this?! Well, let's take a sneak peak at what's coming next. If we look at the contents of a CHOP execute we see: 154 | 155 | ```python 156 | # me - this DAT 157 | # 158 | # channel - the Channel object which has changed 159 | # sampleIndex - the index of the changed sample 160 | # val - the numeric value of the changed sample 161 | # prev - the previous sample value 162 | # 163 | # Make sure the corresponding toggle is enabled in the CHOP Execute DAT. 164 | 165 | def offToOn(channel, sampleIndex, val, prev): 166 | return 167 | 168 | def whileOn(channel, sampleIndex, val, prev): 169 | return 170 | 171 | def onToOff(channel, sampleIndex, val, prev): 172 | return 173 | 174 | def whileOff(channel, sampleIndex, val, prev): 175 | return 176 | 177 | def valueChange(channel, sampleIndex, val, prev): 178 | return 179 | ``` 180 | 181 | We should now recognize the contents of these DATs as functions... and not only are they functions, they're functions with four named incoming arguments. Now we can really start to have fun. 182 | 183 | [Learn more about functions in Python](http://www.tutorialspoint.com/python/python_functions.htm) -------------------------------------------------------------------------------- /088/example_intro_to_functions/text_my_first_function1.py: -------------------------------------------------------------------------------- 1 | def first_function(): 2 | 3 | print( 'Hello World' ) 4 | 5 | return 6 | 7 | first_function() -------------------------------------------------------------------------------- /088/example_intro_to_functions/text_my_first_function2.py: -------------------------------------------------------------------------------- 1 | def first_function(): 2 | 3 | text = "Hello World" 4 | 5 | return text 6 | 7 | first_function() 8 | 9 | print( first_function() ) -------------------------------------------------------------------------------- /088/example_intro_to_functions/text_my_first_function3.1.py: -------------------------------------------------------------------------------- 1 | def percent( val1 ): 2 | 3 | print( "Hello World" ) 4 | 5 | return 6 | 7 | percent( 50 ) -------------------------------------------------------------------------------- /088/example_intro_to_functions/text_my_first_function3.py: -------------------------------------------------------------------------------- 1 | def percent( val1 ): 2 | 3 | calculation = val1 * .01 4 | 5 | return calculation 6 | 7 | print( percent( 50 ) ) -------------------------------------------------------------------------------- /088/example_intro_to_functions/text_my_first_function4.py: -------------------------------------------------------------------------------- 1 | def tip_calculator( total , tip_percentage ): 2 | 3 | tip = total * ( tip_percentage / 100 ) 4 | total_bill = total + tip 5 | 6 | return tip , total_bill 7 | 8 | print( tip_calculator( 50 , 15 ) ) -------------------------------------------------------------------------------- /088/example_intro_to_functions/text_my_first_function5.py: -------------------------------------------------------------------------------- 1 | def tip_calculator( total , tip_percentage ): 2 | 3 | tip = total * ( tip_percentage / 100 ) 4 | total_bill = total + tip 5 | 6 | return tip , total_bill 7 | 8 | def display_total( tip_and_total_bill ): 9 | 10 | dotted_line = '- ' * 10 11 | tip_text = "Your total tip is {}" 12 | total_bill_text = "Your total bill is {}" 13 | 14 | print( dotted_line ) 15 | print( tip_text.format( tip_and_total_bill[ 0 ] ) ) 16 | print( total_bill_text.format( tip_and_total_bill[ 1 ] ) ) 17 | print( dotted_line ) 18 | 19 | return 20 | 21 | total = 100 22 | tip_percentage = 20 23 | 24 | print( tip_calculator( total , tip_percentage ) ) 25 | 26 | display_total( tip_calculator( total , tip_percentage ) ) -------------------------------------------------------------------------------- /088/example_logic/example_logic.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_logic/example_logic.tox -------------------------------------------------------------------------------- /088/example_logic/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_logic ## 8 | 9 | Logical statements are profoundly helpful for us when we're trying to convert an idea from what we understand, into something that a machine can interpret and act upon. Keeping that in mind, we need ways to distill ideas to their most fundamental pieces. What on earth do I mean? Well, we might think about ideas like greater than '>' , less than '<' , equal to '==' , and not equal to '!='. Further, we need to consider how we indicate when something might happen. This this set of examples we're going to focus on 'if' and 'else', as well as 'if' and 'elif' statements. What does all of that mean, well lets dig in and find out. 10 | 11 | 'if' and 'else' go hand in hand when we're thing about logical operations. The broad concept is that we're indicating what should happen if a particular condition is met, as well as what should happen when that condition isn't met. If we were to write this out as a set of instructions what we're really considering is what happens when a condition is met, and when it isn't met. Often when thinking about these kinds of situations it's easy for us to assume in action if our condition isn't met, which is a fine human assumption, but less fine when working with a machine. 12 | 13 | Let's look at a simple let's imagine you want to know when a number is equal to another number. This happens all the time when we're programming, so we can start here. 14 | 15 | First we need to do a bit of housekeeping and set ourselves up: 16 | 17 | ```python 18 | #deine a varaible 19 | my_int1 = 5 20 | ``` 21 | 22 | Next we can start to look at the syntax of our logical test. Our tests starts with a lower case 'if' followed by the test. We end our statement with colon ':' and indented on the next line we indicate what should happen if our test is met. 23 | 24 | ```python 25 | if my_int1 >= 6: 26 | print( 'This number is greater than or equal to 6' ) 27 | ``` 28 | 29 | Now, we're not done yet. We also need to indicate what should happen in all of the other circumstances... what happens if our number isn't great than or equal to? For this we use 'else:', and indented on the next line we indicate what should happen in this circumstance. 30 | 31 | ```python 32 | else: 33 | print( 'This number is less than or equal to 6' ) 34 | ``` 35 | 36 | Okay, that means that the whole python party looks like this: 37 | 38 | ```python 39 | # define our variables 40 | my_int1 = 5 41 | 42 | # a simple logical test 43 | if my_int1 >= 6: 44 | print( 'This number is great than or equal to 6' ) 45 | 46 | else: 47 | print( 'This number is less than or equal to 6' ) 48 | ``` 49 | 50 | That's all well and good... most of the time. BUT, what if we don't want to do anything if our test isn't true? Surely there's a way to handle that circumstance... right? There is indeed. We can use the 'pass' statement as a null operation. Meaning that nothing happens when we call pass. We can change the code above to instead be this: 51 | 52 | ```python 53 | # define our variables 54 | my_int1 = 5 55 | 56 | # a simple logical test 57 | if my_int1 >= 6: 58 | print( 'This number is great than or equal to 6' ) 59 | 60 | else: 61 | pass 62 | ``` 63 | 64 | That's alright, but what if we want to live in more than an if else world? What if I want to explore a lot of possibilities? Well, one thing we might use is 'elif'. 'elif' allows us to insert another if statement before we get to our final else. In English this might be something like, try this - did that work? If it didn't, try this other thing. Did that work? Okay, then do this. Let's look at what that might mean in Python: 65 | 66 | ```python 67 | # define our variables 68 | my_int1 = 5 69 | 70 | if my_int1 == 5: 71 | print( "This number is 5" ) 72 | 73 | elif my_int1 > 4 and my_int1 < 6: 74 | print( "This number is greater than 4, but less than 6" ) 75 | 76 | else: 77 | print( "This number is less than 4 or greter than 6" ) 78 | ``` 79 | 80 | That's pretty great. But what if we want to test the same number twice? For example, what if I wanted to know if the number was greater than 4 and less than 6? How could I write that? In that case we could use 'and' in our logical test. Let's look at what that might look like: 81 | 82 | ```python 83 | # define our variables 84 | my_int1 = 5 85 | 86 | if my_int1 > 4 and my_int1 < 6: 87 | print( "This number is greater than 4, but less than 6" ) 88 | 89 | else: 90 | print( "This number is less than 4 or greater than 6" ) 91 | ``` 92 | 93 | This this case, BOTH conditions must be met in order for our print statement to pass the test. 94 | 95 | What about if I have a circumstance where I have two possible circumstances I want to be treated the same? What if I want to print out a line if my number is greater than 4 or if it is exactly equal to -5? In this case we might use 'or'. 96 | 97 | ```python 98 | # define our variables 99 | my_int1 = -5 100 | 101 | if my_int1 > 4 or my_int1 == -5: 102 | print( "This number is greater than 4 or it's -5" ) 103 | 104 | else: 105 | print( "This number is less than 4 or greter than 6" ) 106 | ``` 107 | 108 | At this point surely you're rolling your eyes thinking "this is all well and good for Python, but what does it mean in TouchDesigner?!" That's an excellent question, and the magic here goes back to what we learned when thinking about references. Because Python variables can point to objects in TouchDesigner, we can move away from hardcoding our scripts, and instead let CHOPs stand in for our variables. For example, let's say we want to compare two numbers, we can use references to CHOPs to do just this. First let's make a constant CHOP with two channels, then we can write this bit of Python: 109 | 110 | ```python 111 | # define our variables 112 | my_int1 = op( 'constant1' )[ 'chan1' ] 113 | my_int2 = op( 'constant1' )[ 'chan2' ] 114 | 115 | if my_int1 == my_int2: 116 | print( "These values are equal!" ) 117 | 118 | elif my_int1 > my_int2: 119 | print( "Integer 1 is greater than integer 2" ) 120 | 121 | elif my_int1 < my_int2: 122 | print( "Integer 1 is less than integer 2" ) 123 | ``` 124 | 125 | This is still the tip of the iceberg as it were. The fun stuff starts to happen in a few more lessons. I know that might feel frustrating, but stick with me. The better our understanding of the fundamentals and printing, the happier we'll be when it comes time to do the fancier foot work of making our networks hum. We need to know a little more about data structures first, and then we'll start to get to the magic of executes... and loops. Then we'll really be screaming along. Before you know it you'll be writing whole functions, and then using them as modules. It's all coming - so hang on tight. 126 | 127 | [Find Python lessons at Learn Python the Hard Way](http://learnpythonthehardway.org/book/) 128 | 129 | _documentation written in markdown_ -------------------------------------------------------------------------------- /088/example_logic/text_comparing_different_CHOPs1.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | chop1_val1 = op( 'constant_CHOP1' )[ 'chan1' ] 3 | chop1_val2 = op( 'constant_CHOP1' )[ 'chan2' ] 4 | chop2_val1 = op( 'constant_CHOP2' )[ 'chan1' ] 5 | chop2_val2 = op( 'constant_CHOP2' )[ 'chan2' ] 6 | 7 | # compare chan1 in CHOP1 and CHOP2 8 | if chop1_val1 == chop2_val1: 9 | print( "These values are equal" ) 10 | 11 | elif chop1_val1 > chop2_val1: 12 | print( "Chan1 in CHOP 1 is greater than Chan2 in CHOP 2" ) 13 | 14 | elif chop1_val1 < chop2_val1: 15 | print( "Chan1 in CHOP 1 is less than Chan2 in CHOP 2" ) 16 | 17 | # compare chan2 in CHOP1 and CHOP2 18 | if chop1_val2 == chop2_val2: 19 | print( "These values are equal" ) 20 | 21 | elif chop1_val2 > chop2_val2: 22 | print( "Chan2 in CHOP 1 is greater than Chan2 in CHOP 2" ) 23 | 24 | elif chop1_val2 < chop2_val2: 25 | print( "Chan2 in CHOP 1 is less than Chan2 in CHOP 2" ) -------------------------------------------------------------------------------- /088/example_logic/text_comparing_different_CHOPs2.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | # this approach takes advantage of the channel class 3 | my_val1 = op( 'constant_CHOP1' ).chan( 0 ) 4 | my_val2 = op( 'constant_CHOP1' ).chan( 1 ) 5 | my_val3 = op( 'constant_CHOP2' ).chan( 0 ) 6 | my_val4 = op( 'constant_CHOP2' ).chan( 1 ) 7 | 8 | # compare chan1 in CHOP1 and CHOP2 9 | if my_val1 == my_val3: 10 | print( "These values are equal" ) 11 | 12 | elif my_val1 > my_val3: 13 | print( "my_val1 wins" ) 14 | print( "That value is " + str( my_val1 ) ) 15 | print( "It's index is %r" % my_val1.index ) 16 | print( "It's channel name is %r" % my_val1.name ) 17 | print( "It's owner is %r" % my_val1.owner.name ) 18 | 19 | elif my_val1 < my_val3: 20 | print( "my_val3 wins" ) 21 | print( "That value is " + str( my_val3 ) ) 22 | print( "It's index is %r" % my_val3.index ) 23 | print( "It's channel name is %r" % my_val3.name ) 24 | print( "It's owner is %r" % my_val3.owner.name ) 25 | 26 | # compare chan2 in CHOP1 and CHOP2 27 | if my_val2 == my_val4: 28 | print( "These values are equal" ) 29 | 30 | elif my_val2 > my_val4: 31 | print( "my_val2 wins" ) 32 | print( "That value is " + str( my_val2 ) ) 33 | print( "It's index is %r" % my_val2.index ) 34 | print( "It's channel name is %r" % my_val2.name ) 35 | print( "It's owner is %r" % my_val2.owner.name ) 36 | 37 | elif my_val2 < my_val4: 38 | print( "my_val4 wins" ) 39 | print( "That value is " + str( my_val4 ) ) 40 | print( "It's index is %r" % my_val4.index ) 41 | print( "It's channel name is %r" % my_val4.name ) 42 | print( "It's owner is %r" % my_val4.owner.name ) -------------------------------------------------------------------------------- /088/example_logic/text_comparing_different_CHOPs3.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | # this approach takes advantage of the channel class 3 | my_val1 = op( 'constant_CHOP1' ).chan( 0 ) 4 | my_val2 = op( 'constant_CHOP1' ).chan( 1 ) 5 | my_val3 = op( 'constant_CHOP2' ).chan( 0 ) 6 | my_val4 = op( 'constant_CHOP2' ).chan( 1 ) 7 | 8 | my_text = '''%r wins 9 | That value is %r 10 | It's index is %r 11 | It's channel name is %r 12 | It's owner is %r 13 | ''' 14 | 15 | # compare chan1 in CHOP1 and CHOP2 16 | if my_val1 == my_val3: 17 | print( "These values are equal" ) 18 | 19 | elif my_val1 > my_val3: 20 | print( my_text % ( 21 | 'my_val1' , 22 | str( my_val1 ) , 23 | my_val1.index , 24 | my_val1.name , 25 | my_val1.owner.name 26 | ) ) 27 | 28 | elif my_val1 < my_val3: 29 | print( my_text % ( 30 | 'my_val3' , 31 | str( my_val3 ) , 32 | my_val3.index , 33 | my_val3.name , 34 | my_val3.owner.name 35 | ) ) 36 | 37 | # compare chan2 in CHOP1 and CHOP2 38 | if my_val2 == my_val4: 39 | print( "These values are equal" ) 40 | 41 | elif my_val2 > my_val4: 42 | print( my_text % ( 43 | 'my_val2' , 44 | str( my_val2 ) , 45 | my_val2.index , 46 | my_val2.name , 47 | my_val2.owner.name 48 | ) ) 49 | 50 | elif my_val2 < my_val4: 51 | print( my_text % ( 52 | 'my_val4' , 53 | str( my_val4 ) , 54 | my_val4.index , 55 | my_val4.name , 56 | my_val4.owner.name 57 | ) ) -------------------------------------------------------------------------------- /088/example_logic/text_if_elif_with_unexpected_result.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_int1 = 6 3 | 4 | # a simple logical test 5 | if my_int1 >= 6: 6 | print( 'This number is great than or equal to 6' ) 7 | 8 | elif my_int1 == 6: 9 | print( 'this number is 6' ) 10 | 11 | else: 12 | print( 'This number is less than or equal to 6' ) 13 | 14 | # here we get an unexpected result... our first test check to see if 15 | # our integer is great than or equal to 6. Because this is true, 16 | # we print our first statement -------------------------------------------------------------------------------- /088/example_logic/text_if_with_references.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_int1 = op( 'constant1' )[ 'chan1' ] 3 | my_int2 = op( 'constant1' )[ 'chan2' ] 4 | 5 | if my_int1 == my_int2: 6 | print( "These values are equal!" ) 7 | 8 | elif my_int1 > my_int2: 9 | print( "Integer 1 is greater than integer 2" ) 10 | 11 | elif my_int1 < my_int2: 12 | print( "Integer 1 is less than integer 2" ) 13 | 14 | -------------------------------------------------------------------------------- /088/example_logic/text_nested_logical_structures.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_int1 = 6 3 | 4 | # a simple logical test 5 | if my_int1 >= 6: 6 | print( 'This number is great than or equal to 6' ) 7 | 8 | # another nested logical test 9 | if my_int1 %2 == 0: 10 | print( "and it's an even number!" ) 11 | 12 | else: 13 | print( 'This number is less than or equal to 6' ) -------------------------------------------------------------------------------- /088/example_logic/text_references_and_printing1.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_val1 = op( 'constant1' )[ 'chan1' ] 3 | my_val2 = op( 'constant1' )[ 'chan2' ] 4 | 5 | if my_val1 == my_val2: 6 | print( "These values are equal!" ) 7 | 8 | elif my_val1 > my_val2: 9 | print( "Integer 1 is greater than integer 2" ) 10 | 11 | elif my_val1 < my_val2: 12 | print( "Integer 1 is less than integer 2" ) 13 | 14 | 15 | text = '''Let's take a closer look... 16 | My Val 1 is %r 17 | My Val 2 is %r''' 18 | 19 | print( text % ( my_val1 , my_val2 ) ) -------------------------------------------------------------------------------- /088/example_logic/text_references_and_printing2.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_val1 = op( 'constant1' )[ 'chan1' ] 3 | my_val2 = op( 'constant1' )[ 'chan2' ] 4 | 5 | if my_val1 == my_val2: 6 | print( "These values are equal!" ) 7 | 8 | elif my_val1 > my_val2: 9 | print( "Integer 1 is greater than integer 2" ) 10 | 11 | elif my_val1 < my_val2: 12 | print( "Integer 1 is less than integer 2" ) 13 | 14 | 15 | text = '''Let's take a closer look... 16 | My Val 1 is %r 17 | My Val 2 is %r''' 18 | 19 | print( text % ( float( my_val1 ) , float( my_val2 ) ) ) -------------------------------------------------------------------------------- /088/example_logic/text_references_and_printing_rounding1.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_val1 = op( 'constant1' )[ 'chan1' ].vals 3 | my_val2 = op( 'constant1' )[ 'chan2' ].vals 4 | 5 | if my_val1 == my_val2: 6 | print( "These values are equal!" ) 7 | 8 | elif my_val1 > my_val2: 9 | print( "Integer 1 is greater than integer 2" ) 10 | 11 | elif my_val1 < my_val2: 12 | print( "Integer 1 is less than integer 2" ) 13 | 14 | 15 | text = '''Let's take a closer look... 16 | My Val 1 is %r 17 | My Val 2 is %r''' 18 | 19 | print( text % ( round( my_val1[ 0 ] , 2 ), round( my_val2[ 0 ] , 2 ) ) ) -------------------------------------------------------------------------------- /088/example_logic/text_references_and_printing_rounding2.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | decimal_places = 2 3 | my_val1 = op( 'constant1' )[ 'chan1' ].vals 4 | my_val2 = op( 'constant1' )[ 'chan2' ].vals 5 | 6 | if my_val1 == my_val2: 7 | print( "These values are equal!" ) 8 | 9 | elif my_val1 > my_val2: 10 | print( "Integer 1 is greater than integer 2" ) 11 | 12 | elif my_val1 < my_val2: 13 | print( "Integer 1 is less than integer 2" ) 14 | 15 | 16 | text = '''Let's take a closer look... 17 | Rounded to %d decimal places 18 | My Val 1 is %r 19 | My Val 2 is %r''' 20 | 21 | print( text % ( 22 | decimal_places , 23 | round( my_val1[ 0 ] , decimal_places ), 24 | round( my_val2[ 0 ] , decimal_places ) 25 | ) ) -------------------------------------------------------------------------------- /088/example_logic/text_revised_if_elif_test.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_int1 = 6 3 | 4 | # a simple logical test 5 | if my_int1 > 6: 6 | print( 'This number is great than or equal to 6' ) 7 | 8 | elif my_int1 == 6: 9 | print( 'this number is 6' ) 10 | 11 | else: 12 | print( 'This number is less than or equal to 6' ) -------------------------------------------------------------------------------- /088/example_logic/text_simple_logic_pass.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_int1 = 5 3 | 4 | # a simple logical test 5 | if my_int1 >= 6: 6 | print( 'This number is great than or equal to 6' ) 7 | 8 | else: 9 | pass -------------------------------------------------------------------------------- /088/example_logic/text_simple_logic_test.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_int1 = 5 3 | 4 | # a simple logical test 5 | if my_int1 >= 6: 6 | print( 'This number is great than or equal to 6' ) 7 | 8 | else: 9 | print( 'This number is less than or equal to 6' ) -------------------------------------------------------------------------------- /088/example_logic/text_the_channel_class.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | # this approach takes advantage of the channel class 3 | my_val1 = op( 'constant_CHOP1' ).chan( 0 ) 4 | 5 | print( "My value is " + str( my_val1 ) ) 6 | print( "My index is %r" % my_val1.index ) 7 | print( "My channel name is %r" % my_val1.name ) 8 | print( "My owner is %r" % my_val1.owner.name ) -------------------------------------------------------------------------------- /088/example_logic/text_two_variable_if_elif.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_int1 = 5 3 | my_int2 = 5 4 | 5 | if my_int1 == my_int2: 6 | print( "These values are equal!" ) 7 | 8 | elif my_int1 > my_int2: 9 | print( "Integer 1 is greater than integer 2" ) 10 | 11 | elif my_int1 < my_int2: 12 | print( "Integer 1 is less than integer 2" ) -------------------------------------------------------------------------------- /088/example_logic/text_using_and.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_int1 = 5 3 | 4 | if my_int1 > 4 and my_int1 < 6: 5 | print( "This number is greater than 4, but less than 6" ) 6 | 7 | else: 8 | print( "This number is less than 4 or greater than 6" ) 9 | -------------------------------------------------------------------------------- /088/example_logic/text_using_and_in_elif.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_int1 = 5 3 | 4 | if my_int1 == 5: 5 | print( "This number is 5" ) 6 | 7 | elif my_int1 > 4 and my_int1 < 6: 8 | print( "This number is greater than 4, but less than 6" ) 9 | 10 | else: 11 | print( "This number is less than 4 or greter than 6" ) 12 | -------------------------------------------------------------------------------- /088/example_logic/text_using_not.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_val = 10 3 | 4 | # using a symbol to test if something is 5 | # not equal 6 | if my_val != 8: 7 | print( "this isn't an 8" ) 8 | 9 | 10 | else: 11 | print( "this is an 8" ) -------------------------------------------------------------------------------- /088/example_logic/text_using_not_with_two_variables.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_val1 = 10 3 | my_val2 = 8 4 | 5 | # using a symbol to test if something is 6 | # not equal 7 | if my_val1 != my_val2: 8 | print( "this is not the number: %r" % my_val2 ) 9 | 10 | 11 | else: 12 | print( "this is an 8" ) -------------------------------------------------------------------------------- /088/example_logic/text_using_or.py: -------------------------------------------------------------------------------- 1 | # define our variables 2 | my_int1 = -5 3 | 4 | if my_int1 > 4 or my_int1 == -5: 5 | print( "This number is greater than 4 or it's -5" ) 6 | 7 | else: 8 | print( "This number is less than 4 or greter than 6" ) 9 | -------------------------------------------------------------------------------- /088/example_modules/example_modules.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_modules/example_modules.tox -------------------------------------------------------------------------------- /088/example_modules/text_calling_a_dictionary.py: -------------------------------------------------------------------------------- 1 | print( mod( 'text_dictionary_as_module' ).fruit ) -------------------------------------------------------------------------------- /088/example_modules/text_dictionary_as_module.py: -------------------------------------------------------------------------------- 1 | fruit = { 2 | "apple" : 10, 3 | "orange" : 5, 4 | "kiwi" : 16 5 | } -------------------------------------------------------------------------------- /088/example_modules/text_log_example.py: -------------------------------------------------------------------------------- 1 | operator = me 2 | 3 | message =''' 4 | Just a friendly message from your TouchDesigner Network. 5 | Anything could go here, an error message an init message. 6 | 7 | You dream it up, and it'll print 8 | ''' 9 | 10 | # print and append log file with a verbose log message 11 | mod( 'text_module1' ).Log_message( operator, message, verbose = True ) 12 | 13 | # print and append log file with a compact log message 14 | # mod( 'text_module1' ).Log_message( operator, message ) 15 | 16 | 17 | # append log file with verbose log message 18 | # mod( 'text_module1' ).Log_message( operator, message, verbose = True, text_port_print = False ) 19 | 20 | 21 | # print a compact log message 22 | # mod( 'text_module1' ).Log_message( operator, message, append_log = False ) 23 | -------------------------------------------------------------------------------- /088/example_modules/text_module1.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | log_file = op( 'text_log' ) 4 | 5 | full_text = '''{now} 6 | 7 | Current Year | {year} 8 | Current Month | {month} 9 | Current Day | {day} 10 | Current Hour | {hour} 11 | Current Minute | {minute} 12 | Current Second | {second} 13 | Current Microsecond | {microsecond} 14 | ''' 15 | 16 | verbose_log_message = '''============================ 17 | VERBOSE MESSAGE 18 | 19 | On {month}-{day}-{year} at {hour}:{minute}:{second} 20 | ---------------------------- 21 | operator || {operator} 22 | At Network Location || {path} 23 | 24 | ---------------------------- 25 | Logged 26 | {message} 27 | ============================ 28 | ''' 29 | 30 | log_message = '''---------------------------- 31 | {now} 32 | ---------------------------- 33 | {operator} 34 | {path} 35 | {message} 36 | ''' 37 | 38 | def Full_date(): 39 | '''Create a formatted time stamp 40 | 41 | A look at how we might create a formatted time stamp to use with 42 | various logging applications. 43 | 44 | Arguments 45 | --------------- 46 | None 47 | 48 | Returns 49 | --------------- 50 | formatted_text( str ) - a string time stamp 51 | 52 | Notes 53 | --------------- 54 | ''' 55 | 56 | now = datetime.datetime.now() 57 | year = datetime.datetime.now().year 58 | month = datetime.datetime.now().month 59 | day = datetime.datetime.now().day 60 | hour = datetime.datetime.now().hour 61 | minute = datetime.datetime.now().minute 62 | second = datetime.datetime.now().second 63 | microsecond = datetime.datetime.now().microsecond 64 | 65 | formatted_text = full_text.format( 66 | now = now, 67 | year = year, 68 | month = month, 69 | day = day, 70 | hour = hour, 71 | minute = minute, 72 | second = second, 73 | microsecond = microsecond 74 | ) 75 | return formatted_text 76 | 77 | def Log_message( operator, message, verbose=False, text_port_print=True, append_log=True ): 78 | '''Logging Method. 79 | 80 | A simple look at how you might start to think about building a logger for a TouchDesigner 81 | application. A logger is a great way to build out files with time stamped events. The 82 | more complex a project becomes, the more important it can become to have some means 83 | of logging the operations of your program. Here's a simple look at what that might 84 | look like. 85 | 86 | Arguments 87 | --------------- 88 | operator( touch object ) - the touch object whose path you'd like incldued in the log message 89 | message( str ) - a message to include in the log 90 | verbose( bool ) - a toggle for verbose or compact messages 91 | text_port_print( bool ) - a toggle to print to the text port, or not 92 | append_log( bool ) - a toggle to append to the log file , or not 93 | 94 | Returns 95 | --------------- 96 | None 97 | 98 | Notes 99 | --------------- 100 | You'll notice that some arguments receive default values. This is so you don't have 101 | to include them in the call. This means that by default the message will be compact, 102 | will print to the text port, and will append the log file. 103 | ''' 104 | 105 | now = datetime.datetime.now() 106 | year = datetime.datetime.now().year 107 | month = datetime.datetime.now().month 108 | day = datetime.datetime.now().day 109 | hour = datetime.datetime.now().hour 110 | minute = datetime.datetime.now().minute 111 | second = datetime.datetime.now().second 112 | microsecond = datetime.datetime.now().microsecond 113 | 114 | path = op( operator ).path 115 | op_name = op( operator ).name 116 | 117 | if verbose: 118 | message = verbose_log_message.format( 119 | month = month, 120 | day = day, 121 | year = year, 122 | hour = hour, 123 | minute = minute, 124 | second = second, 125 | operator = op_name, 126 | path = path, 127 | message = message 128 | ) 129 | else: 130 | message = log_message.format( 131 | now = now, 132 | operator = op_name, 133 | path = path, 134 | message = message 135 | ) 136 | 137 | if text_port_print: 138 | print( message ) 139 | 140 | else: 141 | pass 142 | 143 | if append_log: 144 | log_file.write( '\n' + message ) 145 | 146 | else: 147 | pass 148 | return -------------------------------------------------------------------------------- /088/example_modules/text_module_simple.py: -------------------------------------------------------------------------------- 1 | print( mod( 'text_module1' ).Full_date() ) -------------------------------------------------------------------------------- /088/example_modules/text_printing_doc_strings_the_easy_way.py: -------------------------------------------------------------------------------- 1 | # first let's clear the text port to make sure we're starting fresh 2 | clear() 3 | 4 | # rather than wasting our time writing all the code in the other example, 5 | # instead let's write a for loop to automate that process. 6 | # We'll start by first making a list of all of the methods we want to print 7 | # doc strings for 8 | methods = [ 9 | "multi_by_two", 10 | "logic_test", 11 | "logic_test_two" 12 | ] 13 | 14 | # next we'll make a smiple placeholder expression that we can 15 | # pass each method into so we can print it out easily 16 | doc_string_temp = "mod( 'text_simple_reutrn' ).{target_function}.__doc__" 17 | 18 | # finally we write a little for loop to go through all items in our list 19 | # and pretty print their doc strings to the text port 20 | for method in methods: 21 | print( "The Doc Strings for {} are:".format( method ) ) 22 | temp_doc = doc_string_temp.format( target_function = method ) 23 | print( eval( temp_doc ) ) 24 | print( "= " * 10 ) 25 | -------------------------------------------------------------------------------- /088/example_modules/text_printing_doc_strings_the_hard_way.py: -------------------------------------------------------------------------------- 1 | # first let's clear the text port to make sure we're starting fresh 2 | clear() 3 | 4 | # Here we're printing out the doc strings for multi_by_two 5 | print( "The Doc Strings for multi_by_two are:" ) 6 | print( '\n' ) 7 | print( mod( 'text_simple_reutrn' ).multi_by_two.__doc__ ) 8 | 9 | # Here we're printing out the doc strings for lotic_test 10 | print( "The Doc Strings for logic_test:" ) 11 | print( '\n' ) 12 | print( mod( 'text_simple_reutrn' ).logic_test.__doc__ ) 13 | 14 | # Here we're printing out the doc strings for logic_test_two 15 | print( "The Doc Strings for logic_test_two:" ) 16 | print( '\n' ) 17 | print( mod( 'text_simple_reutrn' ).logic_test_two.__doc__ ) -------------------------------------------------------------------------------- /088/example_modules/text_simple_reutrn.py: -------------------------------------------------------------------------------- 1 | def multi_by_two( value ): 2 | '''Multiplies input value by 2 3 | 4 | A simple example function to see how we can use modules on demand. 5 | This module takes a single arugment which is multplied by 2 and 6 | then returned from the function. 7 | 8 | Arguments 9 | --------------- 10 | value( int / float ) - numeric value to be multiplied by 2 11 | 12 | Returns 13 | --------------- 14 | new_val( int / float ) - value * 2 15 | 16 | Notes 17 | --------------- 18 | These are doc strings - they're a feature of the Python language 19 | and make documenting your code all easier. This format is based largly 20 | on Google's Python documentation format - though not exactly. It's 21 | generally good practice to document your work, leaving notes both for 22 | your future self, as well as for other programmers who might be using 23 | your code in the future. 24 | ''' 25 | new_val = value * 2 26 | 27 | return new_val 28 | 29 | def logic_test( even_or_odd ): 30 | '''Tests if input value is even or odd 31 | 32 | This is a simple little function to test if an integer is even or odd. 33 | 34 | Arguments 35 | --------------- 36 | even_or_odd( int ) - an integer to be tested as even or odd 37 | 38 | Returns 39 | --------------- 40 | test( str ) - string result of the even / odd test 41 | 42 | Notes 43 | --------------- 44 | These are doc strings - they're a feature of the Python language 45 | and make documenting your code all easier. This format is based largly 46 | on Google's Python documentation format - though not exactly. It's 47 | generally good practice to document your work, leaving notes both for 48 | your future self, as well as for other programmers who might be using 49 | your code in the future. 50 | ''' 51 | if even_or_odd % 2: 52 | test = "this value is odd" 53 | 54 | else: 55 | test = "this value is even" 56 | 57 | return test 58 | 59 | 60 | def logic_test_two( value ): 61 | '''Silly logit test example 62 | 63 | Another simple function, this one to see another example of a 64 | logic test working in a module on demand. 65 | 66 | Arguments 67 | --------------- 68 | value( int / float / str / bool ) - a value to be tested 69 | 70 | Returns 71 | --------------- 72 | test( str ) - a string indicating the status of the test 73 | 74 | Notes 75 | --------------- 76 | These are doc strings - they're a feature of the Python language 77 | and make documenting your code all easier. This format is based largly 78 | on Google's Python documentation format - though not exactly. It's 79 | generally good practice to document your work, leaving notes both for 80 | your future self, as well as for other programmers who might be using 81 | your code in the future. 82 | ''' 83 | if value == "TouchDesigner": 84 | test = "Nice work" 85 | 86 | else: 87 | test = "Try again" 88 | 89 | return test -------------------------------------------------------------------------------- /088/example_modules/text_variables.py: -------------------------------------------------------------------------------- 1 | width = 1280 2 | height = 720 3 | 4 | budget = 'small' -------------------------------------------------------------------------------- /088/example_modules/text_variables_in_modules.py: -------------------------------------------------------------------------------- 1 | print( mod( 'text_variables' ).width ) 2 | print( mod( 'text_variables' ).height ) 3 | print( mod( 'text_variables' ).budget ) -------------------------------------------------------------------------------- /088/example_op_class/example_op_class.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_op_class/example_op_class.tox -------------------------------------------------------------------------------- /088/example_op_class/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_op_class ## 8 | 9 | [The OP Class](http://www.derivative.ca/wiki088/index.php?title=OP_Class) 10 | 11 | Taking some time to really understand how you might take better advantage of classes when using Python in TouchDesigner is well worth the time and effort - even if frustrating and intimidating at first. Chances are, you've already used some class methods without even knowing it, and here we're going to take a quick opportunity to better understand them, and how you might use them. 12 | 13 | In the last example we looked at functions, and I mentioned that methods are also functions - in the same way that all squares are rectangles, but squares are a special kind of rectangle. Similarly, methods are a special kind of function. Special in that they belong to a class. In a highly simplified way, we might think of a class as a grouping a functions with a particular purpose. We can also use dot notation to access the members of a class. Let's look at a simple example. For a hot second we're going to depart from TouchDesigner and just talk about this problem as a programmer, then we'll return to how this works and looks in Touch. Let's imagine you want to put together a set of conversion tools. One approach would be to put all of your conversion functions together into one big class. If you're only dealing with a few hundred lines of code that might be fine, but over time you're likely going to need to keep updating this class, or you might find that it's thousands of lines long suddenly and a bit unruly to wrangle. You might instead choose to separate functions into different classes that are thematically related. We might, in this case, choose to write a Temperature Conversion Class, and a Measurement Conversion Class as separate collections of code. That might look like this: 14 | 15 | ```Python 16 | class TemperatureConversion(): 17 | 18 | def F_to_C( self, temp_in_F ): 19 | 20 | temp_in_C = ( temp_in_F - 32 ) * ( 5/9 ) 21 | 22 | return temp_in_C 23 | 24 | def C_to_F( self, temp_in_C ): 25 | 26 | temp_in_F = ( temp_in_C * ( 9/5 ) ) + 32 27 | 28 | return temp_in_F 29 | 30 | class MeasurementConversion(): 31 | 32 | def Inches_to_Centimeters( self, inches ): 33 | 34 | centimeters = inches * 2.54 35 | 36 | return centimeters 37 | 38 | def Centimeters_to_Inches( self centimeters ): 39 | 40 | inches = centimeters * 0.39 41 | 42 | return inches 43 | ``` 44 | 45 | So what's the benefit here? Now when calling one of these functions we can use dot notation in order to get the results. For example: 46 | 47 | ```Python 48 | print( TemperatureConversion.F_to_C( 50 ) ) 49 | print( TemperatureConversion.C_to_F( 100 ) ) 50 | print( MeasurementConversion.Inches_to_Centimeters( 12 ) ) 51 | print( MeasurementConversion.Centimeters_to_Inches( 1200 ) ) 52 | ``` 53 | 54 | Organizationally, here we can easily see how our different classes give us a quick way to separate functions. Whew. Alright, that's a lot of back-story in order to help us have a way to think about classes in TouchDesigner. We have to think / know about classes because that's a part of organizational structure that we're relying on when we use any dot notation for a method call. The Op Class applies to all operators in TouchDesigner, which means that the methods associated with it can be called in relation to any op - part of the reason we're working through what that means. 55 | 56 | Okay, let's look at some examples. If you haven't already looked at the wiki page about the [Op Class](http://www.derivative.ca/wiki088/index.php?title=OP_Class), you should do that now. 57 | 58 | In the next group of examples we're going to use the Eval DAT in order to see how we can evaluate expressions quickly and easily. I frequently use the Eval DAT for just this reason, so I can see which parts of my expressions are working and which parts aren't. Okay. Let's first look at digits: 59 | 60 | ```Python 61 | me.digits 62 | ``` 63 | 64 | Digits returns the integer number associated with an operator. In the example above we get the digits for the operator in question. In the example network it's returning the digits for the operator table1. 65 | 66 | Let's look at another use of digits: 67 | 68 | ```Python 69 | op( 'table2' ).digits 70 | ``` 71 | 72 | In this example we're asking for the digits for table2. Now, it might seem a little useless to ask for the digits of an operator you already know the digits for, but it's not hard to imagine a situation where this becomes very handy. This is especially useful when we use replicators. 73 | 74 | ```Python 75 | parent().digits 76 | ``` 77 | 78 | The above, for example, is a great way to get get the digits of a parent. When using a replicator you might use this approach to increment through the rows of a source table. 79 | 80 | Let's look at some variations on the way you might retrieve the name of an operator: 81 | 82 | ```Python 83 | parent().name 84 | me.parent().name 85 | op( '..' ).name 86 | op( '/python_in_touchdesigner/example_op_class' ).name 87 | ``` 88 | 89 | All of the above return the same result. parent() is a method that accepts an argument for relational distance. Let's say we wanted to get information from our grandparent component: 90 | 91 | ```Python 92 | parent(2).name 93 | me.parent(2).name 94 | op( '../..' ).name 95 | op( '/python_in_touchdesigner' ).name 96 | ``` 97 | 98 | Great, but what other kinds of methods can we use? Before the findOp DAT existed, you might use the findChildren() method to retrieve information about operators in a given component. In this case, I'm using a table generate rows for every operator in a component, and then using an Eval DAT to write one expression that's uniquely evaluated for each row: 99 | 100 | ```Python 101 | parent().children[ me.inputRow ] 102 | ``` 103 | 104 | Alright, one more time let's go back to the wiki article on the Op Class. This time we're going to take what we've learned about the Eval DAT, and what we've learned about classes to look at all of the methods we have access to for a text TOP. Let's write out an expression for each of the methods: 105 | 106 | ```Python 107 | 'valid' op( 'text2' ).valid 108 | 'id' op( 'text2' ).id 109 | 'name' op( 'text2' ).name 110 | 'path' op( 'text2' ).path 111 | 'digits' op( 'text2' ).digits 112 | 'base' op( 'text2' ).base 113 | 'passive' op( 'text2' ).passive 114 | 'time' op( 'text2' ).time 115 | 'activeViewer' op( 'text2' ).activeViewer 116 | 'allowCooking' op( 'text2' ).allowCooking 117 | 'bypass' op( 'text2' ).bypass 118 | 'cloneImmune' op( 'text2' ).cloneImmune 119 | 'current' op( 'text2' ).current 120 | 'display' op( 'text2' ).display 121 | 'expose' op( 'text2' ).expose 122 | 'lock' op( 'text2' ).lock 123 | 'selected' op( 'text2' ).selected 124 | 'render' op( 'text2' ).render 125 | 'viewer' op( 'text2' ).viewer 126 | 'nodeHeight' op( 'text2' ).nodeHeight 127 | 'nodeWidth' op( 'text2' ).nodeWidth 128 | 'nodeX' op( 'text2' ).nodeX 129 | 'nodeY' op( 'text2' ).nodeY 130 | 'nodeCenterX' op( 'text2' ).nodeCenterX 131 | 'nodeCenterY' op( 'text2' ).nodeCenterY 132 | 'inputs' op( 'text2' ).inputs 133 | 'outputs' op( 'text2' ).outputs 134 | 'type' op( 'text2' ).type 135 | 'subType' op( 'text2' ).subType 136 | 'label' op( 'text2' ).label 137 | 'family' op( 'text2' ).family 138 | 'isFilter' op( 'text2' ).isFilter 139 | 'minInputs' op( 'text2' ).minInputs 140 | 'maxInputs' op( 'text2' ).maxInputs 141 | 'isMultiInputs' op( 'text2' ).isMultiInputs 142 | 'visibleLevel' op( 'text2' ).visibleLevel 143 | 'isBase' op( 'text2' ).isBase 144 | 'isCHOP' op( 'text2' ).isCHOP 145 | 'isCOMP' op( 'text2' ).isCOMP 146 | 'isDAT' op( 'text2' ).isDAT 147 | 'isMAT' op( 'text2' ).isMAT 148 | 'isObject' op( 'text2' ).isObject 149 | 'isPanel' op( 'text2' ).isPanel 150 | 'isSOP' op( 'text2' ).isSOP 151 | 'isTOP' op( 'text2' ).isTOP 152 | ``` 153 | 154 | You'll notice that I've separated the name of the method from the expression with a tab. This way when we feed our Eval DAT we get two columns - one with the name of the method, and another with the returned value. 155 | 156 | You'll notice that some methods are marked as ( Read Only ). This means that we can see information in calling these methods, but we can't change anything about our Operator. Let's look at an example of how we can make a change to an operator. Color is something we can change for any operator. I'm going to add three text DATs to my network. One operator to act on, and two text DATs where I'm going to write a simple script. First let's change the color of our operator to red: 157 | 158 | ```Python 159 | target_op = op( 'text1' ) 160 | 161 | target_op.color = ( 1 , 0 , 0 ) 162 | ``` 163 | 164 | If we right click and run this script we'll see that we've changed the color of our operator! Wait, let's change it back: 165 | 166 | ```Python 167 | target_op = op( 'text1' ) 168 | 169 | target_op.color = ( 0.5450000166893005 , 0.5450000166893005 , 0.5450000166893005 ) 170 | ``` 171 | 172 | Perfect. This might seem like a silly example, but it brings to our attention how we might use various class methods to make changes to our networks. As a quick note, you might notice that I've written my scripts in two lines when I could have written them in one. Right? Why write: 173 | 174 | ```Python 175 | target_op = op( 'text1' ) 176 | 177 | target_op.color = ( 1 , 0 , 0 ) 178 | ``` 179 | 180 | When I could just write: 181 | 182 | ```Python 183 | op( 'text1' ).color = ( 1 , 0 , 0 ) 184 | ``` 185 | 186 | Part of the way that I work these days is to anticipate that I'm going to incorporate the pieces of a test script into a larger method or function. Separating the operator from the function call makes it much easier to begin thinking about how I might extend this simple script in the future. I could easily start to think of writing a function that looked like: 187 | 188 | ```Python 189 | def Make_ops_red( op_path ): 190 | target_op = op( op_path ) 191 | 192 | target_op.color = ( 1, 0, 0 ) 193 | return 194 | ``` 195 | 196 | Okay, let's look at one other interesting thing we might consider. What if we wanted to script the processing adding ops to a network? We can do just this with the copy method: 197 | 198 | ```Python 199 | # create a new variable called new_op 200 | # this is also a copy of the operator out 1 201 | new_op = parent().copy( op( 'moviefilein1' ) ) 202 | 203 | # since we've defind our new op with the variable 204 | # name new_op we can continue to use this name 205 | # our next step will be to give it a name 206 | new_op.name = 'moviefilein_new_op' 207 | 208 | # finally we're going to change the location of 209 | # our new operator. In this example we want it 210 | # created at a location in relation to our original 211 | # operator. We start by finding the original operator's 212 | # y position, and then subtract 200 213 | new_op.nodeY = op( 'moviefilein1' ).nodeY - 100 214 | ``` 215 | 216 | There are, of course, many more things you can do with the Op Class - my hope is that this helps you get a sense of where to start and pushes you to start experimenting a little more. -------------------------------------------------------------------------------- /088/example_op_class/text_change_op_color.py: -------------------------------------------------------------------------------- 1 | target_op = op( 'text1' ) 2 | 3 | target_op.color = ( 1 , 0 , 0 ) 4 | -------------------------------------------------------------------------------- /088/example_op_class/text_create_ops.py: -------------------------------------------------------------------------------- 1 | # create a new variable called new_op 2 | # this is also a copy of the operator out 1 3 | new_op = parent().copy( op( 'moviefilein1' ) ) 4 | 5 | # since we've defind our new op with the variable 6 | # name new_op we can continue to use this name 7 | # our next step will be to give it a name 8 | new_op.name = 'moviefilein_new_op' 9 | 10 | # finally we're going to change the location of 11 | # our new operator. In this example we want it 12 | # created at a location in relation to our original 13 | # operator. We start by finding the original operator's 14 | # y position, and then subtract 200 15 | new_op.nodeY = op( 'moviefilein1' ).nodeY - 100 -------------------------------------------------------------------------------- /088/example_op_class/text_create_ops_loop.txt: -------------------------------------------------------------------------------- 1 | # set some variables 2 | new_ops_list = [ 3 | 'text_newop1' , 4 | 'text_newop2' , 5 | 'text_newop3' 6 | ] 7 | 8 | node_distance = 200 9 | 10 | # create an enumerated list from the original 11 | new_ops_enumerate = list( enumerate( new_ops_list) ) 12 | 13 | # loop through list 14 | for item in new_ops_enumerate: 15 | # create and name op 16 | new_op = parent().create( textDAT , item[ 1 ] ) 17 | 18 | # set location of nodes ( x or y ) 19 | #new_op.nodeX = item[ 0 ] * node_distance 20 | new_op.nodeY = - ( item[ 0 ] * node_distance ) -------------------------------------------------------------------------------- /088/example_op_class/text_restore_op_color.py: -------------------------------------------------------------------------------- 1 | target_op = op( 'text1' ) 2 | 3 | target_op.color = ( 0.5450000166893005 , 0.5450000166893005 , 0.5450000166893005 ) 4 | -------------------------------------------------------------------------------- /088/example_print/example_print.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_print/example_print.tox -------------------------------------------------------------------------------- /088/example_print/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_print ## 8 | 9 | Printing out lines isn't especially interesting on the face of it. That being said, this is one of the most powerful places to get your bearings. I almost always start any python related task by printing out bits of pieces of what I'm up to. It lets me see into the otherwise invisible process of code execution. Many folks will swear by this or that debugger, but at the end of the day your best debugging tool is just printing out what's happening. Learning the ins and outs of printing will also transfer to a number of different bits and pieces along the way. We'll start here as a way to get a solid handle on a few basic elements. -------------------------------------------------------------------------------- /088/example_print/text_read_me_floats.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## Printing Strings ## 8 | 9 | A floating point number is one with values after the decimal point. Just like with integers and strings, this special class of numbers needs a categorical identity. 10 | 11 | In these examples we'll look at how we can print numbers, assign numbers to variables and print them, add numbers, and use numbers in strings. -------------------------------------------------------------------------------- /088/example_print/text_read_me_ints.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## Printing Integers ## 8 | 9 | An integer, in case your last math class was a long time ago, is a whole number. Just like with strings, we need a way to explicitly tell a machine that we're dealing with a whole number. 10 | 11 | In these examples we'll look at how we can print numbers, assign numbers to variables and print them, add numbers, and use numbers in strings. -------------------------------------------------------------------------------- /088/example_print/text_read_me_strings.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## Printing Strings ## 8 | 9 | A string is a series of characters that won't be evaluated as numbers. 10 | 11 | Why does that matter? Well, while we've often internalized a difference between different kinds of characters, we need a way of specifying that difference to a machine. We often can't assume that the machine intrinsically knows what a series of characters might be. Most of the time we might think of strings as words. That's not always the case, but as a rule of thumb we might think of them as "words." 12 | 13 | These examples look at various ways we might print strings, multiple lines of strings, how we can print strings stored as variables, and even substitute in words in strings when we print. -------------------------------------------------------------------------------- /088/example_print/text_read_me_strings_and_nubmers.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## Printing Strings ## 8 | 9 | A string is a series of characters that won't be evaluated as numbers. 10 | 11 | Why does that matter? Well, while we've often internalized a difference between different kinds of characters, we need a way of specifying that difference to a machine. We often can't assume that the machine intrinsically knows what a series of characters might be. Most of the time we might think of strings as words. That's not always the case, but as a rule of thumb we might think of them as "words." -------------------------------------------------------------------------------- /088/example_variables/example_references/example_references.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_variables/example_references/example_references.tox -------------------------------------------------------------------------------- /088/example_variables/example_references/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_references ## 8 | 9 | References are one of the engines of TouchDesgner. They're how we connect elements, and move information between operator families (and many other things as well.) I've written a good chunk already about understanding references - [check out understanding referencingg](http://matthewragan.com/2014/06/01/understanding-referencing-touchdesigner/), and [understanding referencing part 2](http://matthewragan.com/2014/06/27/understanding-referencing-part-ii-touchdesigner/) - so I'm not going to do my best not to revisit all of that same information. 10 | 11 | I do, however, want to look at referencing from the stand point of Python. The most important thing we might do is understand the anatomy of a python expression used in as a reference to another operator. Let's start with a simple example. Let's say I have an LFO CHOP (let's say that it's lfo1, and has a single channel, chan1) that's oscillating between -0.5 and 0.5. I want to use this changing number to alter the position of a circle. In the Center X parameter of the circle I can use the following expression: 12 | 13 | ```python 14 | op( 'lfo1' )[ 'chan1' ] 15 | ``` 16 | 17 | Here I've specified that I'm looking at an operator. I know this because I started my expression with: 18 | 19 | ```python 20 | op( ) 21 | ``` 22 | 23 | Inside of that I've placed the string name - remember that we now know it's a string as we can see that it's encased in quote marks - of the operator: 24 | 25 | ```python 26 | op( 'lfo1' ) 27 | ``` 28 | 29 | Next I've indicated which channel in that operator I want to use - [ 'chan1' ]: 30 | 31 | ```python 32 | op( 'lfo1' )[ 'chan1' ] 33 | ``` 34 | 35 | We'll notice that this name is inside of square brackets. In the case above, we referenced the channel we were interested in by using the name of the channel. We could also have used the expression: 36 | 37 | ```python 38 | op( 'lfo1' )[ 0 ] 39 | ``` 40 | 41 | In this case we've accessed the same channel, but by using it's index rather than it's name. We might imagine that our channels are like good little school children heading out to recess all in a line. These lovely kiddos both have a name, and an order that they occupy in the line. 42 | 43 | So why 0? 44 | 45 | In Python, and in fact in many programming languages, 0 is still used as a number that represents a place. 0 is the first item in a sequence. We're accustomed to thinking of numbers as quantities and not as distances or sequences. If we think of this in terms of distance rather than quantity it makes a little more sense. If we're 1 mile (or kilometer) away from our destination, we're not there yet... we still have one more mile (or kilometer) to travel. This takes a bit of getting used to, but the longer you work with any programming language the more this will make sense. 46 | 47 | Alright. This is great, so let's think about a table of information as well while we're here. 48 | 49 | **Sample Table** 50 | 51 | | |0 | 1 | 52 | |----:|------:|---------:| 53 | | 0| Color | Quantity | 54 | | 1| red | 10 | 55 | | 2| blue | 5 | 56 | 57 | Above we have a table that exposes the index values for the rows and columns. Here we can see that the word "Color" is in row 0, column 0. That will become important here in just one moment. Let's imagine that our table is called "marbles." We'd like to write a reference to the quantity of red marbles. We can access the contents of this cell several ways. Just like with Channel Operators, in DATs we can use the index values of our table, or we can use the header names. Let's look at what that means. 58 | 59 | Using the name of the row and column we could write the following reference: 60 | 61 | ```python 62 | op( 'marbles' )[ 'red' , 'Quantity' ] 63 | ``` 64 | 65 | We could also write this expression by using only the index values of the table: 66 | 67 | ```python 68 | op( 'marbles' )[ 1 , 1 ] 69 | ``` 70 | 71 | Finally, we can also mix and match these approaches and use either of these expressions for the same result: 72 | 73 | ```python 74 | op( 'marbles' )[ 1 , 'Quantity' ] 75 | op( 'marbles' )[ 'red' , 1 ] 76 | ``` 77 | -------------------------------------------------------------------------------- /088/example_variables/example_variables.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/example_variables/example_variables.tox -------------------------------------------------------------------------------- /088/example_variables/readme.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## example_variables ## 8 | 9 | There are lots of resources on the web that describe variables better than I might: 10 | * [Variable - Computer Science](https://en.wikipedia.org/wiki/Variable_(computer_science)) 11 | * [Python Programmingg](\https://en.wikibooks.org/wiki/Python_Programming/Variables_and_Strings) 12 | * [Python Basics](http://www.astro.ufl.edu/~warner/prog/python.html) 13 | 14 | The essential idea here, however, is that you have something that you want to reference by name. That something might be a sentence, it might be a quantity, it could be anything really. Again, it's more important for us in this moment to consider that our something (whatever it is) happens to be a piece of information that we want re-use. 15 | 16 | Let's look at a dead simple example, to help us get started. If variables aren't new to you feel free to skip ahead. 17 | 18 | Let's imagine you own a toy store. That toy store happens to sell marbles. That's great, good for you - you're a marvelous little capitalist. Now, let's imagine that you want to do an inventory of all of your marbles. You have several different varieties of marbles, and you'd like to be able to think of them as different, while also having a total count. In this situation we might keep track of your marbles by using some variables: 19 | 20 | ```python 21 | red_marbles = 10 22 | blue_marbles = 5 23 | green_cat_eyes = 6 24 | blue_cat_eyes = 12 25 | ``` 26 | 27 | Nice work. Now, we can print out each one of those, and get back our stored quantity. We could also do something like this: 28 | 29 | ```python 30 | total_marbles = red_marbles + blue_marbles + green_cat_eyes + blue_cat_eyes 31 | ``` 32 | 33 | Now we also know the total quantity of marbles. Super. Finally, we might want to see all of that. Let's look at what that might look like: 34 | 35 | ```python 36 | print( "Currently in your inventory you have:" ) 37 | print( "%d red marbles" % red_marbles ) 38 | print( "%d blue marbles" % blue_marbles ) 39 | print( "%d green cat eye marbles" % green_cat_eyes ) 40 | print( "%d blue cat eye marbles" % blue_cat_eyes ) 41 | print( "-" * 10 ) 42 | print( "That makes for %d total marbles" % total_marbles ) 43 | ``` 44 | 45 | That's great, and hopefully you're a careful shop keeper and you don't loose any of your marbles... it was a long set-up for that bad joke. 46 | 47 | What does this do for us here in TouchDesigner? When we're scripting in Touch it's often useful to be able to assign variables for all sorts of things. This especially useful when referencing operators. 48 | 49 | Let's quickly consider one example. We might, have a level TOP that we want to make changes to. Starting with a simple task, lets imagine we want to use a script to change the opacity of a level TOP to 0. We could easily write something like this to solve this need: 50 | 51 | ```python 52 | op( 'level1' ).par.opacity = 0 53 | ``` 54 | 55 | That's short and simple and gets the job done. Love it. Now, let's imagine a slightly more complicated world where I want to change lots of parameters for this operator. I want to change the invert, black level, brightness 1, gamma 1, contrast, and opacity. That's great. Let's write all of that out and see what we end up with: 56 | 57 | ```python 58 | op( 'level1' ).par.invert = 0.31 59 | op( 'level1' ).par.blacklevel = 0.27 60 | op( 'level1' ).par.brightness1 = 1.45 61 | op( 'level1' ).par.gamma1 = 0.5 62 | op( 'level1' ).par.contrast = 1.76 63 | op( 'level1' ).par.opacity = 0.782 64 | ``` 65 | 66 | That's not too bad, but we could make that a little less error prone if we were to simplify some of our script: 67 | 68 | ```python 69 | level = op( 'level1' ) 70 | 71 | level.par.invert = 0.31 72 | level.par.blacklevel = 0.27 73 | level.par.brightness1 = 1.45 74 | level.par.gamma1 = 0.5 75 | level.par.contrast = 1.76 76 | level.par.opacity = 0.782 77 | ``` 78 | 79 | That's pretty swanky, but let's imagine a situation where I've made a table full of presets that I want to be able to reference. Let's look at how we might tackle something like that: 80 | 81 | ```python 82 | # define our variables: 83 | presets = op( 'table_presets' ) 84 | level = op( 'level1' ) 85 | row_ref = 'preset1' 86 | 87 | # change some parameters 88 | level.par.invert = presets[ row_ref , 'invert' ] 89 | level.par.blacklevel = presets[ row_ref , 'blacklevel' ] 90 | level.par.brightness1 = presets[ row_ref , 'brightness1' ] 91 | level.par.gamma1 = presets[ row_ref , 'gamma1' ] 92 | level.par.contrast = presets[ row_ref , 'contrast' ] 93 | level.par.opacity = presets[ row_ref , 'opacity' ] 94 | ``` 95 | 96 | Okay... so what happened here? First we defined created a variable called "presets" that stands in for op( 'table_presets' ). We also made one called "level" and one called "row_ref". 97 | 98 | Next we wrote a generalized set of instructions to change some parameters using our variables. For the sake of seeing it all written out let's look write it out long-form: 99 | 100 | ```python 101 | op( 'level1' ).par.invert = op( 'table_presets' )[ 'preset1' , 'invert' ] 102 | op( 'level1' ).par.blacklevel = op( 'table_presets' )[ 'preset1' , 'blacklevel' ] 103 | op( 'level1' ).par.brightness1 = op( 'table_presets' )[ 'preset1' , 'brightness1' ] 104 | op( 'level1' ).par.gamma1 = op( 'table_presets' )[ 'preset1' , 'gamma1' ] 105 | op( 'level1' ).par.contrast = op( 'table_presets' )[ 'preset1' , 'contrast' ] 106 | op( 'level1' ).par.opacity = op( 'table_presets' )[ 'preset1' , 'opacity' ] 107 | ``` 108 | 109 | This works just the same... so why use variables. Well, in this case I used variables to keep my code a little more tidy. I also did this because it means I'm less likely to make an error if I'm using shorter names. Most importantly, we did this because we've now created a variable called row_ref. This means we can change how this script works, just by altering this single variable. Let's say that we have two different presets. It would be far less fun to write the same set of scripts all over again just to reference a different preset. Instead, we can just change our variable to indicate which preset to use. That means that by making this single change: 110 | 111 | ```python 112 | row_ref = 'preset2' 113 | ``` 114 | 115 | We've actually made this change: 116 | 117 | ```python 118 | op( 'level1' ).par.invert = op( 'table_presets' )[ 'preset2' , 'invert' ] 119 | op( 'level1' ).par.blacklevel = op( 'table_presets' )[ 'preset2' , 'blacklevel' ] 120 | op( 'level1' ).par.brightness1 = op( 'table_presets' )[ 'preset2' , 'brightness1' ] 121 | op( 'level1' ).par.gamma1 = op( 'table_presets' )[ 'preset2' , 'gamma1' ] 122 | op( 'level1' ).par.contrast = op( 'table_presets' )[ 'preset2' , 'contrast' ] 123 | op( 'level1' ).par.opacity = op( 'table_presets' )[ 'preset2' , 'opacity' ] 124 | ``` 125 | 126 | This is only the tip of the iceberg, but helps us see how useful using variables in Python can be. -------------------------------------------------------------------------------- /088/python_in_touchdesigner.toe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/088/python_in_touchdesigner.toe -------------------------------------------------------------------------------- /099/README.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## Overview ## 8 | 9 | Whoa nelly! Welcome to a set of examples helping you get started with Python 3 in TouchDesigner. With any luck you're either new to TouchDesigner, or Python. If, however, like a growing number of artists you happen to be new to both of those things this is for you. Don't get me wrong, this is likely to be helpful to lots of folks, but if you're new to programming and feeling a little intimidated by the text port and writing scripts this should help you feel a little better about the world. Examples here will run the gamut of the mundane to the interesting, so don't be surprised if you find yourself a little bored while you're noodling around these examples. That's okay, these examples are here for you to come back to and look through at your own pace. 10 | 11 | Many examples here come from the lessons I learned by going though the brilliant [Zed Shaw's ](http://zedshaw.com/) book - [Learn Python the Hard Way](http://learnpythonthehardway.org/book/). I can't recommend this book / ebook enough for those of you new to Python. Read it, re-read it, re-re-read it. It's definitely worth going through several times. 12 | 13 | Happy Programming 14 | 15 | ## Contents and Descriptions ## 16 | 17 | ### example_print ### 18 | 19 | *Overview* 20 | 21 | Printing out lines isn't especially interesting on the face of it. That being said, this is one of the most powerful places to get your bearings. I almost always start any python related task by printing out bits of pieces of what I'm up to. It lets me see into the otherwise invisible process of code execution. Many folks will swear by this or that debugger, but at the end of the day your best debugging tool is just printing out what's happening. Learning the ins and outs of printing will also transfer to a number of different bits and pieces along the way. We'll start here as a way to get a solid handle on a few basic elements. 22 | 23 | ### example_variables ### 24 | 25 | *Overview* 26 | 27 | There are lots of resources on the web that describe variables better than I might: 28 | * [Variable - Computer Science](https://en.wikipedia.org/wiki/Variable_(computer_science)) 29 | * [Python Programmingg](\https://en.wikibooks.org/wiki/Python_Programming/Variables_and_Strings) 30 | * [Python Basics](http://www.astro.ufl.edu/~warner/prog/python.html) 31 | 32 | The essential idea here, however, is that you have something that you want to reference by name. That something might be a sentence, it might be a quantity, it could be anything really. Again, it's more important for us in this moment to consider that our something (whatever it is) happens to be a piece of information that we want re-use. 33 | 34 | ### example_references ### 35 | 36 | *Overview* 37 | 38 | YAOWZA! 39 | 40 | I've got no content here... well truth be told, I haven't ported all of these exmaples to 099 yet. We'll get there, so hang tight! 41 | 42 | _documentation written in markdown_ -------------------------------------------------------------------------------- /099/base_inheritance_099.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/099/base_inheritance_099.tox -------------------------------------------------------------------------------- /099/base_the_channel_class.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raganmd/python_for_td/e94d285340485e5880d1193ee3f678d887a5a2b3/099/base_the_channel_class.tox -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python in TouchDesigner # 2 | 3 | ## Programmer / Artist ## 4 | 5 | **Matthew Ragan** | [ matthewragan.com](http://matthewragan.com) 6 | 7 | ## Overview ## 8 | 9 | Whoa nelly! Welcome to a set of examples helping you get started with Python 3 in TouchDesigner. With any luck you're either new to TouchDesigner, or Python. If, however, like a growing number of artists you happen to be new to both of those things this is for you. Don't get me wrong, this is likely to be helpful to lots of folks, but if you're new to programming and feeling a little intimidated by the text port and writing scripts this should help you feel a little better about the world. Examples here will run the gamut of the mundane to the interesting, so don't be surprised if you find yourself a little bored while you're noodling around these examples. That's okay, these examples are here for you to come back to and look through at your own pace. 10 | 11 | Many examples here come from the lessons I learned by going though the brilliant [Zed Shaw's ](http://zedshaw.com/) book - [Learn Python the Hard Way](http://learnpythonthehardway.org/book/). I can't recommend this book / ebook enough for those of you new to Python. Read it, re-read it, re-re-read it. It's definitely worth going through several times. 12 | 13 | Happy Programming 14 | 15 | As an additional quick note - these examples were oringally set up in 088. 099 has a few changes to the way python is being used, especially when it comes to executes. I'm slowly porting over these to 099, but for now you'll find that there's an 088 version of this project in the directory called 088. 16 | 17 | ## Contents and Descriptions ## 18 | 19 | ### example_print ### 20 | 21 | *Overview* 22 | 23 | Printing out lines isn't especially interesting on the face of it. That being said, this is one of the most powerful places to get your bearings. I almost always start any python related task by printing out bits of pieces of what I'm up to. It lets me see into the otherwise invisible process of code execution. Many folks will swear by this or that debugger, but at the end of the day your best debugging tool is just printing out what's happening. Learning the ins and outs of printing will also transfer to a number of different bits and pieces along the way. We'll start here as a way to get a solid handle on a few basic elements. 24 | 25 | ### example_variables ### 26 | 27 | *Overview* 28 | 29 | There are lots of resources on the web that describe variables better than I might: 30 | * [Variable - Computer Science](https://en.wikipedia.org/wiki/Variable_(computer_science)) 31 | * [Python Programmingg](\https://en.wikibooks.org/wiki/Python_Programming/Variables_and_Strings) 32 | * [Python Basics](http://www.astro.ufl.edu/~warner/prog/python.html) 33 | 34 | The essential idea here, however, is that you have something that you want to reference by name. That something might be a sentence, it might be a quantity, it could be anything really. Again, it's more important for us in this moment to consider that our something (whatever it is) happens to be a piece of information that we want re-use. 35 | 36 | ### example_references ### 37 | 38 | *Overview* 39 | 40 | References are one of the engines of TouchDesgner. They're how we connect elements, and move information between operator families (and many other things as well.) I've written a good chunk already about understanding references - [check out understanding referencingg](http://matthewragan.com/2014/06/01/understanding-referencing-touchdesigner/), and [understanding referencing part 2](http://matthewragan.com/2014/06/27/understanding-referencing-part-ii-touchdesigner/) - so I'm not going to do my best not to revisit all of that same information. 41 | 42 | I do, however, want to look at referencing from the stand point of Python. The most important thing we might do is understand the anatomy of a python expression used in as a reference to another operator. 43 | 44 | ### example_logic ### 45 | 46 | *Overview* 47 | 48 | Logical statements are profoundly helpful for us when we're trying to convert an idea from what we understand, into something that a machine can interpret and act upon. Keeping that in mind, we need ways to distill ideas to their most fundamental pieces. What on earth do I mean? Well, we might think about ideas like greater than '>' , less than '<' , equal to '==' , and not equal to '!='. Further, we need to consider how we indicate when something might happen. This this set of examples we're going to focus on 'if' and 'else', as well as 'if' and 'elif' statements. What does all of that mean, well lets dig in and find out. 49 | 50 | ### example_data_structures ### 51 | 52 | *Overview* 53 | 54 | Short description here 55 | 56 | ### example_executes ### 57 | 58 | *Overview* 59 | 60 | Short description here 61 | 62 | ### example_for_loop ### 63 | 64 | *Overview* 65 | 66 | Short description here 67 | 68 | ### example_dictionary_loops ### 69 | 70 | *Overview* 71 | 72 | Short description here 73 | 74 | ### example_functions ### 75 | 76 | *Overview* 77 | 78 | Short description here 79 | 80 | ### example_modules ### 81 | 82 | *Overview* 83 | 84 | Short description here 85 | 86 | _documentation written in markdown_ --------------------------------------------------------------------------------