Import_3ds: Improved distance cue chunk import
[blender-addons.git] / node_wrangler / interface.py
blobd0131d289cff5c5e4c4e20faf7a67a3acd1a9394
1 # SPDX-FileCopyrightText: 2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
6 from bpy.types import Panel, Menu
7 from bpy.props import StringProperty
8 from nodeitems_utils import node_categories_iter, NodeItemCustom
10 from . import operators
12 from .utils.constants import blend_types, geo_combine_operations, operations
13 from .utils.nodes import get_nodes_links, NWBaseMenu
16 def drawlayout(context, layout, mode='non-panel'):
17 tree_type = context.space_data.tree_type
19 col = layout.column(align=True)
20 col.menu(NWMergeNodesMenu.bl_idname)
21 col.separator()
23 if tree_type == 'ShaderNodeTree':
24 col = layout.column(align=True)
25 col.operator(operators.NWAddTextureSetup.bl_idname, text="Add Texture Setup", icon='NODE_SEL')
26 col.operator(operators.NWAddPrincipledSetup.bl_idname, text="Add Principled Setup", icon='NODE_SEL')
27 col.separator()
29 col = layout.column(align=True)
30 col.operator(operators.NWDetachOutputs.bl_idname, icon='UNLINKED')
31 col.operator(operators.NWSwapLinks.bl_idname)
32 col.menu(NWAddReroutesMenu.bl_idname, text="Add Reroutes", icon='LAYER_USED')
33 col.separator()
35 col = layout.column(align=True)
36 col.menu(NWLinkActiveToSelectedMenu.bl_idname, text="Link Active To Selected", icon='LINKED')
37 if tree_type != 'GeometryNodeTree':
38 col.operator(operators.NWLinkToOutputNode.bl_idname, icon='DRIVER')
39 col.separator()
41 col = layout.column(align=True)
42 if mode == 'panel':
43 row = col.row(align=True)
44 row.operator(operators.NWClearLabel.bl_idname).option = True
45 row.operator(operators.NWModifyLabels.bl_idname)
46 else:
47 col.operator(operators.NWClearLabel.bl_idname).option = True
48 col.operator(operators.NWModifyLabels.bl_idname)
49 col.menu(NWBatchChangeNodesMenu.bl_idname, text="Batch Change")
50 col.separator()
51 col.menu(NWCopyToSelectedMenu.bl_idname, text="Copy to Selected")
52 col.separator()
54 col = layout.column(align=True)
55 if tree_type == 'CompositorNodeTree':
56 col.operator(operators.NWResetBG.bl_idname, icon='ZOOM_PREVIOUS')
57 if tree_type != 'GeometryNodeTree':
58 col.operator(operators.NWReloadImages.bl_idname, icon='FILE_REFRESH')
59 col.separator()
61 col = layout.column(align=True)
62 col.operator(operators.NWFrameSelected.bl_idname, icon='STICKY_UVS_LOC')
63 col.separator()
65 col = layout.column(align=True)
66 col.operator(operators.NWAlignNodes.bl_idname, icon='CENTER_ONLY')
67 col.separator()
69 col = layout.column(align=True)
70 col.operator(operators.NWDeleteUnused.bl_idname, icon='CANCEL')
71 col.separator()
74 class NodeWranglerPanel(Panel, NWBaseMenu):
75 bl_idname = "NODE_PT_nw_node_wrangler"
76 bl_space_type = 'NODE_EDITOR'
77 bl_label = "Node Wrangler"
78 bl_region_type = "UI"
79 bl_category = "Node Wrangler"
81 prepend: StringProperty(
82 name='prepend',
84 append: StringProperty()
85 remove: StringProperty()
87 def draw(self, context):
88 self.layout.label(text="(Quick access: Shift+W)")
89 drawlayout(context, self.layout, mode='panel')
93 # M E N U S
95 class NodeWranglerMenu(Menu, NWBaseMenu):
96 bl_idname = "NODE_MT_nw_node_wrangler_menu"
97 bl_label = "Node Wrangler"
99 def draw(self, context):
100 self.layout.operator_context = 'INVOKE_DEFAULT'
101 drawlayout(context, self.layout)
104 class NWMergeNodesMenu(Menu, NWBaseMenu):
105 bl_idname = "NODE_MT_nw_merge_nodes_menu"
106 bl_label = "Merge Selected Nodes"
108 def draw(self, context):
109 type = context.space_data.tree_type
110 layout = self.layout
111 if type == 'ShaderNodeTree':
112 layout.menu(NWMergeShadersMenu.bl_idname, text="Use Shaders")
113 if type == 'GeometryNodeTree':
114 layout.menu(NWMergeGeometryMenu.bl_idname, text="Use Geometry Nodes")
115 layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes")
116 else:
117 layout.menu(NWMergeMixMenu.bl_idname, text="Use Mix Nodes")
118 layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes")
119 props = layout.operator(operators.NWMergeNodes.bl_idname, text="Use Z-Combine Nodes")
120 props.mode = 'MIX'
121 props.merge_type = 'ZCOMBINE'
122 props = layout.operator(operators.NWMergeNodes.bl_idname, text="Use Alpha Over Nodes")
123 props.mode = 'MIX'
124 props.merge_type = 'ALPHAOVER'
127 class NWMergeGeometryMenu(Menu, NWBaseMenu):
128 bl_idname = "NODE_MT_nw_merge_geometry_menu"
129 bl_label = "Merge Selected Nodes using Geometry Nodes"
131 def draw(self, context):
132 layout = self.layout
133 # The boolean node + Join Geometry node
134 for type, name, description in geo_combine_operations:
135 props = layout.operator(operators.NWMergeNodes.bl_idname, text=name)
136 props.mode = type
137 props.merge_type = 'GEOMETRY'
140 class NWMergeShadersMenu(Menu, NWBaseMenu):
141 bl_idname = "NODE_MT_nw_merge_shaders_menu"
142 bl_label = "Merge Selected Nodes using Shaders"
144 def draw(self, context):
145 layout = self.layout
146 for type in ('MIX', 'ADD'):
147 name = f'{type.capitalize()} Shader'
148 props = layout.operator(operators.NWMergeNodes.bl_idname, text=name)
149 props.mode = type
150 props.merge_type = 'SHADER'
153 class NWMergeMixMenu(Menu, NWBaseMenu):
154 bl_idname = "NODE_MT_nw_merge_mix_menu"
155 bl_label = "Merge Selected Nodes using Mix"
157 def draw(self, context):
158 layout = self.layout
159 for type, name, description in blend_types:
160 props = layout.operator(operators.NWMergeNodes.bl_idname, text=name)
161 props.mode = type
162 props.merge_type = 'MIX'
165 class NWConnectionListOutputs(Menu, NWBaseMenu):
166 bl_idname = "NODE_MT_nw_connection_list_out"
167 bl_label = "From:"
169 def draw(self, context):
170 layout = self.layout
171 nodes, links = get_nodes_links(context)
173 n1 = nodes[context.scene.NWLazySource]
174 for index, output in enumerate(n1.outputs):
175 # Only show sockets that are exposed.
176 if output.enabled:
177 layout.operator(
178 operators.NWCallInputsMenu.bl_idname,
179 text=output.name,
180 icon="RADIOBUT_OFF").from_socket = index
183 class NWConnectionListInputs(Menu, NWBaseMenu):
184 bl_idname = "NODE_MT_nw_connection_list_in"
185 bl_label = "To:"
187 def draw(self, context):
188 layout = self.layout
189 nodes, links = get_nodes_links(context)
191 n2 = nodes[context.scene.NWLazyTarget]
193 for index, input in enumerate(n2.inputs):
194 # Only show sockets that are exposed.
195 # This prevents, for example, the scale value socket
196 # of the vector math node being added to the list when
197 # the mode is not 'SCALE'.
198 if input.enabled:
199 op = layout.operator(operators.NWMakeLink.bl_idname, text=input.name, icon="FORWARD")
200 op.from_socket = context.scene.NWSourceSocket
201 op.to_socket = index
204 class NWMergeMathMenu(Menu, NWBaseMenu):
205 bl_idname = "NODE_MT_nw_merge_math_menu"
206 bl_label = "Merge Selected Nodes using Math"
208 def draw(self, context):
209 layout = self.layout
210 for type, name, description in operations:
211 props = layout.operator(operators.NWMergeNodes.bl_idname, text=name)
212 props.mode = type
213 props.merge_type = 'MATH'
216 class NWBatchChangeNodesMenu(Menu, NWBaseMenu):
217 bl_idname = "NODE_MT_nw_batch_change_nodes_menu"
218 bl_label = "Batch Change Selected Nodes"
220 def draw(self, context):
221 layout = self.layout
222 layout.menu(NWBatchChangeBlendTypeMenu.bl_idname)
223 layout.menu(NWBatchChangeOperationMenu.bl_idname)
226 class NWBatchChangeBlendTypeMenu(Menu, NWBaseMenu):
227 bl_idname = "NODE_MT_nw_batch_change_blend_type_menu"
228 bl_label = "Batch Change Blend Type"
230 def draw(self, context):
231 layout = self.layout
232 for type, name, description in blend_types:
233 props = layout.operator(operators.NWBatchChangeNodes.bl_idname, text=name)
234 props.blend_type = type
235 props.operation = 'CURRENT'
238 class NWBatchChangeOperationMenu(Menu, NWBaseMenu):
239 bl_idname = "NODE_MT_nw_batch_change_operation_menu"
240 bl_label = "Batch Change Math Operation"
242 def draw(self, context):
243 layout = self.layout
244 for type, name, description in operations:
245 props = layout.operator(operators.NWBatchChangeNodes.bl_idname, text=name)
246 props.blend_type = 'CURRENT'
247 props.operation = type
250 class NWCopyToSelectedMenu(Menu, NWBaseMenu):
251 bl_idname = "NODE_MT_nw_copy_node_properties_menu"
252 bl_label = "Copy to Selected"
254 def draw(self, context):
255 layout = self.layout
256 layout.operator(operators.NWCopySettings.bl_idname, text="Settings from Active")
257 layout.menu(NWCopyLabelMenu.bl_idname)
260 class NWCopyLabelMenu(Menu, NWBaseMenu):
261 bl_idname = "NODE_MT_nw_copy_label_menu"
262 bl_label = "Copy Label"
264 def draw(self, context):
265 layout = self.layout
266 layout.operator(operators.NWCopyLabel.bl_idname, text="from Active Node's Label").option = 'FROM_ACTIVE'
267 layout.operator(operators.NWCopyLabel.bl_idname, text="from Linked Node's Label").option = 'FROM_NODE'
268 layout.operator(operators.NWCopyLabel.bl_idname, text="from Linked Output's Name").option = 'FROM_SOCKET'
271 class NWAddReroutesMenu(Menu, NWBaseMenu):
272 bl_idname = "NODE_MT_nw_add_reroutes_menu"
273 bl_label = "Add Reroutes"
274 bl_description = "Add Reroute Nodes to Selected Nodes' Outputs"
276 def draw(self, context):
277 layout = self.layout
278 layout.operator(operators.NWAddReroutes.bl_idname, text="to All Outputs").option = 'ALL'
279 layout.operator(operators.NWAddReroutes.bl_idname, text="to Loose Outputs").option = 'LOOSE'
280 layout.operator(operators.NWAddReroutes.bl_idname, text="to Linked Outputs").option = 'LINKED'
283 class NWLinkActiveToSelectedMenu(Menu, NWBaseMenu):
284 bl_idname = "NODE_MT_nw_link_active_to_selected_menu"
285 bl_label = "Link Active to Selected"
287 def draw(self, context):
288 layout = self.layout
289 layout.menu(NWLinkStandardMenu.bl_idname)
290 layout.menu(NWLinkUseNodeNameMenu.bl_idname)
291 layout.menu(NWLinkUseOutputsNamesMenu.bl_idname)
294 class NWLinkStandardMenu(Menu, NWBaseMenu):
295 bl_idname = "NODE_MT_nw_link_standard_menu"
296 bl_label = "To All Selected"
298 def draw(self, context):
299 layout = self.layout
300 props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
301 props.replace = False
302 props.use_node_name = False
303 props.use_outputs_names = False
304 props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Replace Links")
305 props.replace = True
306 props.use_node_name = False
307 props.use_outputs_names = False
310 class NWLinkUseNodeNameMenu(Menu, NWBaseMenu):
311 bl_idname = "NODE_MT_nw_link_use_node_name_menu"
312 bl_label = "Use Node Name/Label"
314 def draw(self, context):
315 layout = self.layout
316 props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
317 props.replace = False
318 props.use_node_name = True
319 props.use_outputs_names = False
320 props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Replace Links")
321 props.replace = True
322 props.use_node_name = True
323 props.use_outputs_names = False
326 class NWLinkUseOutputsNamesMenu(Menu, NWBaseMenu):
327 bl_idname = "NODE_MT_nw_link_use_outputs_names_menu"
328 bl_label = "Use Outputs Names"
330 def draw(self, context):
331 layout = self.layout
332 props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Don't Replace Links")
333 props.replace = False
334 props.use_node_name = False
335 props.use_outputs_names = True
336 props = layout.operator(operators.NWLinkActiveToSelected.bl_idname, text="Replace Links")
337 props.replace = True
338 props.use_node_name = False
339 props.use_outputs_names = True
342 class NWAttributeMenu(bpy.types.Menu):
343 bl_idname = "NODE_MT_nw_node_attribute_menu"
344 bl_label = "Attributes"
346 @classmethod
347 def poll(cls, context):
348 space = context.space_data
349 return (space.type == 'NODE_EDITOR'
350 and space.node_tree is not None
351 and space.node_tree.library is None
352 and space.tree_type == 'ShaderNodeTree')
354 def draw(self, context):
355 l = self.layout
356 nodes, links = get_nodes_links(context)
357 mat = context.object.active_material
359 objs = []
360 for obj in bpy.data.objects:
361 for slot in obj.material_slots:
362 if slot.material == mat:
363 objs.append(obj)
364 attrs = []
365 for obj in objs:
366 if obj.data.attributes:
367 for attr in obj.data.attributes:
368 if not attr.is_internal:
369 attrs.append(attr.name)
370 attrs = list(set(attrs)) # get a unique list
372 if attrs:
373 for attr in attrs:
374 l.operator(operators.NWAddAttrNode.bl_idname, text=attr).attr_name = attr
375 else:
376 l.label(text="No attributes on objects with this material")
379 class NWSwitchNodeTypeMenu(Menu, NWBaseMenu):
380 bl_idname = "NODE_MT_nw_switch_node_type_menu"
381 bl_label = "Switch Type to..."
383 def draw(self, context):
384 layout = self.layout
385 layout.label(text="This operator is removed due to the changes of node menus.", icon='ERROR')
386 layout.label(text="A native implementation of the function is expected in the future.")
389 # APPENDAGES TO EXISTING UI
393 def select_parent_children_buttons(self, context):
394 layout = self.layout
395 layout.operator(operators.NWSelectParentChildren.bl_idname,
396 text="Select frame's members (children)").option = 'CHILD'
397 layout.operator(operators.NWSelectParentChildren.bl_idname, text="Select parent frame").option = 'PARENT'
400 def attr_nodes_menu_func(self, context):
401 col = self.layout.column(align=True)
402 col.menu("NODE_MT_nw_node_attribute_menu")
403 col.separator()
406 def multipleimages_menu_func(self, context):
407 col = self.layout.column(align=True)
408 col.operator(operators.NWAddMultipleImages.bl_idname, text="Multiple Images")
409 col.operator(operators.NWAddSequence.bl_idname, text="Image Sequence")
410 col.separator()
413 def bgreset_menu_func(self, context):
414 self.layout.operator(operators.NWResetBG.bl_idname)
417 def save_viewer_menu_func(self, context):
418 space = context.space_data
419 if (space.type == 'NODE_EDITOR'
420 and space.node_tree is not None
421 and space.node_tree.library is None
422 and space.tree_type == 'CompositorNodeTree'
423 and context.scene.node_tree.nodes.active
424 and context.scene.node_tree.nodes.active.type == "VIEWER"):
425 self.layout.operator(operators.NWSaveViewer.bl_idname, icon='FILE_IMAGE')
428 def reset_nodes_button(self, context):
429 node_active = context.active_node
430 node_selected = context.selected_nodes
432 # Check if active node is in the selection, ignore some node types
433 if (len(node_selected) != 1
434 or node_active is None
435 or not node_active.select
436 or node_active.type in {"REROUTE", "GROUP"}):
437 return
439 row = self.layout.row()
441 if node_active.type == "FRAME":
442 row.operator(operators.NWResetNodes.bl_idname, text="Reset Nodes in Frame", icon="FILE_REFRESH")
443 else:
444 row.operator(operators.NWResetNodes.bl_idname, text="Reset Node", icon="FILE_REFRESH")
446 self.layout.separator()
449 classes = (
450 NodeWranglerPanel,
451 NodeWranglerMenu,
452 NWMergeNodesMenu,
453 NWMergeGeometryMenu,
454 NWMergeShadersMenu,
455 NWMergeMixMenu,
456 NWConnectionListOutputs,
457 NWConnectionListInputs,
458 NWMergeMathMenu,
459 NWBatchChangeNodesMenu,
460 NWBatchChangeBlendTypeMenu,
461 NWBatchChangeOperationMenu,
462 NWCopyToSelectedMenu,
463 NWCopyLabelMenu,
464 NWAddReroutesMenu,
465 NWLinkActiveToSelectedMenu,
466 NWLinkStandardMenu,
467 NWLinkUseNodeNameMenu,
468 NWLinkUseOutputsNamesMenu,
469 NWAttributeMenu,
470 NWSwitchNodeTypeMenu,
474 def register():
475 from bpy.utils import register_class
476 for cls in classes:
477 register_class(cls)
479 # menu items
480 bpy.types.NODE_MT_select.append(select_parent_children_buttons)
481 bpy.types.NODE_MT_category_shader_input.prepend(attr_nodes_menu_func)
482 bpy.types.NODE_PT_backdrop.append(bgreset_menu_func)
483 bpy.types.NODE_PT_active_node_generic.append(save_viewer_menu_func)
484 bpy.types.NODE_MT_category_shader_texture.prepend(multipleimages_menu_func)
485 bpy.types.NODE_MT_category_compositor_input.prepend(multipleimages_menu_func)
486 bpy.types.NODE_PT_active_node_generic.prepend(reset_nodes_button)
487 bpy.types.NODE_MT_node.prepend(reset_nodes_button)
490 def unregister():
491 # menu items
492 bpy.types.NODE_MT_select.remove(select_parent_children_buttons)
493 bpy.types.NODE_MT_category_shader_input.remove(attr_nodes_menu_func)
494 bpy.types.NODE_PT_backdrop.remove(bgreset_menu_func)
495 bpy.types.NODE_PT_active_node_generic.remove(save_viewer_menu_func)
496 bpy.types.NODE_MT_category_shader_texture.remove(multipleimages_menu_func)
497 bpy.types.NODE_MT_category_compositor_input.remove(multipleimages_menu_func)
498 bpy.types.NODE_PT_active_node_generic.remove(reset_nodes_button)
499 bpy.types.NODE_MT_node.remove(reset_nodes_button)
501 from bpy.utils import unregister_class
502 for cls in classes:
503 unregister_class(cls)