31451d7eedf90db06a2fbf5822d066d55b8ee1f8
[blender.git] / release / scripts / startup / bl_ui / space_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 compliant>
20 import bpy
21 import nodeitems_utils
22 from bpy.types import Header, Menu, Panel
23 from bpy.app.translations import pgettext_iface as iface_
24 from bl_ui.utils import PresetPanel
25 from .properties_grease_pencil_common import (
26     AnnotationDataPanel,
27     GreasePencilToolsPanel,
28 )
29 from .properties_material import (
30     EEVEE_MATERIAL_PT_settings,
31     MATERIAL_PT_viewport
32 )
33 from .properties_world import (
34     WORLD_PT_viewport_display
35 )
36 from .properties_data_light import (
37     DATA_PT_light,
38     DATA_PT_EEVEE_light,
39 )
40
41
42 class NODE_HT_header(Header):
43     bl_space_type = 'NODE_EDITOR'
44
45     def draw(self, context):
46         layout = self.layout
47
48         scene = context.scene
49         snode = context.space_data
50         snode_id = snode.id
51         id_from = snode.id_from
52         tool_settings = context.tool_settings
53
54         row = layout.row(align=True)
55         row.template_header()
56
57         # Now expanded via the 'ui_type'
58         # layout.prop(snode, "tree_type", text="")
59
60         if snode.tree_type == 'ShaderNodeTree':
61             layout.prop(snode, "shader_type", text="")
62
63             ob = context.object
64             if snode.shader_type == 'OBJECT' and ob:
65
66                 NODE_MT_editor_menus.draw_collapsible(context, layout)
67
68                 # No shader nodes for Eevee lights
69                 if snode_id and not (context.engine == 'BLENDER_EEVEE' and ob.type == 'LIGHT'):
70                     row = layout.row()
71                     row.prop(snode_id, "use_nodes")
72
73                 layout.separator_spacer()
74
75                 row = layout.row()
76                 types_that_support_material = {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META', 'GPENCIL'}
77                 # disable material slot buttons when pinned, cannot find correct slot within id_from (#36589)
78                 # disable also when the selected object does not support materials
79                 row.enabled = not snode.pin and ob.type in types_that_support_material
80                 # Show material.new when no active ID/slot exists
81                 if not id_from and ob.type in types_that_support_material:
82                     row.template_ID(ob, "active_material", new="material.new")
83                 # Material ID, but not for Lights
84                 if id_from and ob.type != 'LIGHT':
85                     row.template_ID(id_from, "active_material", new="material.new")
86
87             if snode.shader_type == 'WORLD':
88                 NODE_MT_editor_menus.draw_collapsible(context, layout)
89
90                 if snode_id:
91                     row = layout.row()
92                     row.prop(snode_id, "use_nodes")
93
94                 layout.separator_spacer()
95
96                 row = layout.row()
97                 row.enabled = not snode.pin
98                 row.template_ID(scene, "world", new="world.new")
99
100             if snode.shader_type == 'LINESTYLE':
101                 view_layer = context.view_layer
102                 lineset = view_layer.freestyle_settings.linesets.active
103
104                 if lineset is not None:
105                     NODE_MT_editor_menus.draw_collapsible(context, layout)
106
107                     if snode_id:
108                         row = layout.row()
109                         row.prop(snode_id, "use_nodes")
110
111                     layout.separator_spacer()
112
113                     row = layout.row()
114                     row.enabled = not snode.pin
115                     row.template_ID(lineset, "linestyle", new="scene.freestyle_linestyle_new")
116
117         elif snode.tree_type == 'TextureNodeTree':
118             layout.prop(snode, "texture_type", text="")
119
120             NODE_MT_editor_menus.draw_collapsible(context, layout)
121
122             if snode_id:
123                 layout.prop(snode_id, "use_nodes")
124
125             layout.separator_spacer()
126
127             if id_from:
128                 if snode.texture_type == 'BRUSH':
129                     layout.template_ID(id_from, "texture", new="texture.new")
130                 else:
131                     layout.template_ID(id_from, "active_texture", new="texture.new")
132
133         elif snode.tree_type == 'CompositorNodeTree':
134
135             NODE_MT_editor_menus.draw_collapsible(context, layout)
136
137             if snode_id:
138                 layout.prop(snode_id, "use_nodes")
139
140             layout.prop(snode, "use_auto_render")
141             layout.prop(snode, "show_backdrop")
142             if snode.show_backdrop:
143                 row = layout.row(align=True)
144                 row.prop(snode, "backdrop_channels", text="", expand=True)
145
146         else:
147             # Custom node tree is edited as independent ID block
148             NODE_MT_editor_menus.draw_collapsible(context, layout)
149
150             layout.separator_spacer()
151
152             layout.template_ID(snode, "node_tree", new="node.new_node_tree")
153
154         layout.prop(snode, "pin", text="")
155         layout.separator_spacer()
156
157         layout.template_running_jobs()
158
159         layout.operator("node.tree_path_parent", text="", icon='FILE_PARENT')
160
161         # Snap
162         row = layout.row(align=True)
163         row.prop(tool_settings, "use_snap", text="")
164         row.prop(tool_settings, "snap_node_element", icon_only=True)
165         if tool_settings.snap_node_element != 'GRID':
166             row.prop(tool_settings, "snap_target", text="")
167
168
169 class NODE_MT_editor_menus(Menu):
170     bl_idname = "NODE_MT_editor_menus"
171     bl_label = ""
172
173     def draw(self, context):
174         layout = self.layout
175         layout.menu("NODE_MT_view")
176         layout.menu("NODE_MT_select")
177         layout.menu("NODE_MT_add")
178         layout.menu("NODE_MT_node")
179
180
181 class NODE_MT_add(bpy.types.Menu):
182     bl_space_type = 'NODE_EDITOR'
183     bl_label = "Add"
184
185     def draw(self, context):
186         layout = self.layout
187
188         layout.operator_context = 'INVOKE_DEFAULT'
189         props = layout.operator("node.add_search", text="Search...", icon='VIEWZOOM')
190         props.use_transform = True
191
192         layout.separator()
193
194         # actual node submenus are defined by draw functions from node categories
195         nodeitems_utils.draw_node_categories_menu(self, context)
196
197
198 class NODE_MT_view(Menu):
199     bl_label = "View"
200
201     def draw(self, context):
202         layout = self.layout
203
204         snode = context.space_data
205
206         layout.operator("node.properties", icon='MENU_PANEL')
207         layout.operator("node.toolbar", icon='MENU_PANEL')
208
209         layout.separator()
210
211         # Auto-offset nodes (called "insert_offset" in code)
212         layout.prop(snode, "use_insert_offset")
213
214         layout.separator()
215
216         layout.operator("view2d.zoom_in")
217         layout.operator("view2d.zoom_out")
218
219         layout.separator()
220
221         layout.operator("node.view_selected")
222         layout.operator("node.view_all")
223
224         if context.space_data.show_backdrop:
225             layout.separator()
226
227             layout.operator("node.backimage_move", text="Backdrop Move")
228             layout.operator("node.backimage_zoom", text="Backdrop Zoom In").factor = 1.2
229             layout.operator("node.backimage_zoom", text="Backdrop Zoom Out").factor = 1.0 / 1.2
230             layout.operator("node.backimage_fit", text="Fit Backdrop to Available Space")
231
232         layout.separator()
233
234         layout.menu("INFO_MT_area")
235
236
237 class NODE_MT_select(Menu):
238     bl_label = "Select"
239
240     def draw(self, context):
241         layout = self.layout
242
243         layout.operator("node.select_box").tweak = False
244         layout.operator("node.select_circle")
245
246         layout.separator()
247         layout.operator("node.select_all").action = 'TOGGLE'
248         layout.operator("node.select_all", text="Inverse").action = 'INVERT'
249         layout.operator("node.select_linked_from")
250         layout.operator("node.select_linked_to")
251
252         layout.separator()
253
254         layout.operator("node.select_grouped").extend = False
255         layout.operator("node.select_same_type_step", text="Activate Same Type Previous").prev = True
256         layout.operator("node.select_same_type_step", text="Activate Same Type Next").prev = False
257
258         layout.separator()
259
260         layout.operator("node.find_node")
261
262
263 class NODE_MT_node(Menu):
264     bl_label = "Node"
265
266     def draw(self, context):
267         layout = self.layout
268
269         layout.operator("transform.translate")
270         layout.operator("transform.rotate")
271         layout.operator("transform.resize")
272
273         layout.separator()
274         layout.operator("node.clipboard_copy", text="Copy")
275         layout.operator("node.clipboard_paste", text="Paste")
276         layout.operator("node.duplicate_move")
277         layout.operator("node.delete")
278         layout.operator("node.delete_reconnect")
279
280         layout.separator()
281
282         layout.operator("node.join", text="Join in New Frame")
283         layout.operator("node.detach", text="Remove from Frame")
284
285         layout.separator()
286
287         layout.operator("node.link_make").replace = False
288         layout.operator("node.link_make", text="Make and Replace Links").replace = True
289         layout.operator("node.links_cut")
290         layout.operator("node.links_detach")
291
292         layout.separator()
293
294         layout.operator("node.group_edit").exit = False
295         layout.operator("node.group_ungroup")
296         layout.operator("node.group_make")
297         layout.operator("node.group_insert")
298
299         layout.separator()
300
301         layout.operator("node.hide_toggle")
302         layout.operator("node.mute_toggle")
303         layout.operator("node.preview_toggle")
304         layout.operator("node.hide_socket_toggle")
305         layout.operator("node.options_toggle")
306         layout.operator("node.collapse_hide_unused_toggle")
307
308         layout.separator()
309
310         layout.operator("node.read_viewlayers")
311
312
313 class NODE_PT_node_color_presets(PresetPanel, Panel):
314     """Predefined node color"""
315     bl_label = "Color Presets"
316     preset_subdir = "node_color"
317     preset_operator = "script.execute_preset"
318     preset_add_operator = "node.node_color_preset_add"
319
320
321 class NODE_MT_node_color_context_menu(Menu):
322     bl_label = "Node Color Specials"
323
324     def draw(self, context):
325         layout = self.layout
326
327         layout.operator("node.node_copy_color", icon='COPY_ID')
328
329
330 class NODE_MT_context_menu(Menu):
331     bl_label = "Node Context Menu"
332
333     def draw(self, context):
334         layout = self.layout
335
336         selected_nodes_len = len(context.selected_nodes)
337
338         # If nothing is selected
339         # (disabled for now until it can be made more useful).
340         '''
341         if selected_nodes_len == 0:
342             layout.operator_context = 'INVOKE_DEFAULT'
343             layout.menu("NODE_MT_add")
344             layout.operator("node.clipboard_paste", text="Paste")
345             return
346         '''
347
348         # If something is selected
349         layout.operator_context = 'INVOKE_DEFAULT'
350         layout.operator("node.duplicate_move")
351         layout.operator("node.delete")
352         layout.operator("node.clipboard_copy", text="Copy")
353         layout.operator("node.clipboard_paste", text="Paste")
354         layout.operator_context = 'EXEC_DEFAULT'
355
356         layout.operator("node.delete_reconnect")
357
358         if selected_nodes_len > 1:
359             layout.separator()
360
361             layout.operator("node.link_make").replace = False
362             layout.operator("node.link_make", text="Make and Replace Links").replace = True
363             layout.operator("node.links_detach")
364
365             layout.separator()
366
367             layout.operator("node.group_make", text="Group")
368
369         layout.operator("node.group_ungroup", text="Ungroup")
370         layout.operator("node.group_edit").exit = False
371
372         layout.separator()
373
374         layout.operator("node.hide_toggle")
375         layout.operator("node.mute_toggle")
376         layout.operator("node.preview_toggle")
377         layout.operator("node.hide_socket_toggle")
378         layout.operator("node.options_toggle")
379         layout.operator("node.collapse_hide_unused_toggle")
380
381
382 class NODE_PT_active_node_generic(Panel):
383     bl_space_type = 'NODE_EDITOR'
384     bl_region_type = 'UI'
385     bl_category = "Node"
386     bl_label = "Node"
387
388     @classmethod
389     def poll(cls, context):
390         return context.active_node is not None
391
392     def draw(self, context):
393         layout = self.layout
394         node = context.active_node
395
396         layout.prop(node, "name", icon='NODE')
397         layout.prop(node, "label", icon='NODE')
398
399
400 class NODE_PT_active_node_color(Panel):
401     bl_space_type = 'NODE_EDITOR'
402     bl_region_type = 'UI'
403     bl_category = "Node"
404     bl_label = "Color"
405     bl_options = {'DEFAULT_CLOSED'}
406     bl_parent_id = 'NODE_PT_active_node_generic'
407
408     @classmethod
409     def poll(cls, context):
410         return context.active_node is not None
411
412     def draw_header(self, context):
413         node = context.active_node
414         self.layout.prop(node, "use_custom_color", text="")
415
416     def draw_header_preset(self, context):
417         NODE_PT_node_color_presets.draw_panel_header(self.layout)
418
419     def draw(self, context):
420         layout = self.layout
421         node = context.active_node
422
423         layout.enabled = node.use_custom_color
424
425         row = layout.row()
426         row.prop(node, "color", text="")
427         row.menu("NODE_MT_node_color_context_menu", text="", icon='DOWNARROW_HLT')
428
429
430 class NODE_PT_active_node_properties(Panel):
431     bl_space_type = 'NODE_EDITOR'
432     bl_region_type = 'UI'
433     bl_category = "Node"
434     bl_label = "Properties"
435     bl_options = {'DEFAULT_CLOSED'}
436     bl_parent_id = 'NODE_PT_active_node_generic'
437
438     @classmethod
439     def poll(cls, context):
440         return context.active_node is not None
441
442     def draw(self, context):
443         layout = self.layout
444         node = context.active_node
445         # set "node" context pointer for the panel layout
446         layout.context_pointer_set("node", node)
447
448         if hasattr(node, "draw_buttons_ext"):
449             node.draw_buttons_ext(context, layout)
450         elif hasattr(node, "draw_buttons"):
451             node.draw_buttons(context, layout)
452
453         # XXX this could be filtered further to exclude socket types
454         # which don't have meaningful input values (e.g. cycles shader)
455         value_inputs = [socket for socket in node.inputs if socket.enabled and not socket.is_linked]
456         if value_inputs:
457             layout.separator()
458             layout.label(text="Inputs:")
459             for socket in value_inputs:
460                 row = layout.row()
461                 socket.draw(context, row, node, iface_(socket.name, socket.bl_rna.translation_context))
462
463
464 # Node Backdrop options
465 class NODE_PT_backdrop(Panel):
466     bl_space_type = 'NODE_EDITOR'
467     bl_region_type = 'UI'
468     bl_category = "Node"
469     bl_label = "Backdrop"
470
471     @classmethod
472     def poll(cls, context):
473         snode = context.space_data
474         return snode.tree_type == 'CompositorNodeTree'
475
476     def draw_header(self, context):
477         snode = context.space_data
478         self.layout.prop(snode, "show_backdrop", text="")
479
480     def draw(self, context):
481         layout = self.layout
482         layout.use_property_split = True
483
484         snode = context.space_data
485         layout.active = snode.show_backdrop
486
487         col = layout.column()
488
489         col.prop(snode, "backdrop_channels", text="Channels")
490         col.prop(snode, "backdrop_zoom", text="Zoom")
491
492         col.prop(snode, "backdrop_offset", text="Offset")
493
494         col.separator()
495
496         col.operator("node.backimage_move", text="Move")
497         col.operator("node.backimage_fit", text="Fit")
498
499
500 class NODE_PT_quality(bpy.types.Panel):
501     bl_space_type = 'NODE_EDITOR'
502     bl_region_type = 'UI'
503     bl_category = "Node"
504     bl_label = "Performance"
505
506     @classmethod
507     def poll(cls, context):
508         snode = context.space_data
509         return snode.tree_type == 'CompositorNodeTree' and snode.node_tree is not None
510
511     def draw(self, context):
512         layout = self.layout
513         layout.use_property_split = True
514
515         snode = context.space_data
516         tree = snode.node_tree
517
518         col = layout.column()
519         col.prop(tree, "render_quality", text="Render")
520         col.prop(tree, "edit_quality", text="Edit")
521         col.prop(tree, "chunk_size")
522
523         col = layout.column()
524         col.prop(tree, "use_opencl")
525         col.prop(tree, "use_groupnode_buffer")
526         col.prop(tree, "use_two_pass")
527         col.prop(tree, "use_viewer_border")
528
529
530 class NODE_UL_interface_sockets(bpy.types.UIList):
531     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
532         socket = item
533         color = socket.draw_color(context)
534
535         if self.layout_type in {'DEFAULT', 'COMPACT'}:
536             row = layout.row(align=True)
537
538             # inputs get icon on the left
539             if not socket.is_output:
540                 row.template_node_socket(color=color)
541
542             row.prop(socket, "name", text="", emboss=False, icon_value=icon)
543
544             # outputs get icon on the right
545             if socket.is_output:
546                 row.template_node_socket(color=color)
547
548         elif self.layout_type == 'GRID':
549             layout.alignment = 'CENTER'
550             layout.template_node_socket(color=color)
551
552
553 # Grease Pencil properties
554 class NODE_PT_grease_pencil(AnnotationDataPanel, Panel):
555     bl_space_type = 'NODE_EDITOR'
556     bl_region_type = 'UI'
557     bl_category = "Node"
558     bl_options = {'DEFAULT_CLOSED'}
559
560     # NOTE: this is just a wrapper around the generic GP Panel
561
562     @classmethod
563     def poll(cls, context):
564         snode = context.space_data
565         return snode is not None and snode.node_tree is not None
566
567
568 class NODE_PT_grease_pencil_tools(GreasePencilToolsPanel, Panel):
569     bl_space_type = 'NODE_EDITOR'
570     bl_region_type = 'UI'
571     bl_category = "Node"
572     bl_options = {'DEFAULT_CLOSED'}
573
574     # NOTE: this is just a wrapper around the generic GP tools panel
575     # It contains access to some essential tools usually found only in
576     # toolbar, but which may not necessarily be open
577
578
579 def node_draw_tree_view(layout, context):
580     pass
581
582
583 # Adapt properties editor panel to display in node editor. We have to
584 # copy the class rather than inherit due to the way bpy registration works.
585 def node_panel(cls):
586     node_cls = type('NODE_' + cls.__name__, cls.__bases__, dict(cls.__dict__))
587
588     node_cls.bl_space_type = 'NODE_EDITOR'
589     node_cls.bl_region_type = 'UI'
590     node_cls.bl_category = "Node"
591     if hasattr(node_cls, 'bl_parent_id'):
592         node_cls.bl_parent_id = 'NODE_' + node_cls.bl_parent_id
593
594     return node_cls
595
596
597 classes = (
598     NODE_HT_header,
599     NODE_MT_editor_menus,
600     NODE_MT_add,
601     NODE_MT_view,
602     NODE_MT_select,
603     NODE_MT_node,
604     NODE_PT_node_color_presets,
605     NODE_MT_node_color_context_menu,
606     NODE_MT_context_menu,
607     NODE_PT_active_node_generic,
608     NODE_PT_active_node_color,
609     NODE_PT_active_node_properties,
610     NODE_PT_backdrop,
611     NODE_PT_quality,
612     NODE_UL_interface_sockets,
613     NODE_PT_grease_pencil,
614     NODE_PT_grease_pencil_tools,
615     node_panel(EEVEE_MATERIAL_PT_settings),
616     node_panel(MATERIAL_PT_viewport),
617     node_panel(WORLD_PT_viewport_display),
618     node_panel(DATA_PT_light),
619     node_panel(DATA_PT_EEVEE_light),
620 )
621
622
623 if __name__ == "__main__":  # only for live edit.
624     from bpy.utils import register_class
625     for cls in classes:
626         register_class(cls)