6
$\begingroup$

Ok, this is quite a weird case.

TL;DR

  • Why (and how is it Pythonically possible that) Blender's nodes dont have the __dict__ attribute.

  • How to programatically add attributes to them without this attribute. (No, I am not going to use exec(), thank you very much. The incoming data is royally untrusted.)

First problem

I am trying to deserialize a bunch of nodes that I previously saved to JSON. I have one somewhat working solution to this, but it involves exec(). The basic idea is that I have an assignment written like this into JSON:

"a":b

Which should as the file is read become:

<reference to newly created node>.a = b

However I don't want to run the actual thing for security reasons, so what I actually want to do is this:

  1. Add the field a to the node
  2. Assign the field to whatever b is. (B is always a litteral value)

So, naively I tried this:

<reference to the node>.__dict__[a] = b

Python didn't like it:

'ShaderNodeMixRGB' object has no attribute '__dict__'

Yet when running dir() on the node, '__dict__' shows up. As the very first result, to be exact.

I find all this very puzzling: I didn't even know an object in Python can exist without a __dict__ attribute. Now I know the nodes are some kind of twisted wrapper around C and in some cases the very C is a wrapper around OSL. But still?

Nevertheless, I continued my search. Trying a static assignment on the node didn't work. (By this I mean <reference to the node>.foo = "bar". It seems to be blocked by __slots__. A simmilar assignment worked in the code with the exec(). And I honestly can't say why.

At this point I started thinking something was terribly wrong with my machine. I switched over to another, which had:

  • A different processor architechture
  • A different operating system
  • A different version of Blender (the first thing was with an unstable one)

but still it had the same problem. Thinking some external factor had changed, I tried the old code. And it worked.

So what is going on?

Second problem

One may set an attribute's attribute's value (<reference to node>.inputs["foobar"] = "baz" being the most common case) in the file. This works like a charm on the exec()-based version. However, I don't see how I could make this work withous exec().

$\endgroup$
  • $\begingroup$ brecht's advice to use setattr(node_reference, 'property_name', property_value) should be the non exec() equivalent. We use it in Sverchok to import / export node trees as .json ( see github.com/nortikin/sverchok/blob/master/utils/… ) $\endgroup$ – zeffii Oct 18 '15 at 7:13
  • $\begingroup$ For reference python objects can be created without a __dict__ - read about __slots__ $\endgroup$ – sambler Oct 19 '15 at 13:24
5
$\begingroup$

Blender data does not have a __dict__ because it doesn't persistently store a Python object for all data. That would be very memory inefficient.

Set value for built-in and user defined properties of the node type:

setattr(node, property_name, property_value)

Set node input socket value:

node.inputs[socket_name].default_value = socket_value

For properties for one specific node you can use the method mentioned in the other answer:

node[property_name] = property_value
$\endgroup$
  • $\begingroup$ Thank you. The honorable mention goes to TLousky, who did do a good job at trying to understand my deranged ramblings. $\endgroup$ – Mörkö Oct 18 '15 at 12:16
3
$\begingroup$

You can create and set a value for a new custom property in a slighly more intuitive way:

t = bpy.context.scene.node_tree
n = t.nodes[0]
n['a'] = 1.2

>>>n.keys()
['a']
>>>n['a']
1.2

No need to use the dict explicitly, the node behaves a bit like it has a built in dict anyway, which you can access with keys() and values(), or directly as shown above.

If you actually want to add an attribute to the node, you can do that by altering the node type. Here's an example specific to CompositorNodes:

bpy.types.CompositorNode.a = bpy.props.StringProperty()

>>>bpy.context.scene.node_tree.nodes[0].a
''

>>>bpy.context.scene.node_tree.nodes[0].a = "Hello World"

This will add an attribute "a" to all compositor nodes.

Of course you can find the relevant type for other nodes in bpy.types, and alter those as well.

$\endgroup$
  • $\begingroup$ This is very interesting. Thank you very much. But that doesn't solve the problem itself, sadly. What I want is to set an actual attribute. (If we look at your example, I would want print(n.a) to output 1.2. $\endgroup$ – Mörkö Oct 17 '15 at 16:04
  • $\begingroup$ My point is to serialize a setup and deserialize it. This requires me to procedurally set attributes. But thank you very much anyway. $\endgroup$ – Mörkö Oct 17 '15 at 16:05
  • $\begingroup$ Alrighty, that's possible too, see updated answer $\endgroup$ – TLousky Oct 17 '15 at 16:10
  • $\begingroup$ Still missed the mark by some but we are getting there. I don't want to add properties to multple nodes. Just to one at a time. When I say procedurally, I mean "no idea what property, node or value it will affect". At least no idea at write-time. Also, if it matters at all, I am working with material, not Compositor nodes. $\endgroup$ – Mörkö Oct 17 '15 at 16:15
  • $\begingroup$ And not neccessarily add. It could also be crushing an older value. $\endgroup$ – Mörkö Oct 17 '15 at 16:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.