Cleanup: Node Wrangler: capitalize comments in AddReroutes operator
[blender-addons.git] / node_presets.py
blob92293d72dfc34f2aa4f997acf5ae7c4e844a9f84
1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "Node Presets",
7 "description": "Useful and time-saving tools for node group workflow",
8 "author": "Campbell Barton",
9 "version": (1, 1),
10 "blender": (2, 80, 0),
11 "location": "Node Editors > Add > Template",
12 "description": "Add node groups directly to the node editors",
13 "warning": "",
14 "doc_url": "{BLENDER_MANUAL_URL}/addons/node/node_presets.html",
15 "category": "Node",
18 import os
19 import bpy
20 from bpy.types import (
21 Operator,
22 Menu,
23 AddonPreferences,
26 from bpy.props import (
27 StringProperty,
31 # -----------------------------------------------------------------------------
32 # Node Adding Operator
35 def node_center(context):
36 from mathutils import Vector
37 loc = Vector((0.0, 0.0))
38 node_selected = context.selected_nodes
39 if node_selected:
40 for node in node_selected:
41 loc += node.location
42 loc /= len(node_selected)
43 return loc
46 def node_template_add(context, filepath, node_group, ungroup, report):
47 """ Main function
48 """
50 space = context.space_data
51 node_tree = space.node_tree
52 node_active = context.active_node
53 node_selected = context.selected_nodes
55 if node_tree is None:
56 report({'ERROR'}, "No node tree available")
57 return
59 with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
60 assert(node_group in data_from.node_groups)
61 data_to.node_groups = [node_group]
62 node_group = data_to.node_groups[0]
64 # add node!
65 center = node_center(context)
67 for node in node_tree.nodes:
68 node.select = False
70 node_type_string = {
71 "ShaderNodeTree": "ShaderNodeGroup",
72 "CompositorNodeTree": "CompositorNodeGroup",
73 "TextureNodeTree": "TextureNodeGroup",
74 "GeometryNodeTree": "GeometryNodeGroup",
75 }[type(node_tree).__name__]
77 node = node_tree.nodes.new(type=node_type_string)
78 node.node_tree = node_group
80 is_fail = (node.node_tree is None)
81 if is_fail:
82 report({'WARNING'}, "Incompatible node type")
84 node.select = True
85 node_tree.nodes.active = node
86 node.location = center
88 if is_fail:
89 node_tree.nodes.remove(node)
90 else:
91 if ungroup:
92 bpy.ops.node.group_ungroup()
94 # node_group.user_clear()
95 # bpy.data.node_groups.remove(node_group)
98 # -----------------------------------------------------------------------------
99 # Node Template Prefs
101 def node_search_path(context):
102 preferences = context.preferences
103 addon_prefs = preferences.addons[__name__].preferences
104 dirpath = addon_prefs.search_path
105 return dirpath
108 class NodeTemplatePrefs(AddonPreferences):
109 bl_idname = __name__
111 search_path: StringProperty(
112 name="Directory of blend files with node-groups",
113 subtype='DIR_PATH',
116 def draw(self, context):
117 layout = self.layout
118 layout.prop(self, "search_path")
121 class NODE_OT_template_add(Operator):
122 """Add a node template"""
123 bl_idname = "node.template_add"
124 bl_label = "Add node group template"
125 bl_description = "Add node group template"
126 bl_options = {'REGISTER', 'UNDO'}
128 filepath: StringProperty(
129 subtype='FILE_PATH',
131 group_name: StringProperty()
133 def execute(self, context):
134 node_template_add(context, self.filepath, self.group_name, True, self.report)
136 return {'FINISHED'}
138 def invoke(self, context, event):
139 node_template_add(context, self.filepath, self.group_name, event.shift, self.report)
141 return {'FINISHED'}
144 # -----------------------------------------------------------------------------
145 # Node menu list
147 def node_template_cache(context, *, reload=False):
148 dirpath = node_search_path(context)
150 if node_template_cache._node_cache_path != dirpath:
151 reload = True
153 node_cache = node_template_cache._node_cache
154 if reload:
155 node_cache = []
156 if node_cache:
157 return node_cache
159 for fn in os.listdir(dirpath):
160 if fn.endswith(".blend"):
161 filepath = os.path.join(dirpath, fn)
162 with bpy.data.libraries.load(filepath) as (data_from, data_to):
163 for group_name in data_from.node_groups:
164 if not group_name.startswith("_"):
165 node_cache.append((filepath, group_name))
167 node_template_cache._node_cache = node_cache
168 node_template_cache._node_cache_path = dirpath
170 return node_cache
173 node_template_cache._node_cache = []
174 node_template_cache._node_cache_path = ""
177 class NODE_MT_template_add(Menu):
178 bl_label = "Node Template"
180 def draw(self, context):
181 layout = self.layout
183 dirpath = node_search_path(context)
184 if dirpath == "":
185 layout.label(text="Set search dir in the addon-prefs")
186 return
188 try:
189 node_items = node_template_cache(context)
190 except Exception as ex:
191 node_items = ()
192 layout.label(text=repr(ex), icon='ERROR')
194 for filepath, group_name in node_items:
195 props = layout.operator(
196 NODE_OT_template_add.bl_idname,
197 text=group_name,
199 props.filepath = filepath
200 props.group_name = group_name
203 def add_node_button(self, context):
204 self.layout.menu(
205 NODE_MT_template_add.__name__,
206 text="Template",
207 icon='PLUGIN',
211 classes = (
212 NODE_OT_template_add,
213 NODE_MT_template_add,
214 NodeTemplatePrefs
218 def register():
219 for cls in classes:
220 bpy.utils.register_class(cls)
222 bpy.types.NODE_MT_add.append(add_node_button)
225 def unregister():
226 for cls in classes:
227 bpy.utils.unregister_class(cls)
229 bpy.types.NODE_MT_add.remove(add_node_button)
232 if __name__ == "__main__":
233 register()