# ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### # import bpy from bpy.types import Operator from bpy.props import BoolProperty, EnumProperty, StringProperty # Base class for node 'Add' operators class NodeAddOperator(): @staticmethod def store_mouse_cursor(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) def create_node(self, context, node_type): space = context.space_data tree = space.edit_tree # select only the new node for n in tree.nodes: n.select = False node = tree.nodes.new(type=node_type) node.select = True tree.nodes.active = node node.location = space.cursor_location return 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) # Default invoke stores the mouse position to place the node correctly def invoke(self, context, event): self.store_mouse_cursor(context, event) return self.execute(context) # Simple basic operator for adding a node class NODE_OT_add_node(NodeAddOperator, Operator): '''Add a node to the active tree''' bl_idname = "node.add_node" bl_label = "Add Node" type = StringProperty( name="Node Type", description="Node type", ) # optional group tree parameter for group nodes group_tree = StringProperty( name="Group tree", description="Group node tree name", ) use_transform = BoolProperty( name="Use Transform", description="Start transform operator after inserting the node", default=False, ) def execute(self, context): node = self.create_node(context, self.type) # set the node group tree of a group node if self.properties.is_property_set('group_tree'): node.node_tree = bpy.data.node_groups[self.group_tree] return {'FINISHED'} def invoke(self, context, event): self.store_mouse_cursor(context, event) result = self.execute(context) if self.use_transform and ('FINISHED' in result): return bpy.ops.transform.translate('INVOKE_DEFAULT') else: 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] # Returns the enum item list for the edited tree in the context def node_type_items_cb(self, context): snode = context.space_data if not snode: 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 () class NODE_OT_add_search(Operator): '''Add a node to the active tree''' bl_idname = "node.add_search" bl_label = "Search and Add Node" bl_options = {'REGISTER', 'UNDO'} # XXX this should be called 'node_type' but the operator search # property is hardcoded to 'type' by a hack in bpy_operator_wrap.c ... type = EnumProperty( name="Node Type", description="Node type", items=node_type_items_cb, ) _node_type_items_dict = None def create_node(self, context): space = context.space_data tree = space.edit_tree # 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 @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) def execute(self, context): self.create_node(context) 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) context.window_manager.invoke_search_popup(self) return {'CANCELLED'} class NODE_OT_collapse_hide_unused_toggle(Operator): '''Toggle collapsed nodes and hide unused sockets''' bl_idname = "node.collapse_hide_unused_toggle" bl_label = "Collapse and Hide Unused Sockets" 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 space.edit_tree) def execute(self, context): space = context.space_data tree = space.edit_tree for node in tree.nodes: if node.select: hide = (not node.hide) node.hide = hide # Note: connected sockets are ignored internally for socket in node.inputs: socket.hide = hide for socket in node.outputs: socket.hide = hide return {'FINISHED'}