1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 "name": "Node Presets",
7 "description": "Useful and time-saving tools for node group workflow",
8 "author": "Campbell Barton",
10 "blender": (2, 80, 0),
11 "location": "Node Editors > Add > Template",
12 "description": "Add node groups directly to the node editors",
14 "doc_url": "{BLENDER_MANUAL_URL}/addons/node/node_presets.html",
20 from bpy
.types
import (
26 from bpy
.props
import (
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
40 for node
in node_selected
:
42 loc
/= len(node_selected
)
46 def node_template_add(context
, filepath
, node_group
, ungroup
, report
):
50 space
= context
.space_data
51 node_tree
= space
.node_tree
52 node_active
= context
.active_node
53 node_selected
= context
.selected_nodes
56 report({'ERROR'}, "No node tree available")
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]
65 center
= node_center(context
)
67 for node
in node_tree
.nodes
:
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)
82 report({'WARNING'}, "Incompatible node type")
85 node_tree
.nodes
.active
= node
86 node
.location
= center
89 node_tree
.nodes
.remove(node
)
92 bpy
.ops
.node
.group_ungroup()
94 # node_group.user_clear()
95 # bpy.data.node_groups.remove(node_group)
98 # -----------------------------------------------------------------------------
101 def node_search_path(context
):
102 preferences
= context
.preferences
103 addon_prefs
= preferences
.addons
[__name__
].preferences
104 dirpath
= addon_prefs
.search_path
108 class NodeTemplatePrefs(AddonPreferences
):
111 search_path
: StringProperty(
112 name
="Directory of blend files with node-groups",
116 def draw(self
, context
):
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(
131 group_name
: StringProperty()
133 def execute(self
, context
):
134 node_template_add(context
, self
.filepath
, self
.group_name
, True, self
.report
)
138 def invoke(self
, context
, event
):
139 node_template_add(context
, self
.filepath
, self
.group_name
, event
.shift
, self
.report
)
144 # -----------------------------------------------------------------------------
147 def node_template_cache(context
, *, reload=False):
148 dirpath
= node_search_path(context
)
150 if node_template_cache
._node
_cache
_path
!= dirpath
:
153 node_cache
= node_template_cache
._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
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
):
183 dirpath
= node_search_path(context
)
185 layout
.label(text
="Set search dir in the addon-prefs")
189 node_items
= node_template_cache(context
)
190 except Exception as ex
:
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
,
199 props
.filepath
= filepath
200 props
.group_name
= group_name
203 def add_node_button(self
, context
):
205 NODE_MT_template_add
.__name
__,
212 NODE_OT_template_add
,
213 NODE_MT_template_add
,
220 bpy
.utils
.register_class(cls
)
222 bpy
.types
.NODE_MT_add
.append(add_node_button
)
227 bpy
.utils
.unregister_class(cls
)
229 bpy
.types
.NODE_MT_add
.remove(add_node_button
)
232 if __name__
== "__main__":