Merging r59113 through r59129 from trunk into soc-2013-depsgraph_mt
[blender-staging.git] / release / scripts / startup / bl_operators / node.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8-80 compliant>
20
21 import bpy
22 import nodeitems_utils
23 from bpy.types import (Operator,
24                        PropertyGroup,
25                        )
26 from bpy.props import (BoolProperty,
27                        CollectionProperty,
28                        EnumProperty,
29                        IntProperty,
30                        StringProperty,
31                        )
32
33
34 class NodeSetting(PropertyGroup):
35     value = StringProperty(
36             name="Value",
37             description="Python expression to be evaluated as the initial node setting",
38             default="",
39             )
40
41
42 # Base class for node 'Add' operators
43 class NodeAddOperator():
44
45     type = StringProperty(
46             name="Node Type",
47             description="Node type",
48             )
49     use_transform = BoolProperty(
50             name="Use Transform",
51             description="Start transform operator after inserting the node",
52             default=False,
53             )
54     settings = CollectionProperty(
55             name="Settings",
56             description="Settings to be applied on the newly created node",
57             type=NodeSetting,
58             options={'SKIP_SAVE'},
59             )
60
61     @staticmethod
62     def store_mouse_cursor(context, event):
63         space = context.space_data
64         v2d = context.region.view2d
65         tree = space.edit_tree
66
67         # convert mouse position to the View2D for later node placement
68         if context.region.type == 'WINDOW':
69             # XXX, temp fix for [#35920], still fails for (U.pixelsize != 1)
70             dpi_fac = context.user_preferences.system.dpi / 72.0
71             space.cursor_location = v2d.region_to_view(event.mouse_region_x,
72                                                        event.mouse_region_y)
73             space.cursor_location /= dpi_fac
74
75         else:
76             space.cursor_location = tree.view_center
77
78     # XXX explicit node_type argument is usually not necessary, but required to make search operator work:
79     # add_search has to override the 'type' property since it's hardcoded in bpy_operator_wrap.c ...
80     def create_node(self, context, node_type=None):
81         space = context.space_data
82         tree = space.edit_tree
83
84         if node_type is None:
85             node_type = self.type
86
87         # select only the new node
88         for n in tree.nodes:
89             n.select = False
90
91         node = tree.nodes.new(type=node_type)
92
93         for setting in self.settings:
94             # XXX catch exceptions here?
95             value = eval(setting.value)
96
97             try:
98                 setattr(node, setting.name, value)
99             except AttributeError as e:
100                 self.report({'ERROR_INVALID_INPUT'}, "Node has no attribute " + setting.name)
101                 print(str(e))
102                 # Continue despite invalid attribute
103
104         if space.use_hidden_preview:
105             node.show_preview = False
106
107         node.select = True
108         tree.nodes.active = node
109         node.location = space.cursor_location
110         return node
111
112     @classmethod
113     def poll(cls, context):
114         space = context.space_data
115         # needs active node editor and a tree to add nodes to
116         return (space.type == 'NODE_EDITOR' and space.edit_tree and not space.edit_tree.library)
117
118     # Default execute simply adds a node
119     def execute(self, context):
120         self.create_node(context)
121         return {'FINISHED'}
122
123     # Default invoke stores the mouse position to place the node correctly
124     # and optionally invokes the transform operator
125     def invoke(self, context, event):
126         self.store_mouse_cursor(context, event)
127         result = self.execute(context)
128
129         if self.use_transform and ('FINISHED' in result):
130             bpy.ops.transform.translate('INVOKE_DEFAULT')
131
132         return result
133
134
135 # Simple basic operator for adding a node
136 class NODE_OT_add_node(NodeAddOperator, Operator):
137     '''Add a node to the active tree'''
138     bl_idname = "node.add_node"
139     bl_label = "Add Node"
140     bl_options = {'REGISTER', 'UNDO'}
141
142
143 # Add a node and link it to an existing socket
144 class NODE_OT_add_and_link_node(NodeAddOperator, Operator):
145     '''Add a node to the active tree and link to an existing socket'''
146     bl_idname = "node.add_and_link_node"
147     bl_label = "Add and Link Node"
148     bl_options = {'REGISTER', 'UNDO'}
149
150     link_socket_index = IntProperty(
151             name="Link Socket Index",
152             description="Index of the socket to link",
153             )
154
155     def execute(self, context):
156         space = context.space_data
157         ntree = space.edit_tree
158
159         node = self.create_node(context)
160         if not node:
161             return {'CANCELLED'}
162
163         to_socket = getattr(context, "link_to_socket", None)
164         if to_socket:
165             ntree.links.new(node.outputs[self.link_socket_index], to_socket)
166
167         from_socket = getattr(context, "link_from_socket", None)
168         if from_socket:
169             ntree.links.new(from_socket, node.inputs[self.link_socket_index])
170
171         return {'FINISHED'}
172
173
174 class NODE_OT_add_search(NodeAddOperator, Operator):
175     '''Add a node to the active tree'''
176     bl_idname = "node.add_search"
177     bl_label = "Search and Add Node"
178     bl_options = {'REGISTER', 'UNDO'}
179     bl_property = "node_item"
180
181     _enum_item_hack = []
182
183     # Create an enum list from node items
184     def node_enum_items(self, context):
185         enum_items = NODE_OT_add_search._enum_item_hack
186         enum_items.clear()
187
188         for index, item in enumerate(nodeitems_utils.node_items_iter(context)):
189             if isinstance(item, nodeitems_utils.NodeItem):
190                 nodetype = getattr(bpy.types, item.nodetype, None)
191                 if nodetype:
192                     enum_items.append((str(index), item.label, nodetype.bl_rna.description, index))
193         return enum_items
194
195     # Look up the item based on index
196     def find_node_item(self, context):
197         node_item = int(self.node_item)
198         for index, item in enumerate(nodeitems_utils.node_items_iter(context)):
199             if index == node_item:
200                 return item
201         return None
202
203     node_item = EnumProperty(
204             name="Node Type",
205             description="Node type",
206             items=node_enum_items,
207             )
208
209     def execute(self, context):
210         item = self.find_node_item(context)
211
212         # no need to keep
213         self._enum_item_hack.clear()
214
215         if item:
216             # apply settings from the node item
217             for setting in item.settings.items():
218                 ops = self.settings.add()
219                 ops.name = setting[0]
220                 ops.value = setting[1]
221
222             self.create_node(context, item.nodetype)
223
224             if self.use_transform:
225                 bpy.ops.transform.translate('INVOKE_DEFAULT')
226
227             return {'FINISHED'}
228         else:
229             return {'CANCELLED'}
230
231     def invoke(self, context, event):
232         self.store_mouse_cursor(context, event)
233         # Delayed execution in the search popup
234         context.window_manager.invoke_search_popup(self)
235         return {'CANCELLED'}
236
237
238 class NODE_OT_collapse_hide_unused_toggle(Operator):
239     '''Toggle collapsed nodes and hide unused sockets'''
240     bl_idname = "node.collapse_hide_unused_toggle"
241     bl_label = "Collapse and Hide Unused Sockets"
242     bl_options = {'REGISTER', 'UNDO'}
243
244     @classmethod
245     def poll(cls, context):
246         space = context.space_data
247         # needs active node editor and a tree
248         return (space.type == 'NODE_EDITOR' and space.edit_tree and not space.edit_tree.library)
249
250     def execute(self, context):
251         space = context.space_data
252         tree = space.edit_tree
253
254         for node in tree.nodes:
255             if node.select:
256                 hide = (not node.hide)
257
258                 node.hide = hide
259                 # Note: connected sockets are ignored internally
260                 for socket in node.inputs:
261                     socket.hide = hide
262                 for socket in node.outputs:
263                     socket.hide = hide
264
265         return {'FINISHED'}
266
267
268 class NODE_OT_tree_path_parent(Operator):
269     '''Go to parent node tree'''
270     bl_idname = "node.tree_path_parent"
271     bl_label = "Parent Node Tree"
272     bl_options = {'REGISTER', 'UNDO'}
273
274     @classmethod
275     def poll(cls, context):
276         space = context.space_data
277         # needs active node editor and a tree
278         return (space.type == 'NODE_EDITOR' and len(space.path) > 1)
279
280     def execute(self, context):
281         space = context.space_data
282
283         space.path.pop()
284
285         return {'FINISHED'}