Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Toenne <lukas.toenne@googlemail.com>2013-03-18 20:34:57 +0400
committerLukas Toenne <lukas.toenne@googlemail.com>2013-03-18 20:34:57 +0400
commit4638e5f99a9ba59ad0b8a1fd52b12e876480b9e8 (patch)
tree2444f12b4612440f44cf02835cdf5951b6564e92 /release/scripts/startup
parent7bfef29f2f2a1b262d28abdc6e30fcd9c1f1caad (diff)
Merge of the PyNodes branch (aka "custom nodes") into trunk.
PyNodes opens up the node system in Blender to scripters and adds a number of UI-level improvements. === Dynamic node type registration === Node types can now be added at runtime, using the RNA registration mechanism from python. This enables addons such as render engines to create a complete user interface with nodes. Examples of how such nodes can be defined can be found in my personal wiki docs atm [1] and as a script template in release/scripts/templates_py/custom_nodes.py [2]. === Node group improvements === Each node editor now has a tree history of edited node groups, which allows opening and editing nested node groups. The node editor also supports pinning now, so that different spaces can be used to edit different node groups simultaneously. For more ramblings and rationale see (really old) blog post on code.blender.org [3]. The interface of node groups has been overhauled. Sockets of a node group are no longer displayed in columns on either side, but instead special input/output nodes are used to mirror group sockets inside a node tree. This solves the problem of long node lines in groups and allows more adaptable node layout. Internal sockets can be exposed from a group by either connecting to the extension sockets in input/output nodes (shown as empty circle) or by adding sockets from the node property bar in the "Interface" panel. Further details such as the socket name can also be changed there. [1] http://wiki.blender.org/index.php/User:Phonybone/Python_Nodes [2] http://projects.blender.org/scm/viewvc.php/trunk/blender/release/scripts/templates_py/custom_nodes.py?view=markup&root=bf-blender [3] http://code.blender.org/index.php/2012/01/improving-node-group-interface-editing/
Diffstat (limited to 'release/scripts/startup')
-rw-r--r--release/scripts/startup/bl_operators/node.py194
-rw-r--r--release/scripts/startup/bl_ui/space_node.py43
2 files changed, 131 insertions, 106 deletions
diff --git a/release/scripts/startup/bl_operators/node.py b/release/scripts/startup/bl_operators/node.py
index bc0224db765..9839e0ee092 100644
--- a/release/scripts/startup/bl_operators/node.py
+++ b/release/scripts/startup/bl_operators/node.py
@@ -100,75 +100,55 @@ class NODE_OT_add_node(NodeAddOperator, Operator):
return result
-# XXX These node item lists should actually be generated by a callback at
-# operator execution time (see node_type_items below),
-# using the active node tree from the context.
-# Due to a difficult bug in bpy this is not possible
-# (item list memory gets freed too early),
-# so for now just copy the static item lists to these global variables.
-#
-# In the custom_nodes branch, the static per-tree-type node items are replaced
-# by a single independent type list anyway (with a poll function to limit node
-# types to the respective trees). So this workaround is only temporary.
-
-# lazy init
-node_type_items_dict = {}
-
-# Prefixes used to distinguish base node types and node groups
-node_type_prefix = 'NODE_'
-node_group_prefix = 'GROUP_'
-
-
-# Generate a list of enum items for a given node class
-# Copy existing type enum, adding a prefix to distinguish from node groups
-# Skip the base node group type,
-# node groups will be added below for all existing group trees
-def node_type_items(node_class):
- return [(node_type_prefix + item.identifier, item.name, item.description)
- for item in node_class.bl_rna.properties['type'].enum_items
- if item.identifier != 'GROUP']
-
-
-# Generate items for node group types
-# Filter by the given tree_type
-# Node group trees don't have a description property yet
-# (could add this as a custom property though)
-def node_group_items(tree_type):
- return [(node_group_prefix + group.name, group.name, '')
- for group in bpy.data.node_groups if group.type == tree_type]
+def node_classes_iter(base=bpy.types.Node):
+ """
+ Yields all true node classes by checking for the is_registered_node_type classmethod.
+ Node types can use specialized subtypes of bpy.types.Node, which are not usable
+ nodes themselves (e.g. CompositorNode).
+ """
+ if base.is_registered_node_type():
+ yield base
+ for subclass in base.__subclasses__():
+ for node_class in node_classes_iter(subclass):
+ yield node_class
+
+
+def node_class_items_iter(node_class, context):
+ identifier = node_class.bl_rna.identifier
+ # XXX Checking for explicit group node types is stupid.
+ # This should be replaced by a generic system of generating
+ # node items via callback.
+ # Group node_tree pointer should also use a poll function to filter the library list,
+ # but cannot do that without a node instance here. A node callback could just use the internal poll function.
+ if identifier in {'ShaderNodeGroup', 'CompositorNodeGroup', 'TextureNodeGroup'}:
+ tree_idname = context.space_data.edit_tree.bl_idname
+ for group in bpy.data.node_groups:
+ if group.bl_idname == tree_idname:
+ yield (group.name, "", {"node_tree":group}) # XXX empty string should be replaced by description from tree
+ else:
+ yield (node_class.bl_rna.name, node_class.bl_rna.description, {})
-# Returns the enum item list for the edited tree in the context
-def node_type_items_cb(self, context):
+def node_items_iter(context):
snode = context.space_data
if not snode:
- return ()
+ return
tree = snode.edit_tree
if not tree:
- return ()
-
- # Lists of basic node types for each
- if not node_type_items_dict:
- node_type_items_dict.update({
- 'SHADER': node_type_items(bpy.types.ShaderNode),
- 'COMPOSITING': node_type_items(bpy.types.CompositorNode),
- 'TEXTURE': node_type_items(bpy.types.TextureNode),
- })
-
- # XXX Does not work correctly, see comment above
- '''
- return [(item.identifier, item.name, item.description, item.value)
- for item in
- tree.nodes.bl_rna.functions['new'].parameters['type'].enum_items]
- '''
-
- if tree.type in node_type_items_dict:
- return node_type_items_dict[tree.type] + node_group_items(tree.type)
- else:
- return ()
+ return
+
+ for node_class in node_classes_iter():
+ if node_class.poll(tree):
+ for item in node_class_items_iter(node_class, context):
+ yield (node_class,) + item
+
+
+# Create an enum list from node class items
+def node_type_items_cb(self, context):
+ return [(str(index), item[1], item[2]) for index, item in enumerate(node_items_iter(context))]
-class NODE_OT_add_search(Operator):
+class NODE_OT_add_search(NodeAddOperator, Operator):
'''Add a node to the active tree'''
bl_idname = "node.add_search"
bl_label = "Search and Add Node"
@@ -182,55 +162,48 @@ class NODE_OT_add_search(Operator):
items=node_type_items_cb,
)
- _node_type_items_dict = None
+ def execute(self, context):
+ for index, item in enumerate(node_items_iter(context)):
+ if str(index) == self.type:
+ node = self.create_node(context, item[0].bl_rna.identifier)
+ for prop,value in item[3].items():
+ setattr(node, prop, value)
+ break
+ return {'FINISHED'}
- def create_node(self, context):
- space = context.space_data
- tree = space.edit_tree
+ def invoke(self, context, event):
+ self.store_mouse_cursor(context, event)
+ # Delayed execution in the search popup
+ context.window_manager.invoke_search_popup(self)
+ return {'CANCELLED'}
- # Enum item identifier has an additional prefix to
- # distinguish base node types from node groups
- item = self.type
- if item.startswith(node_type_prefix):
- # item means base node type
- node = tree.nodes.new(type=item[len(node_type_prefix):])
- elif item.startswith(node_group_prefix):
- # item means node group type
- node = tree.nodes.new(
- type='GROUP',
- group=bpy.data.node_groups[item[len(node_group_prefix):]])
- else:
- return None
- for n in tree.nodes:
- if n == node:
- node.select = True
- tree.nodes.active = node
- else:
- node.select = False
- node.location = space.cursor_location
- return node
+# Simple basic operator for adding a node without further initialization
+class NODE_OT_add_node(NodeAddOperator, bpy.types.Operator):
+ '''Add a node to the active tree'''
+ bl_idname = "node.add_node"
+ bl_label = "Add Node"
- @classmethod
- def poll(cls, context):
- space = context.space_data
- # needs active node editor and a tree to add nodes to
- return (space.type == 'NODE_EDITOR' and space.edit_tree)
+ type = StringProperty(name="Node Type", description="Node type")
def execute(self, context):
- self.create_node(context)
+ node = self.create_node(context, self.type)
return {'FINISHED'}
- def invoke(self, context, event):
- space = context.space_data
- v2d = context.region.view2d
- # convert mouse position to the View2D for later node placement
- space.cursor_location = v2d.region_to_view(event.mouse_region_x,
- event.mouse_region_y)
+class NODE_OT_add_group_node(NodeAddOperator, bpy.types.Operator):
+ '''Add a group node to the active tree'''
+ bl_idname = "node.add_group_node"
+ bl_label = "Add Group Node"
- context.window_manager.invoke_search_popup(self)
- return {'CANCELLED'}
+ type = StringProperty(name="Node Type", description="Node type")
+ grouptree = StringProperty(name="Group tree", description="Group node tree name")
+
+ def execute(self, context):
+ node = self.create_node(context, self.type)
+ node.node_tree = bpy.data.node_groups[self.grouptree]
+
+ return {'FINISHED'}
class NODE_OT_collapse_hide_unused_toggle(Operator):
@@ -261,3 +234,24 @@ class NODE_OT_collapse_hide_unused_toggle(Operator):
socket.hide = hide
return {'FINISHED'}
+
+
+class NODE_OT_tree_path_parent(Operator):
+ '''Go to parent node tree'''
+ bl_idname = "node.tree_path_parent"
+ bl_label = "Parent Node Tree"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ space = context.space_data
+ # needs active node editor and a tree
+ return (space.type == 'NODE_EDITOR' and len(space.path) > 1)
+
+ def execute(self, context):
+ space = context.space_data
+
+ space.path.pop()
+
+ return {'FINISHED'}
+
diff --git a/release/scripts/startup/bl_ui/space_node.py b/release/scripts/startup/bl_ui/space_node.py
index 1865b049a03..e739c5ea5d4 100644
--- a/release/scripts/startup/bl_ui/space_node.py
+++ b/release/scripts/startup/bl_ui/space_node.py
@@ -44,8 +44,8 @@ class NODE_HT_header(Header):
row.menu("NODE_MT_node")
layout.prop(snode, "tree_type", text="", expand=True)
-
- if snode.tree_type == 'SHADER':
+
+ if snode.tree_type == 'ShaderNodeTree':
if scene.render.use_shading_nodes:
layout.prop(snode, "shader_type", text="", expand=True)
@@ -65,7 +65,7 @@ class NODE_HT_header(Header):
if snode_id:
layout.prop(snode_id, "use_nodes")
- elif snode.tree_type == 'TEXTURE':
+ elif snode.tree_type == 'TextureNodeTree':
layout.prop(snode, "texture_type", text="", expand=True)
if id_from:
@@ -76,7 +76,7 @@ class NODE_HT_header(Header):
if snode_id:
layout.prop(snode_id, "use_nodes")
- elif snode.tree_type == 'COMPOSITING':
+ elif snode.tree_type == 'CompositorNodeTree':
layout.prop(snode_id, "use_nodes")
layout.prop(snode_id.render, "use_free_unused_nodes", text="Free Unused")
layout.prop(snode, "show_backdrop")
@@ -84,6 +84,13 @@ class NODE_HT_header(Header):
row = layout.row(align=True)
row.prop(snode, "backdrop_channels", text="", expand=True)
layout.prop(snode, "use_auto_render")
+
+ else:
+ # Custom node tree is edited as independent ID block
+ layout.template_ID(snode, "node_tree", new="node.new_node_tree")
+
+ layout.prop(snode, "pin", text="")
+ layout.operator("node.tree_path_parent", text="", icon='FILE_PARENT')
layout.separator()
@@ -182,6 +189,7 @@ class NODE_MT_node(Menu):
layout.operator("node.group_edit")
layout.operator("node.group_ungroup")
layout.operator("node.group_make")
+ layout.operator("node.group_insert")
layout.separator()
@@ -208,7 +216,7 @@ class NODE_PT_properties(Panel):
@classmethod
def poll(cls, context):
snode = context.space_data
- return snode.tree_type == 'COMPOSITING'
+ return snode.tree_type == 'CompositorNodeTree'
def draw_header(self, context):
snode = context.space_data
@@ -237,7 +245,7 @@ class NODE_PT_quality(bpy.types.Panel):
@classmethod
def poll(cls, context):
snode = context.space_data
- return snode.tree_type == 'COMPOSITING' and snode.node_tree is not None
+ return snode.tree_type == 'CompositorNodeTree' and snode.node_tree is not None
def draw(self, context):
layout = self.layout
@@ -276,5 +284,28 @@ class NODE_MT_node_color_specials(Menu):
layout.operator("node.node_copy_color", icon='COPY_ID')
+class NODE_UL_interface_sockets(bpy.types.UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+ socket = item
+ color = socket.draw_color(context)
+
+ if self.layout_type in {'DEFAULT', 'COMPACT'}:
+ row = layout.row(align=True)
+
+ # inputs get icon on the left
+ if socket.in_out == 'IN':
+ row.template_node_socket(color)
+
+ row.label(text=socket.name, icon_value=icon)
+
+ # outputs get icon on the right
+ if socket.in_out == 'OUT':
+ row.template_node_socket(color)
+
+ elif self.layout_type in {'GRID'}:
+ layout.alignment = 'CENTER'
+ layout.template_node_socket(color)
+
+
if __name__ == "__main__": # only for live edit.
bpy.utils.register_module(__name__)