1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
20 "name": "Node Presets",
21 "description": "Useful and time-saving tools for node group workflow",
22 "author": "Campbell Barton",
24 "blender": (2, 80, 0),
25 "location": "Node Editors > Add > Template",
26 "description": "Add node groups directly to the node editors",
28 "doc_url": "{BLENDER_MANUAL_URL}/addons/node/node_presets.html",
34 from bpy
.types
import (
40 from bpy
.props
import (
45 # -----------------------------------------------------------------------------
46 # Node Adding Operator
49 def node_center(context
):
50 from mathutils
import Vector
51 loc
= Vector((0.0, 0.0))
52 node_selected
= context
.selected_nodes
54 for node
in node_selected
:
56 loc
/= len(node_selected
)
60 def node_template_add(context
, filepath
, node_group
, ungroup
, report
):
64 space
= context
.space_data
65 node_tree
= space
.node_tree
66 node_active
= context
.active_node
67 node_selected
= context
.selected_nodes
70 report({'ERROR'}, "No node tree available")
73 with bpy
.data
.libraries
.load(filepath
, link
=False) as (data_from
, data_to
):
74 assert(node_group
in data_from
.node_groups
)
75 data_to
.node_groups
= [node_group
]
76 node_group
= data_to
.node_groups
[0]
79 center
= node_center(context
)
81 for node
in node_tree
.nodes
:
85 "ShaderNodeTree": "ShaderNodeGroup",
86 "CompositorNodeTree": "CompositorNodeGroup",
87 "TextureNodeTree": "TextureNodeGroup",
88 }[type(node_tree
).__name
__]
90 node
= node_tree
.nodes
.new(type=node_type_string
)
91 node
.node_tree
= node_group
93 is_fail
= (node
.node_tree
is None)
95 report({'WARNING'}, "Incompatible node type")
98 node_tree
.nodes
.active
= node
99 node
.location
= center
102 node_tree
.nodes
.remove(node
)
105 bpy
.ops
.node
.group_ungroup()
107 # node_group.user_clear()
108 # bpy.data.node_groups.remove(node_group)
111 # -----------------------------------------------------------------------------
112 # Node Template Prefs
114 def node_search_path(context
):
115 preferences
= context
.preferences
116 addon_prefs
= preferences
.addons
[__name__
].preferences
117 dirpath
= addon_prefs
.search_path
121 class NodeTemplatePrefs(AddonPreferences
):
124 search_path
: StringProperty(
125 name
="Directory of blend files with node-groups",
129 def draw(self
, context
):
131 layout
.prop(self
, "search_path")
134 class NODE_OT_template_add(Operator
):
135 """Add a node template"""
136 bl_idname
= "node.template_add"
137 bl_label
= "Add node group template"
138 bl_description
= "Add node group template"
139 bl_options
= {'REGISTER', 'UNDO'}
141 filepath
: StringProperty(
144 group_name
: StringProperty()
146 def execute(self
, context
):
147 node_template_add(context
, self
.filepath
, self
.group_name
, True, self
.report
)
151 def invoke(self
, context
, event
):
152 node_template_add(context
, self
.filepath
, self
.group_name
, event
.shift
, self
.report
)
157 # -----------------------------------------------------------------------------
160 def node_template_cache(context
, *, reload=False):
161 dirpath
= node_search_path(context
)
163 if node_template_cache
._node
_cache
_path
!= dirpath
:
166 node_cache
= node_template_cache
._node
_cache
172 for fn
in os
.listdir(dirpath
):
173 if fn
.endswith(".blend"):
174 filepath
= os
.path
.join(dirpath
, fn
)
175 with bpy
.data
.libraries
.load(filepath
) as (data_from
, data_to
):
176 for group_name
in data_from
.node_groups
:
177 if not group_name
.startswith("_"):
178 node_cache
.append((filepath
, group_name
))
180 node_template_cache
._node
_cache
= node_cache
181 node_template_cache
._node
_cache
_path
= dirpath
186 node_template_cache
._node
_cache
= []
187 node_template_cache
._node
_cache
_path
= ""
190 class NODE_MT_template_add(Menu
):
191 bl_label
= "Node Template"
193 def draw(self
, context
):
196 dirpath
= node_search_path(context
)
198 layout
.label(text
="Set search dir in the addon-prefs")
202 node_items
= node_template_cache(context
)
203 except Exception as ex
:
205 layout
.label(text
=repr(ex
), icon
='ERROR')
207 for filepath
, group_name
in node_items
:
208 props
= layout
.operator(
209 NODE_OT_template_add
.bl_idname
,
212 props
.filepath
= filepath
213 props
.group_name
= group_name
216 def add_node_button(self
, context
):
218 NODE_MT_template_add
.__name
__,
225 NODE_OT_template_add
,
226 NODE_MT_template_add
,
233 bpy
.utils
.register_class(cls
)
235 bpy
.types
.NODE_MT_add
.append(add_node_button
)
240 bpy
.utils
.unregister_class(cls
)
242 bpy
.types
.NODE_MT_add
.remove(add_node_button
)
245 if __name__
== "__main__":