Created
December 13, 2022 00:34
-
-
Save strike-digital/42b04de40b3eff0da6b3788550132be9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright (C) 2021 'BD3D DIGITAL DESIGN, SLU' | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
bl_info = { | |
"name": "'Group Wrapper' for Geometry-Node", | |
"author": "Andrew Stevenson, BD3D", | |
"description": "wrap a node group", | |
"blender": (3, 3, 0), | |
"version": (1, 1, 0), | |
"location": "Node Editor > Geometry Node > Add Menu > Extra", | |
"warning": "", | |
"tracker_url": "https://devtalk.blender.org/t/extra-nodes-for-geometrynodes/20942", | |
"category": "Node", | |
} | |
""" | |
# About creating GeometryNodeCustomGroup: | |
>Here are the possibilities: | |
- you can either create a custom interface that interact with a nodegroup | |
- or create simple input node, this plugin is only creating input values. all boiler plate below is dedicated to output sockets. | |
> if you want to process data, forget about it: | |
- currently there's no way to get the value out of a socket, not sure how they could translate field to python. | |
- writing simple output value is possible, forget about fields tho. | |
> update management is not ideal | |
- socket_value_update() function should send us signal when a socket value is being updated, the api is dead for now | |
- to update, rely on handlers or msgbus ( https://docs.blender.org/api/blender2.8/bpy.app.handlers.html?highlight=handler#module-bpy.app.handlers ) | |
> socket.type is read only, everything is hardcoded in operators | |
- to change socket type, we forced to use operator `bpy.ops.node.tree_socket_change_type(in_out='IN', socket_type='')` + context 'override'. this is far from ideal. | |
this means that changing socket type outside the node editor context is not possible. | |
> in order to change the default value of an output, nodegroup.outputs[n].default value won't work use, api is confusing, it is done via the nodegroup.nodes instead: | |
- nodegroup.nodes["Group Output"].inputs[n].default_value ->see boiler plate functions i wrote below | |
> Warning `node_groups[x].outputs.new("NodeSocketBool","test")` is tricky, type need to be exact, no warning upon error, will just return none | |
About this script: | |
>You will note that there is an extra attention to detail in order to not register handlers twice | |
>You will note that there is an extra attention in the extension of the Add menu with this new 'Extra' category. | |
In, my opinion all plugin nodes should be in this "Extra" menu. | |
Feel free to reuse the menu registration snippets so all custom node makers can share the 'Extra' menu without confilcts. | |
""" | |
import bpy | |
################################################## | |
# BOILER PLATE | |
################################################## | |
def get_socket_value(ng, idx): | |
return ng.nodes["Group Output"].inputs[idx].default_value | |
def set_socket_value(ng, idx, value=None): | |
"""Useful if you just want to output a single value from an output socket""" | |
ng.nodes["Group Output"].inputs[idx].default_value = value | |
return ng.nodes["Group Output"].inputs[idx].default_value | |
def set_socket_label(ng, idx, label=None): | |
ng.outputs[idx].name = str(label) | |
return None | |
def get_socket_type(ng, idx): | |
return ng.outputs[idx].type | |
def create_socket(ng, socket_type="NodeSocketFloat", socket_name="Value"): | |
socket = ng.outputs.new(socket_type, socket_name) | |
return socket | |
def remove_socket(ng, idx): | |
todel = ng.outputs[idx] | |
ng.outputs.remove(todel) | |
return None | |
################################################## | |
# CUSTOM NODE | |
################################################## | |
""" | |
This is the main custom node group class. If you want it to wrap one of your own node groups from another file, | |
I suggest having a look a appending (there is a very good scripting for artists video on it) with python, and then | |
you can just set the node tree in the init function. | |
""" | |
class EXTRANODEGROUPWRAPPER_NG_group_wrapper(bpy.types.GeometryNodeCustomGroup): | |
bl_idname = "GeometryNodeGroupWrapper" | |
bl_label = "Group Wrapper" | |
@classmethod | |
def poll(cls, context): #mandatory with geonode | |
"""If this returns False, when trying to add the node from an operator you will get an error.""" | |
return True | |
def init(self, context): | |
"""this is run when appending the node for the first time""" | |
self.width = 250 | |
# here you could set the node group you want to wrap on startup | |
# self.node_tree = ... | |
def copy(self, node): | |
"""Run when dupplicating the node""" | |
self.node_tree = node.node_tree.copy() | |
def update(self): | |
"""generic update function. Called when the node tree is updated""" | |
pass | |
def draw_label(self,): | |
"""The return result of this is used shown as the label of the node""" | |
if not self.node_tree: | |
return "Pick a group!" | |
return self.node_tree.name if self.node_tree.name else "" | |
def draw_buttons(self, context, layout): | |
"""This is where you can draw custom properties for your node""" | |
# remove this if you don't want people to be able to change which node is selected | |
layout.template_ID(self, "node_tree") | |
################################################## | |
# EXTEND MENU | |
################################################## | |
""" | |
Everything in this section is to do with adding a custom menu to the add menu, | |
But it's a lot more complicated than you would probably need, since it has to take into account | |
Multiple scripts trying to add the same menu. You could just remove this and add each node to your own | |
custom menu, which would be much easier. | |
""" | |
def extra_geonode_menu(self, context): | |
"""extend NODE_MT_add with new extra menu""" | |
self.layout.menu("NODE_MT_category_GEO_EXTRA", text="Extra") | |
return None | |
class NODE_MT_category_GEO_EXTRA(bpy.types.Menu): | |
bl_idname = "NODE_MT_category_GEO_EXTRA" | |
bl_label = "" | |
@classmethod | |
def poll(cls, context): | |
return (bpy.context.space_data.tree_type == "GeometryNodeTree") | |
def draw(self, context): | |
return None | |
#extra menu extension | |
def extra_node_group_wrapper(self, context): | |
"""extend extra menu with new node""" | |
op = self.layout.operator("node.add_node", text="Group Wrapper") | |
op.type = "GeometryNodeGroupWrapper" | |
op.use_transform = True | |
#register | |
def register_menus(status): | |
"""register extra menu, if not already, append item, if not already""" | |
if (status == "register"): | |
#register new extra menu class if not exists already, perhaps another plugin already implemented it | |
if "NODE_MT_category_GEO_EXTRA" not in bpy.types.__dir__(): | |
bpy.utils.register_class(NODE_MT_category_GEO_EXTRA) | |
#extend add menu with extra menu if not already, _dyn_ui_initialize() will get appended drawing functions of a menu | |
add_menu = bpy.types.NODE_MT_add | |
if "extra_geonode_menu" not in [f.__name__ for f in add_menu._dyn_ui_initialize()]: | |
add_menu.append(extra_geonode_menu) | |
#extend extra menu with our custom nodes if not already | |
extra_menu = bpy.types.NODE_MT_category_GEO_EXTRA | |
if "extra_node_group_wrapper" not in [f.__name__ for f in extra_menu._dyn_ui_initialize()]: | |
extra_menu.append(extra_node_group_wrapper) | |
return None | |
elif (status == "unregister"): | |
add_menu = bpy.types.NODE_MT_add | |
extra_menu = bpy.types.NODE_MT_category_GEO_EXTRA | |
#remove our custom function to extra menu | |
for f in extra_menu._dyn_ui_initialize().copy(): | |
if (f.__name__ == "extra_node_group_wrapper"): | |
extra_menu.remove(f) | |
#if extra menu is empty | |
if len(extra_menu._dyn_ui_initialize()) == 1: | |
#remove our extra menu item draw fct add menu | |
for f in add_menu._dyn_ui_initialize().copy(): | |
if (f.__name__ == "extra_geonode_menu"): | |
add_menu.remove(f) | |
#unregister extra menu | |
bpy.utils.unregister_class(extra_menu) | |
return None | |
################################################## | |
# PROPERTIES & PREFS | |
################################################## | |
# The preferences of this addon | |
class EXTRANODEGROUPWRAPPER_AddonPref(bpy.types.AddonPreferences): | |
"""addon_prefs = bpy.context.preferences.addons["extra_node_group_wrapper"].preferences""" | |
bl_idname = "extra_node_group_wrapper" | |
#drawing part in ui module | |
# def draw(self, context): | |
# layout = self.layout | |
# box = layout.box() | |
# return None | |
################################################## | |
# INIT REGISTRATION | |
################################################## | |
classes = [ | |
EXTRANODEGROUPWRAPPER_AddonPref, | |
EXTRANODEGROUPWRAPPER_NG_group_wrapper, | |
] | |
def register(): | |
#classes | |
for cls in classes: | |
bpy.utils.register_class(cls) | |
#extend add menu | |
register_menus("register") | |
return None | |
def unregister(): | |
#extend add menu | |
register_menus("unregister") | |
#classes | |
for cls in reversed(classes): | |
bpy.utils.unregister_class(cls) | |
return None | |
if __name__ == "__main__": | |
try: | |
unregister() | |
except: | |
pass | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment