As an addendum to my comment, here's the custom node template (in full so can test), showing a number of ways to set up update callbacks.
In your question, even if the update worked on the PointerProperty, self
in the update method uda
would be the object that has the updated property (from the update callback), not necessarily the instance of the class from which it is defined.
Bit of overkill, as the single "catch all" update method on the property group (update_prop
) would suffice IMHO.
import bpy
from bpy.types import NodeTree, Node, NodeSocket
from bpy.props import FloatVectorProperty
# Implementation of custom nodes from Python
def ogpropclass(update_fn):
def update_prop(prop):
def x(self, context):
print("Called update (%s)" % prop)
node = None
for n in self.id_data.nodes:
if n.originProperty == self:
print("Node", n.name)
node = n
node.update_fn(context)
update_fn(node, context)
break
print("Updated", prop, getattr(self, prop))
return x
class CSGOriginProperty(bpy.types.PropertyGroup):
origin = FloatVectorProperty(subtype="XYZ", default=(0.0, 0.0, 0.0), update=update_prop("origin"))
size = FloatVectorProperty(subtype="XYZ", default=(1.0, 1.0, 1.0), update=update_prop("size"))
def template_layout(self, layout):
row = layout.row(align=True)
row.label("Origin")
row.prop(self, "origin", text="")
row = layout.row(align=True)
row.label("Size")
row.prop(self, "size", text="")
bpy.utils.register_class(CSGOriginProperty)
return CSGOriginProperty
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
class MyCustomTree(NodeTree):
'''A custom node tree type that will show up in the node editor header'''
bl_idname = 'CustomTreeType'
bl_label = 'Custom Node Tree'
bl_icon = 'NODETREE'
# Custom socket type
class MyCustomSocket(NodeSocket):
# Description string
'''Custom node socket type'''
# Optional identifier string. If not explicitly defined, the python class name is used.
bl_idname = 'CustomSocketType'
# Label for nice name display
bl_label = 'Custom Node Socket'
# Enum items list
my_items = [
("DOWN", "Down", "Where your feet are"),
("UP", "Up", "Where your head should be"),
("LEFT", "Left", "Not right"),
("RIGHT", "Right", "Not left")
]
myEnumProperty = bpy.props.EnumProperty(name="Direction", description="Just an example", items=my_items, default='UP')
# Optional function for drawing the socket input value
def draw(self, context, layout, node, text):
if self.is_output or self.is_linked:
layout.label(text)
else:
layout.prop(self, "myEnumProperty", text=text)
# Socket color
def draw_color(self, context, node):
return (1.0, 0.4, 0.216, 0.5)
class MyCustomTreeNode:
@classmethod
def poll(cls, ntree):
return ntree.bl_idname == 'CustomTreeType'
class MyCustomNode(Node, MyCustomTreeNode):
'''A custom node'''
bl_idname = 'CustomNodeType'
bl_label = 'Custom Node'
bl_icon = 'SOUND'
myStringProperty = bpy.props.StringProperty()
myFloatProperty = bpy.props.FloatProperty(default=3.1415926)
def update_fn(self, context):
print("Called Node.update_fn", repr(self))
return None
originProperty = bpy.props.PointerProperty(type=ogpropclass(update_fn), name="OriginProperty")
def init(self, context):
self.inputs.new('CustomSocketType', "InPut")
self.outputs.new('NodeSocketColor', "OutPut")
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
print("Removing node ", self, ", Goodbye!")
# Additional buttons displayed on the node.
def draw_buttons(self, context, layout):
layout.label("Node settings")
self.originProperty.template_layout(layout)
# Detail buttons in the sidebar.
# If this function is not defined, the draw_buttons function is used instead
def draw_buttons_ext(self, context, layout):
layout.label("Node settings Ext")
self.originProperty.template_layout(layout)
# Optional: custom label
# Explicit user label overrides this, but here we can define a label dynamically
def draw_label(self):
return "I am a custom node"
import nodeitems_utils
from nodeitems_utils import NodeCategory, NodeItem
class MyNodeCategory(NodeCategory):
@classmethod
def poll(cls, context):
return context.space_data.tree_type == 'CustomTreeType'
# all categories in a list
node_categories = [
# identifier, label, items list
MyNodeCategory("SOMENODES", "Some Nodes", items=[
# our basic node
NodeItem("CustomNodeType"),
]),
MyNodeCategory("OTHERNODES", "Other Nodes", items=[
NodeItem("CustomNodeType", label="Node A", settings={
"myStringProperty": repr("Lorem ipsum dolor sit amet"),
"myFloatProperty": repr(1.0),
}),
NodeItem("CustomNodeType", label="Node B", settings={
"myStringProperty": repr("consectetur adipisicing elit"),
"myFloatProperty": repr(2.0),
}),
]),
]
def register():
bpy.utils.register_class(MyCustomTree)
bpy.utils.register_class(MyCustomSocket)
bpy.utils.register_class(MyCustomNode)
nodeitems_utils.register_node_categories("CUSTOM_NODES", node_categories)
def unregister():
nodeitems_utils.unregister_node_categories("CUSTOM_NODES")
bpy.utils.unregister_class(MyCustomTree)
bpy.utils.unregister_class(MyCustomSocket)
bpy.utils.unregister_class(MyCustomNode)
if __name__ == "__main__":
register()