123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- # ##### 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 #####
- # <pep8-80 compliant>
- import bpy
- import nodeitems_utils
- from bpy.types import (
- Operator,
- PropertyGroup,
- )
- from bpy.props import (
- BoolProperty,
- CollectionProperty,
- EnumProperty,
- IntProperty,
- StringProperty,
- )
- class NodeSetting(PropertyGroup):
- value: StringProperty(
- name="Value",
- description="Python expression to be evaluated "
- "as the initial node setting",
- default="",
- )
- # Base class for node 'Add' operators
- class NodeAddOperator:
- type: StringProperty(
- name="Node Type",
- description="Node type",
- )
- use_transform: BoolProperty(
- name="Use Transform",
- description="Start transform operator after inserting the node",
- default=False,
- )
- settings: CollectionProperty(
- name="Settings",
- description="Settings to be applied on the newly created node",
- type=NodeSetting,
- options={'SKIP_SAVE'},
- )
- @staticmethod
- def store_mouse_cursor(context, event):
- space = context.space_data
- tree = space.edit_tree
- # convert mouse position to the View2D for later node placement
- if context.region.type == 'WINDOW':
- # convert mouse position to the View2D for later node placement
- space.cursor_location_from_region(
- event.mouse_region_x, event.mouse_region_y)
- else:
- space.cursor_location = tree.view_center
- # XXX explicit node_type argument is usually not necessary,
- # but required to make search operator work:
- # add_search has to override the 'type' property
- # since it's hardcoded in bpy_operator_wrap.c ...
- def create_node(self, context, node_type=None):
- space = context.space_data
- tree = space.edit_tree
- if node_type is None:
- node_type = self.type
- # select only the new node
- for n in tree.nodes:
- n.select = False
- node = tree.nodes.new(type=node_type)
- for setting in self.settings:
- # XXX catch exceptions here?
- value = eval(setting.value)
- try:
- setattr(node, setting.name, value)
- except AttributeError as e:
- self.report(
- {'ERROR_INVALID_INPUT'},
- "Node has no attribute " + setting.name)
- print(str(e))
- # Continue despite invalid attribute
- 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 and not space.edit_tree.library)
- # Default execute simply adds a node
- def execute(self, context):
- if self.properties.is_property_set("type"):
- self.create_node(context)
- return {'FINISHED'}
- else:
- return {'CANCELLED'}
- # Default invoke stores the mouse position to place the node correctly
- # and optionally invokes the transform operator
- def invoke(self, context, event):
- self.store_mouse_cursor(context, event)
- result = self.execute(context)
- if self.use_transform and ('FINISHED' in result):
- # removes the node again if transform is canceled
- bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
- return result
- # 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"
- bl_options = {'REGISTER', 'UNDO'}
- # Add a node and link it to an existing socket
- class NODE_OT_add_and_link_node(NodeAddOperator, Operator):
- '''Add a node to the active tree and link to an existing socket'''
- bl_idname = "node.add_and_link_node"
- bl_label = "Add and Link Node"
- bl_options = {'REGISTER', 'UNDO'}
- link_socket_index: IntProperty(
- name="Link Socket Index",
- description="Index of the socket to link",
- )
- def execute(self, context):
- space = context.space_data
- ntree = space.edit_tree
- node = self.create_node(context)
- if not node:
- return {'CANCELLED'}
- to_socket = getattr(context, "link_to_socket", None)
- if to_socket:
- ntree.links.new(node.outputs[self.link_socket_index], to_socket)
- from_socket = getattr(context, "link_from_socket", None)
- if from_socket:
- ntree.links.new(from_socket, node.inputs[self.link_socket_index])
- return {'FINISHED'}
- 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"
- bl_options = {'REGISTER', 'UNDO'}
- bl_property = "node_item"
- _enum_item_hack = []
- # Create an enum list from node items
- def node_enum_items(self, context):
- enum_items = NODE_OT_add_search._enum_item_hack
- enum_items.clear()
- for index, item in enumerate(nodeitems_utils.node_items_iter(context)):
- if isinstance(item, nodeitems_utils.NodeItem):
- enum_items.append(
- (str(index),
- item.label,
- "",
- index,
- ))
- return enum_items
- # Look up the item based on index
- def find_node_item(self, context):
- node_item = int(self.node_item)
- for index, item in enumerate(nodeitems_utils.node_items_iter(context)):
- if index == node_item:
- return item
- return None
- node_item: EnumProperty(
- name="Node Type",
- description="Node type",
- items=node_enum_items,
- )
- def execute(self, context):
- item = self.find_node_item(context)
- # no need to keep
- self._enum_item_hack.clear()
- if item:
- # apply settings from the node item
- for setting in item.settings.items():
- ops = self.settings.add()
- ops.name = setting[0]
- ops.value = setting[1]
- self.create_node(context, item.nodetype)
- if self.use_transform:
- bpy.ops.node.translate_attach_remove_on_cancel(
- 'INVOKE_DEFAULT')
- return {'FINISHED'}
- else:
- return {'CANCELLED'}
- 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'}
- 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 and not space.edit_tree.library))
- 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'}
- 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'}
- classes = (
- NodeSetting,
- NODE_OT_add_and_link_node,
- NODE_OT_add_node,
- NODE_OT_add_search,
- NODE_OT_collapse_hide_unused_toggle,
- NODE_OT_tree_path_parent,
- )
|