Automatically update a class attribute

Hi all, been struggling with this all day, so any help appreciated.

I have a component with some custom parameters and an extension. Two of these parameters are Pan and Distance, which I’ve also made properties using the TDF.createProperty() call.

I want two other properties called X and Y, and I want these to update their values automatically, so that as I change my Pan and Distance parameters, the object converts them to cartesian coordinates and assigns those coordinates to X and Y.

Nothing I’ve tried so far has worked, whether I make them properties or not, whether I reference the Pan/Distance parameters directly, or as class properties, the class evaluates them once when the extension is initialized and then the value is static. I’ve tried using the tdu.Dependency call a bunch of different ways; I’ve tried various approaches to GetX(), SetX(), etc., and I just can’t get the behavior I want.

I’ve accomplished this in a class I wrote outside of Touch that can be run in the command prompt, and it works fine, so I’m really hoping someone has some insight into how to make this work properly in Touch.

@CorbinWhite can you post a little example of where you’ve gotten to? Here’s a simplified example that has an example of using both dependencies and non-dependent properties.

In both examples you should see that moving Mypar1 drives the other pars.

base_property_example.tox (686 Bytes)

@raganmd
Okay, this was super helpful! I think I have a much better idea of what that function under the property decorator is actually doing. I do have a couple questions about exactly what’s going on, though.

In the tox I uploaded, the only way to get the tx/ty parameters to update based on pan or distance, is to reference the pan/distance properties into the Constant CHOP “prop_cooker”. This is very confusing to me, but is this because the dependencies declared in the class require a reference to force it to cook?

I would also like this to work in reverse, so that changing Tx/Ty will update Pan/Distance, and there doesn’t seem to be a way to do this without causing a feedback loop. Is there some lovely Pythonic way to do this? Since I’m going to force the user to choose between a coordinate system with the Cartesian toggle parameter, I can stick with the if-statements in the property functions, but this looks really ugly and inefficient, especially considering that according to spec, the user should be able to have 96 simultaneous instances of this component.

property_problems.tox (3.6 KB)

I see a couple things happening here - a big piece of this puzzle is actually the network com to both send and receive OSC messages to update you pars. I don’ think you actually want to bind to the OSC channels, since that makes for a messy question of ownership.

Instead, I think you want the par (or the extension) to be the source of truth that you push out to clients, and then use incoming messages to update the par (or the extension). I think part of you missing ingredient here is a setter to go along with your property. The a setter for your property should let you do the operations you’re after without needing case statements - since you’ll set the values for your properties based on which par is updated / changed.

In plain python that looks like

class bar:
	def __init__(self):
		self._Myval = 0

	@property
	def Myval(self):
		return self._Myval
	
	@Myval.setter
	def Myval(self, val):
		self._Myval = val


my_object = bar()
print(my_object.Myval)

my_object.Myval = 12
print(my_object.Myval)

What you’d get out of that would look like:

In TouchDesigner we might use that idea like this in an extension:

class Foo:

	def __init__(self, myOp):
		self.MyOp = myOp
		self._MyPar1 = tdu.Dependency(0)
		self._MyPar2 = tdu.Dependency(0)

	@property
	def MyPar1(self):
		return self._MyPar1

	'''setter syntax looks like 
	@nameOfTheMethod.setter
	def nameOfTheMethod(self, val):
		self._yourPrivateMember = val

	in the case of a tdu.Depdency you update the .val property of the depdency.
	'''
	@MyPar1.setter
	def MyPar1(self, val):
		self._MyPar1.val = val
		self._MyPar2.val = val + .25

	@property
	def MyPar2(self):
		return self._MyPar2

	@MyPar2.setter
	def MyPar2(self, val):
		self._MyPar2.val = val
		self._MyPar1.val = val - .25

You can then use an execute to update the dependent member:

This means that the dependent private member is the actual holder of the value - which we then just update with setattr - which you could also do from a script or callback.

Here’s tox example:
setattr-example.tox (1.1 KB)

It’s cook related, but not the way you’re thinking exactly. Your Pan property is what triggers the updated evaluation of T1 and T2 - if you’re not using this property, rather, if an operator doesn’t need this property then TouchDesigner doesn’t evaluate it. Like how many ops don’t cook if you’re not looking at them, if you’re not using Pan to drive a part of your network, then Touch’s optimization doesn’t saves that block from execution.

I think using a setattr approach here should help solve this problem. There’s a lot more python to make this work, but I think that gets you what you need. I think there are also some other more TouchDesigner / node based techniques you could use as well if you want to lighten the python load on this one.

I was planning on changing the OSC I/O to be handled by the extension; one goal is to port all of this to a standalone Python library. I had also tried using setters, but it seems like I had a lot of the right pieces, I just wasn’t putting them together properly. It’s finally working, though, following your examples and recommendations.

When I sat down to program in this functionality, I thought it was going to take an hour, and instead I spent about 11 hours coding in circles :sweat_smile:, so I really cannot thank you enough for this!

1 Like

I know that feeling so deeply… programming is a continuously humbling. Every once and awhile this quote will pop up in a social feed or me, and it always hits too hard:

“Give a man a program, frustrate him for a day.
Teach a man to program, frustrate him for a lifetime.”
― Muhammad Waseem

Glad the examples helped you move forward :slight_smile:

Hi all, I’m sitting on a similar problem right now and have looked at the sample file. I can understand the code well. Only the part with

def __init__(self, myOp):
		self.MyOp = myOp

i do not understand.

myOp is not defined or assigned anywhere. So what is the purpose of this and why can’t it be omitted?

I would be very grateful for a short feedback!

Hi @theArduinoGuy, myOp is passed into the class via the expression in the parent COMP’s extension object parameter like so:
op("./Foo").module.Foo(me), where me refers to the component itself. This expression isn’t just a reference to the class you’re building, it actually instantiates an object. In pure Python, it looks something like this:

class Foo:
     def __init__(self, bar):
          self.bar = bar


myFoo = Foo(myBar)

This is not strictly necessary, it’s just a convenient way to get the parent COMP into the class so you can make calls to it.

Hope that helps.

Hey @CorbinWhite , yes that makes sense to me. Thanks a lot for the explanation!