Example using custom node template.

Test code, put together with the custom nodes template for 2.8. Recommend that scripting now in 2.8 will save time going forward
Using Set up methods in the property group have a method to draw, and set and get from another object that shares the same pointer.
The draw method simply iterates and draws a basic prop layout for each property in way of example. I've added a list of chosen props. Begs for a nicely formatted method.
The operator requires context.node
this has been added to the context at where the operator is displayed in custom node UI, the context member is set to the node, which is self
in the node code. For the N panel draw, the context active node is used instead.
def draw_buttons(self, context, layout):
box = layout.box()
box.context_pointer_set("node", self)
op = box.operator("node.configure") # XXX
The config properties are drawn using. box
is any layout element defined, could be self.layout
or self.layout.column()
etc.
self.config.draw(box, context)
Note could flag to use self.node
instead of self above would display the node values directly. Contemplating using this but inverting the operator to revert if cancelled, and make props live so one can dynamically dick around and only save when happy.
The node values are loaded on invoke using CustomNodeConfig.getfrom
and set on execute with CustomNodeConfig.setfrom
Note It appears the config pointer will not register on the operator if it has an ID type pointer as a member.
from nodeitems_utils import NodeCategory, NodeItem
import nodeitems_utils
import bpy
from bpy.types import NodeTree, Node, NodeSocket
class CustomNodeConfig(bpy.types.PropertyGroup):
__props = ["x", "my_enum_prop"]
x: bpy.props.FloatProperty(name="X factor")
my_items = (
('DOWN', "Down", "Where your feet are"),
('UP', "Up", "Where your head should be"),
('LEFT', "Left", "Not right"),
('RIGHT', "Right", "Not left"),
)
my_enum_prop: bpy.props.EnumProperty(
name="Direction",
description="Just an example",
items=my_items,
default='UP',
)
def getfrom(self, node):
for p in node.__props:
setattr(self, p, getattr(node, p))
def setfrom(self, node):
for p in self.__props:
setattr(node, p, getattr(self, p))
def draw(self, layout, context):
layout.label(text="Settings...")
for p in self.__props:
layout.prop(self, p)
class NODE_OT_configure(bpy.types.Operator):
bl_label = "Configure"
bl_idname = "node.configure"
node = None
config: bpy.props.PointerProperty(type=CustomNodeConfig)
def invoke(self, context, event):
self.node = context.node
if self.node:
self.config.getfrom(self.node.config)
return context.window_manager.invoke_props_dialog(self)
def execute(self, context):
if self.node:
self.config.setfrom(self.node.config)
# Copy config -> node_ref.conf
return {'FINISHED'}
def draw(self, context):
if self.node:
self.layout.label(text="%s" % (repr(self.node.id_data)))
self.layout.label(text="%s" % (self.node.name))
self.config.draw(self.layout, context)
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'
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'
my_string_prop: bpy.props.StringProperty()
my_float_prop: bpy.props.FloatProperty(default=3.1415926)
config: bpy.props.PointerProperty(type=CustomNodeConfig)
def init(self, context):
self.inputs.new('NodeSocketFloat', "World")
self.outputs.new('NodeSocketFloat', "How")
def copy(self, node):
print("Copying from node ", node)
def free(self):
print("Removing node ", self, ", Goodbye!")
def draw_buttons(self, context, layout):
box = layout.box()
box.context_pointer_set("node", self)
op = box.operator("node.configure") # XXX
self.config.draw(box, context)
layout.label(text="Node settings")
layout.prop(self, "my_float_prop")
def draw_buttons_ext(self, context, layout):
box = layout.box()
box.context_pointer_set("node", context.active_node)
op = box.operator("node.configure")
self.config.draw(box, context)
layout.prop(self, "my_float_prop")
layout.prop(self, "my_string_prop")
def draw_label(self):
return "I am a custom node"
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"),
]),
]
classes = (
CustomNodeConfig,
NODE_OT_configure,
MyCustomTree,
MyCustomNode,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
nodeitems_utils.register_node_categories('CUSTOM_NODES', node_categories)
def unregister():
nodeitems_utils.unregister_node_categories('CUSTOM_NODES')
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
if __name__ == "__main__":
register()