Missed one usage of use_hidden_preview.
[blender.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             # convert mouse position to the View2D for later node placement
70             space.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y)
71         else:
72             space.cursor_location = tree.view_center
73
74     # XXX explicit node_type argument is usually not necessary, but required to make search operator work:
75     # add_search has to override the 'type' property since it's hardcoded in bpy_operator_wrap.c ...
76     def create_node(self, context, node_type=None):
77         space = context.space_data
78         tree = space.edit_tree
79
80         if node_type is None:
81             node_type = self.type
82
83         # select only the new node
84         for n in tree.nodes:
85             n.select = False
86
87         node = tree.nodes.new(type=node_type)
88
89         for setting in self.settings:
90             # XXX catch exceptions here?
91             value = eval(setting.value)
92
93             try:
94                 setattr(node, setting.name, value)
95             except AttributeError as e:
96                 self.report({'ERROR_INVALID_INPUT'}, "Node has no attribute " + setting.name)
97                 print(str(e))
98                 # Continue despite invalid attribute
99
100         node.select = True
101         tree.nodes.active = node
102         node.location = space.cursor_location
103         return node
104
105     @classmethod
106     def poll(cls, context):
107         space = context.space_data
108         # needs active node editor and a tree to add nodes to
109         return (space.type == 'NODE_EDITOR' and space.edit_tree and not space.edit_tree.library)
110
111     # Default execute simply adds a node
112     def execute(self, context):
113         if self.properties.is_property_set("type"):
114             self.create_node(context)
115             return {'FINISHED'}
116         else:
117             return {'CANCELLED'}
118
119     # Default invoke stores the mouse position to place the node correctly
120     # and optionally invokes the transform operator
121     def invoke(self, context, event):
122         self.store_mouse_cursor(context, event)
123         result = self.execute(context)
124
125         if self.use_transform and ('FINISHED' in result):
126             # removes the node again if transform is cancelled
127             bpy.ops.transform.translate('INVOKE_DEFAULT', remove_on_cancel=True)
128
129         return result
130
131
132 # Simple basic operator for adding a node
133 class NODE_OT_add_node(NodeAddOperator, Operator):
134     '''Add a node to the active tree'''
135     bl_idname = "node.add_node"
136     bl_label = "Add Node"
137     bl_options = {'REGISTER', 'UNDO'}
138
139
140 # Add a node and link it to an existing socket
141 class NODE_OT_add_and_link_node(NodeAddOperator, Operator):
142     '''Add a node to the active tree and link to an existing socket'''
143     bl_idname = "node.add_and_link_node"
144     bl_label = "Add and Link Node"
145     bl_options = {'REGISTER', 'UNDO'}
146
147     link_socket_index = IntProperty(
148             name="Link Socket Index",
149             description="Index of the socket to link",
150             )
151
152     def execute(self, context):
153         space = context.space_data
154         ntree = space.edit_tree
155
156         node = self.create_node(context)
157         if not node:
158             return {'CANCELLED'}
159
160         to_socket = getattr(context, "link_to_socket", None)
161         if to_socket:
162             ntree.links.new(node.outputs[self.link_socket_index], to_socket)
163
164         from_socket = getattr(context, "link_from_socket", None)
165         if from_socket:
166             ntree.links.new(from_socket, node.inputs[self.link_socket_index])
167
168         return {'FINISHED'}
169
170
171 class NODE_OT_add_search(NodeAddOperator, Operator):
172     '''Add a node to the active tree'''
173     bl_idname = "node.add_search"
174     bl_label = "Search and Add Node"
175     bl_options = {'REGISTER', 'UNDO'}
176     bl_property = "node_item"
177
178     _enum_item_hack = []
179
180     # Create an enum list from node items
181     def node_enum_items(self, context):
182         enum_items = NODE_OT_add_search._enum_item_hack
183         enum_items.clear()
184
185         for index, item in enumerate(nodeitems_utils.node_items_iter(context)):
186             if isinstance(item, nodeitems_utils.NodeItem):
187                 nodetype = getattr(bpy.types, item.nodetype, None)
188                 if nodetype:
189                     enum_items.append((str(index), item.label, nodetype.bl_rna.description, index))
190         return enum_items
191
192     # Look up the item based on index
193     def find_node_item(self, context):
194         node_item = int(self.node_item)
195         for index, item in enumerate(nodeitems_utils.node_items_iter(context)):
196             if index == node_item:
197                 return item
198         return None
199
200     node_item = EnumProperty(
201             name="Node Type",
202             description="Node type",
203             items=node_enum_items,
204             )
205
206     def execute(self, context):
207         item = self.find_node_item(context)
208
209         # no need to keep
210         self._enum_item_hack.clear()
211
212         if item:
213             # apply settings from the node item
214             for setting in item.settings.items():
215                 ops = self.settings.add()
216                 ops.name = setting[0]
217                 ops.value = setting[1]
218
219             self.create_node(context, item.nodetype)
220
221             if self.use_transform:
222                 bpy.ops.transform.translate('INVOKE_DEFAULT', remove_on_cancel=True)
223
224             return {'FINISHED'}
225         else:
226             return {'CANCELLED'}
227
228     def invoke(self, context, event):
229         self.store_mouse_cursor(context, event)
230         # Delayed execution in the search popup
231         context.window_manager.invoke_search_popup(self)
232         return {'CANCELLED'}
233
234
235 class NODE_OT_collapse_hide_unused_toggle(Operator):
236     '''Toggle collapsed nodes and hide unused sockets'''
237     bl_idname = "node.collapse_hide_unused_toggle"
238     bl_label = "Collapse and Hide Unused Sockets"
239     bl_options = {'REGISTER', 'UNDO'}
240
241     @classmethod
242     def poll(cls, context):
243         space = context.space_data
244         # needs active node editor and a tree
245         return (space.type == 'NODE_EDITOR' and space.edit_tree and not space.edit_tree.library)
246
247     def execute(self, context):
248         space = context.space_data
249         tree = space.edit_tree
250
251         for node in tree.nodes:
252             if node.select:
253                 hide = (not node.hide)
254
255                 node.hide = hide
256                 # Note: connected sockets are ignored internally
257                 for socket in node.inputs:
258                     socket.hide = hide
259                 for socket in node.outputs:
260                     socket.hide = hide
261
262         return {'FINISHED'}
263
264
265 class NODE_OT_tree_path_parent(Operator):
266     '''Go to parent node tree'''
267     bl_idname = "node.tree_path_parent"
268     bl_label = "Parent Node Tree"
269     bl_options = {'REGISTER', 'UNDO'}
270
271     @classmethod
272     def poll(cls, context):
273         space = context.space_data
274         # needs active node editor and a tree
275         return (space.type == 'NODE_EDITOR' and len(space.path) > 1)
276
277     def execute(self, context):
278         space = context.space_data
279
280         space.path.pop()
281
282         return {'FINISHED'}