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; version 2
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 "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
29 "node/node_presets.html",
35 from bpy
.types
import (
41 from bpy
.props
import (
46 # -----------------------------------------------------------------------------
47 # Node Adding Operator
50 def node_center(context
):
51 from mathutils
import Vector
52 loc
= Vector((0.0, 0.0))
53 node_selected
= context
.selected_nodes
55 for node
in node_selected
:
57 loc
/= len(node_selected
)
61 def node_template_add(context
, filepath
, node_group
, ungroup
, report
):
65 space
= context
.space_data
66 node_tree
= space
.node_tree
67 node_active
= context
.active_node
68 node_selected
= context
.selected_nodes
71 report({'ERROR'}, "No node tree available")
74 with bpy
.data
.libraries
.load(filepath
, link
=False) as (data_from
, data_to
):
75 assert(node_group
in data_from
.node_groups
)
76 data_to
.node_groups
= [node_group
]
77 node_group
= data_to
.node_groups
[0]
80 center
= node_center(context
)
82 for node
in node_tree
.nodes
:
86 "ShaderNodeTree": "ShaderNodeGroup",
87 "CompositorNodeTree": "CompositorNodeGroup",
88 "TextureNodeTree": "TextureNodeGroup",
89 }[type(node_tree
).__name
__]
91 node
= node_tree
.nodes
.new(type=node_type_string
)
92 node
.node_tree
= node_group
94 is_fail
= (node
.node_tree
is None)
96 report({'WARNING'}, "Incompatible node type")
99 node_tree
.nodes
.active
= node
100 node
.location
= center
103 node_tree
.nodes
.remove(node
)
106 bpy
.ops
.node
.group_ungroup()
108 # node_group.user_clear()
109 # bpy.data.node_groups.remove(node_group)
112 # -----------------------------------------------------------------------------
113 # Node Template Prefs
115 def node_search_path(context
):
116 preferences
= context
.preferences
117 addon_prefs
= preferences
.addons
[__name__
].preferences
118 dirpath
= addon_prefs
.search_path
122 class NodeTemplatePrefs(AddonPreferences
):
125 search_path
: StringProperty(
126 name
="Directory of blend files with node-groups",
130 def draw(self
, context
):
132 layout
.prop(self
, "search_path")
135 class NODE_OT_template_add(Operator
):
136 """Add a node template"""
137 bl_idname
= "node.template_add"
138 bl_label
= "Add node group template"
139 bl_description
= "Add node group template"
140 bl_options
= {'REGISTER', 'UNDO'}
142 filepath
: StringProperty(
145 group_name
: StringProperty()
147 def execute(self
, context
):
148 node_template_add(context
, self
.filepath
, self
.group_name
, True, self
.report
)
152 def invoke(self
, context
, event
):
153 node_template_add(context
, self
.filepath
, self
.group_name
, event
.shift
, self
.report
)
158 # -----------------------------------------------------------------------------
161 def node_template_cache(context
, *, reload=False):
162 dirpath
= node_search_path(context
)
164 if node_template_cache
._node
_cache
_path
!= dirpath
:
167 node_cache
= node_template_cache
._node
_cache
173 for fn
in os
.listdir(dirpath
):
174 if fn
.endswith(".blend"):
175 filepath
= os
.path
.join(dirpath
, fn
)
176 with bpy
.data
.libraries
.load(filepath
) as (data_from
, data_to
):
177 for group_name
in data_from
.node_groups
:
178 if not group_name
.startswith("_"):
179 node_cache
.append((filepath
, group_name
))
181 node_template_cache
._node
_cache
= node_cache
182 node_template_cache
._node
_cache
_path
= dirpath
187 node_template_cache
._node
_cache
= []
188 node_template_cache
._node
_cache
_path
= ""
191 class NODE_MT_template_add(Menu
):
192 bl_label
= "Node Template"
194 def draw(self
, context
):
197 dirpath
= node_search_path(context
)
199 layout
.label(text
="Set search dir in the addon-prefs")
203 node_items
= node_template_cache(context
)
204 except Exception as ex
:
206 layout
.label(text
=repr(ex
), icon
='ERROR')
208 for filepath
, group_name
in node_items
:
209 props
= layout
.operator(
210 NODE_OT_template_add
.bl_idname
,
213 props
.filepath
= filepath
214 props
.group_name
= group_name
217 def add_node_button(self
, context
):
219 NODE_MT_template_add
.__name
__,
226 NODE_OT_template_add
,
227 NODE_MT_template_add
,
234 bpy
.utils
.register_class(cls
)
236 bpy
.types
.NODE_MT_add
.append(add_node_button
)
241 bpy
.utils
.unregister_class(cls
)
243 bpy
.types
.NODE_MT_add
.remove(add_node_button
)
246 if __name__
== "__main__":