Update scripts to account for removal of the context override to bpy.ops
[blender-addons.git] / node_presets.py
blobae43a2eb8d86119a8f73316f2c2b00c4e71427c6
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 "name": "Node Presets",
5 "description": "Useful and time-saving tools for node group workflow",
6 "author": "Campbell Barton",
7 "version": (1, 1),
8 "blender": (2, 80, 0),
9 "location": "Node Editors > Add > Template",
10 "description": "Add node groups directly to the node editors",
11 "warning": "",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/node/node_presets.html",
13 "category": "Node",
16 import os
17 import bpy
18 from bpy.types import (
19 Operator,
20 Menu,
21 AddonPreferences,
24 from bpy.props import (
25 StringProperty,
29 # -----------------------------------------------------------------------------
30 # Node Adding Operator
33 def node_center(context):
34 from mathutils import Vector
35 loc = Vector((0.0, 0.0))
36 node_selected = context.selected_nodes
37 if node_selected:
38 for node in node_selected:
39 loc += node.location
40 loc /= len(node_selected)
41 return loc
44 def node_template_add(context, filepath, node_group, ungroup, report):
45 """ Main function
46 """
48 space = context.space_data
49 node_tree = space.node_tree
50 node_active = context.active_node
51 node_selected = context.selected_nodes
53 if node_tree is None:
54 report({'ERROR'}, "No node tree available")
55 return
57 with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
58 assert(node_group in data_from.node_groups)
59 data_to.node_groups = [node_group]
60 node_group = data_to.node_groups[0]
62 # add node!
63 center = node_center(context)
65 for node in node_tree.nodes:
66 node.select = False
68 node_type_string = {
69 "ShaderNodeTree": "ShaderNodeGroup",
70 "CompositorNodeTree": "CompositorNodeGroup",
71 "TextureNodeTree": "TextureNodeGroup",
72 "GeometryNodeTree": "GeometryNodeGroup",
73 }[type(node_tree).__name__]
75 node = node_tree.nodes.new(type=node_type_string)
76 node.node_tree = node_group
78 is_fail = (node.node_tree is None)
79 if is_fail:
80 report({'WARNING'}, "Incompatible node type")
82 node.select = True
83 node_tree.nodes.active = node
84 node.location = center
86 if is_fail:
87 node_tree.nodes.remove(node)
88 else:
89 if ungroup:
90 bpy.ops.node.group_ungroup()
92 # node_group.user_clear()
93 # bpy.data.node_groups.remove(node_group)
96 # -----------------------------------------------------------------------------
97 # Node Template Prefs
99 def node_search_path(context):
100 preferences = context.preferences
101 addon_prefs = preferences.addons[__name__].preferences
102 dirpath = addon_prefs.search_path
103 return dirpath
106 class NodeTemplatePrefs(AddonPreferences):
107 bl_idname = __name__
109 search_path: StringProperty(
110 name="Directory of blend files with node-groups",
111 subtype='DIR_PATH',
114 def draw(self, context):
115 layout = self.layout
116 layout.prop(self, "search_path")
119 class NODE_OT_template_add(Operator):
120 """Add a node template"""
121 bl_idname = "node.template_add"
122 bl_label = "Add node group template"
123 bl_description = "Add node group template"
124 bl_options = {'REGISTER', 'UNDO'}
126 filepath: StringProperty(
127 subtype='FILE_PATH',
129 group_name: StringProperty()
131 def execute(self, context):
132 node_template_add(context, self.filepath, self.group_name, True, self.report)
134 return {'FINISHED'}
136 def invoke(self, context, event):
137 node_template_add(context, self.filepath, self.group_name, event.shift, self.report)
139 return {'FINISHED'}
142 # -----------------------------------------------------------------------------
143 # Node menu list
145 def node_template_cache(context, *, reload=False):
146 dirpath = node_search_path(context)
148 if node_template_cache._node_cache_path != dirpath:
149 reload = True
151 node_cache = node_template_cache._node_cache
152 if reload:
153 node_cache = []
154 if node_cache:
155 return node_cache
157 for fn in os.listdir(dirpath):
158 if fn.endswith(".blend"):
159 filepath = os.path.join(dirpath, fn)
160 with bpy.data.libraries.load(filepath) as (data_from, data_to):
161 for group_name in data_from.node_groups:
162 if not group_name.startswith("_"):
163 node_cache.append((filepath, group_name))
165 node_template_cache._node_cache = node_cache
166 node_template_cache._node_cache_path = dirpath
168 return node_cache
171 node_template_cache._node_cache = []
172 node_template_cache._node_cache_path = ""
175 class NODE_MT_template_add(Menu):
176 bl_label = "Node Template"
178 def draw(self, context):
179 layout = self.layout
181 dirpath = node_search_path(context)
182 if dirpath == "":
183 layout.label(text="Set search dir in the addon-prefs")
184 return
186 try:
187 node_items = node_template_cache(context)
188 except Exception as ex:
189 node_items = ()
190 layout.label(text=repr(ex), icon='ERROR')
192 for filepath, group_name in node_items:
193 props = layout.operator(
194 NODE_OT_template_add.bl_idname,
195 text=group_name,
197 props.filepath = filepath
198 props.group_name = group_name
201 def add_node_button(self, context):
202 self.layout.menu(
203 NODE_MT_template_add.__name__,
204 text="Template",
205 icon='PLUGIN',
209 classes = (
210 NODE_OT_template_add,
211 NODE_MT_template_add,
212 NodeTemplatePrefs
216 def register():
217 for cls in classes:
218 bpy.utils.register_class(cls)
220 bpy.types.NODE_MT_add.append(add_node_button)
223 def unregister():
224 for cls in classes:
225 bpy.utils.unregister_class(cls)
227 bpy.types.NODE_MT_add.remove(add_node_button)
230 if __name__ == "__main__":
231 register()