1 # SPDX-License-Identifier: GPL-2.0-or-later
4 "name": "Node Presets",
5 "description": "Useful and time-saving tools for node group workflow",
6 "author": "Campbell Barton",
9 "location": "Node Editors > Add > Template",
10 "description": "Add node groups directly to the node editors",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/node/node_presets.html",
18 from bpy
.types
import (
24 from bpy
.props
import (
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
38 for node
in node_selected
:
40 loc
/= len(node_selected
)
44 def node_template_add(context
, filepath
, node_group
, ungroup
, report
):
48 space
= context
.space_data
49 node_tree
= space
.node_tree
50 node_active
= context
.active_node
51 node_selected
= context
.selected_nodes
54 report({'ERROR'}, "No node tree available")
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]
63 center
= node_center(context
)
65 for node
in node_tree
.nodes
:
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)
80 report({'WARNING'}, "Incompatible node type")
83 node_tree
.nodes
.active
= node
84 node
.location
= center
87 node_tree
.nodes
.remove(node
)
90 bpy
.ops
.node
.group_ungroup()
92 # node_group.user_clear()
93 # bpy.data.node_groups.remove(node_group)
96 # -----------------------------------------------------------------------------
99 def node_search_path(context
):
100 preferences
= context
.preferences
101 addon_prefs
= preferences
.addons
[__name__
].preferences
102 dirpath
= addon_prefs
.search_path
106 class NodeTemplatePrefs(AddonPreferences
):
109 search_path
: StringProperty(
110 name
="Directory of blend files with node-groups",
114 def draw(self
, context
):
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(
129 group_name
: StringProperty()
131 def execute(self
, context
):
132 node_template_add(context
, self
.filepath
, self
.group_name
, True, self
.report
)
136 def invoke(self
, context
, event
):
137 node_template_add(context
, self
.filepath
, self
.group_name
, event
.shift
, self
.report
)
142 # -----------------------------------------------------------------------------
145 def node_template_cache(context
, *, reload=False):
146 dirpath
= node_search_path(context
)
148 if node_template_cache
._node
_cache
_path
!= dirpath
:
151 node_cache
= node_template_cache
._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
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
):
181 dirpath
= node_search_path(context
)
183 layout
.label(text
="Set search dir in the addon-prefs")
187 node_items
= node_template_cache(context
)
188 except Exception as ex
:
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
,
197 props
.filepath
= filepath
198 props
.group_name
= group_name
201 def add_node_button(self
, context
):
203 NODE_MT_template_add
.__name
__,
210 NODE_OT_template_add
,
211 NODE_MT_template_add
,
218 bpy
.utils
.register_class(cls
)
220 bpy
.types
.NODE_MT_add
.append(add_node_button
)
225 bpy
.utils
.unregister_class(cls
)
227 bpy
.types
.NODE_MT_add
.remove(add_node_button
)
230 if __name__
== "__main__":