Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support of node groups in Blender 4.0 #5013

Merged
merged 3 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 43 additions & 15 deletions core/node_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ def update(self):
if 'init_tree' in self.id_data: # tree is building by a script - let it do this
return

self.check_last_socket() # Should not be too expensive to call it each update
if bpy.app.version < (4, 0): # https://projects.blender.org/blender/blender/issues/113134
self.check_last_socket() # Should not be too expensive to call it each update

if self.name not in bpy.data.node_groups: # load new file event
return
Expand All @@ -142,14 +143,14 @@ def update(self):
def update_sockets(self): # todo it lets simplify sockets API
"""Set properties of sockets of parent nodes and of output modes"""
for node in self.parent_nodes():
for n_in_s, t_in_s in zip(node.inputs, self.inputs):
for n_in_s, t_in_s in zip(node.inputs, self.sockets('INPUTS')):
# also before getting data from socket `socket.use_prop` property should be set
if hasattr(n_in_s, 'default_property'):
n_in_s.use_prop = not t_in_s.hide_value
if hasattr(t_in_s, 'default_type'):
n_in_s.default_property_type = t_in_s.default_type
for out_node in (n for n in self.nodes if n.bl_idname == 'NodeGroupOutput'):
for n_in_s, t_out_s in zip(out_node.inputs, self.outputs):
for n_in_s, t_out_s in zip(out_node.inputs, self.sockets('OUTPUTS')):
if hasattr(n_in_s, 'default_property'):
n_in_s.use_prop = not t_out_s.hide_value
if hasattr(t_out_s, 'default_type'):
Expand Down Expand Up @@ -269,6 +270,15 @@ def valid_socket_type(cls, socket_type: str):
# https://docs.blender.org/api/master/bpy.types.NodeTree.html#bpy.types.NodeTree.valid_socket_type
return socket_type in socket_type_names()

def sockets(self, in_out='INTPUT'):
if bpy.app.version >= (4, 0):
for item in self.interface.items_tree:
if item.item_type == 'SOCKET':
if item.in_out == in_out:
yield item
else:
yield from self.inputs if in_out == 'INPUT' else self.outputs


class BaseNode:
n_id: bpy.props.StringProperty(options={'SKIP_SAVE'})
Expand Down Expand Up @@ -320,7 +330,7 @@ def update_group_tree(self, context):
# also default values should be fixed
if self.node_tree:
self.node_tree.use_fake_user = True
for node_sock, interface_sock in zip(self.inputs, self.node_tree.inputs):
for node_sock, interface_sock in zip(self.inputs, self.node_tree.sockets('INPUT')):
if hasattr(interface_sock, 'default_value') and hasattr(node_sock, 'default_property'):
node_sock.default_property = interface_sock.default_value
self.node_tree.update_sockets() # properties of input socket properties should be updated
Expand Down Expand Up @@ -444,15 +454,17 @@ def copy_socket_names(from_sockets, to_sockets):
for from_s, to_s in zip(from_sockets, to_sockets):
to_s.name = from_s.name

tree_inputs = list(self.node_tree.sockets('INPUT'))
tree_outputs = list(self.node_tree.sockets('OUTPUT'))
if bpy.app.version >= (3, 5): # sockets should be generated manually
BlSockets(self.inputs).copy_sockets(self.node_tree.inputs)
copy_socket_names(self.node_tree.inputs, self.inputs)
BlSockets(self.outputs).copy_sockets(self.node_tree.outputs)
copy_socket_names(self.node_tree.outputs, self.outputs)
BlSockets(self.inputs).copy_sockets(tree_inputs)
copy_socket_names(tree_inputs, self.inputs)
BlSockets(self.outputs).copy_sockets(tree_outputs)
copy_socket_names(tree_outputs, self.outputs)

# this code should work only first time a socket was added
if self.node_tree:
for n_in_s, t_in_s in zip(self.inputs, self.node_tree.inputs):
for n_in_s, t_in_s in zip(self.inputs, tree_inputs):
# also before getting data from socket `socket.use_prop` property should be set
if hasattr(n_in_s, 'default_property'):
n_in_s.use_prop = not t_in_s.hide_value
Expand Down Expand Up @@ -663,13 +675,13 @@ def execute(self, context):
if not in_py_socket.node.select:
to_sockets[in_py_socket.bl_tween].add(out_py_socket.get_bl_socket(sub_tree))
for fs in from_sockets.keys():
sub_tree.inputs.new(fs.bl_idname, fs.name)
self.new_tree_socket(sub_tree, fs.bl_idname, fs.name, in_out='INPUT')
for ts in to_sockets.keys():
sub_tree.outputs.new(ts.bl_idname, ts.name)
self.new_tree_socket(sub_tree, ts.bl_idname, ts.name, in_out='OUTPUT')
if bpy.app.version >= (3, 5): # generate also sockets of group nodes
for fs in sub_tree.inputs:
for fs in sub_tree.sockets('INPUT'):
group_node.inputs.new(fs.bl_socket_idname, fs.name, identifier=fs.identifier)
for ts in sub_tree.outputs:
for ts in sub_tree.sockets('OUTPUT'):
group_node.outputs.new(ts.bl_socket_idname, ts.name, identifier=ts.identifier)

# linking, linking should be ordered from first socket to last (in case like `join list` nodes)
Expand All @@ -689,7 +701,11 @@ def execute(self, context):
if n.name in frame_names and n.name not in with_children_frames]

# todo one ui update (useless) will be done by the operator and another with update system of main handler
bpy.ops.node.edit_group_tree({'node': group_node}, is_new_group=True)
if bpy.app.version >= (3, 2):
with context.temp_override(node=group_node):
bpy.ops.node.edit_group_tree(is_new_group=True)
else:
bpy.ops.node.edit_group_tree({'node': group_node}, is_new_group=True)

return {'FINISHED'}

Expand Down Expand Up @@ -742,6 +758,14 @@ def recreate_frames(from_tree: bpy.types.NodeTree,
to_node = to_tree.nodes[from_to_node_names[from_node.name]]
to_node.parent = to_tree.nodes[new_frame_names[from_node.parent.name]]

@staticmethod
def new_tree_socket(tree, bl_idname, name, in_out='INPUT'):
if bpy.app.version >= (4, 0):
tree.interface.new_socket(name, in_out=in_out, socket_type=bl_idname)
else:
socks = tree.inputs if in_out == 'INPUT' else tree.outputs
return socks.new(bl_idname, name)


class UngroupGroupTree(bpy.types.Operator):
"""Put sub nodes into current layout and delete current group node"""
Expand All @@ -763,7 +787,11 @@ def execute(self, context):
# go to sub tree, select all except input and output groups and mark nodes to be copied
group_node = context.node
sub_tree = group_node.node_tree
bpy.ops.node.edit_group_tree({'node': group_node})
if bpy.app.version >= (3, 2):
with context.temp_override(node=group_node):
bpy.ops.node.edit_group_tree()
else:
bpy.ops.node.edit_group_tree({'node': group_node})
[setattr(n, 'select', False) for n in sub_tree.nodes]
group_nodes_filter = filter(lambda n: n.bl_idname not in {'NodeGroupInput', 'NodeGroupOutput'}, sub_tree.nodes)
for node in group_nodes_filter:
Expand Down
13 changes: 9 additions & 4 deletions core/sockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ def draw_label(text):

elif node.bl_idname == 'SvGroupTreeNode' and hasattr(self, 'draw_group_property'): # group node
if node.node_tree: # when tree is removed from node sockets still exist
interface_socket = node.node_tree.inputs[self.index]
interface_socket = list(node.node_tree.sockets('INPUT'))[self.index]
self.draw_group_property(layout, text, interface_socket)

elif node.bl_idname == 'NodeGroupOutput' and hasattr(self, 'draw_group_property'): # group out node
Expand All @@ -568,9 +568,14 @@ def draw_label(text):
if self.has_menu(context):
self.draw_menu_button(context, layout, node, text)


def draw_color(self, context, node):
return self.color
# https://wiki.blender.org/wiki/Reference/Release_Notes/4.0/Python_API#Node_Groups
if bpy.app.version >= (4, 0):
@classmethod
def draw_color_simple(cls):
return cls.color
else:
def draw_color(self, context, node):
return self.color


class SvObjectSocket(NodeSocket, SvSocketCommon):
Expand Down
6 changes: 5 additions & 1 deletion ui/nodeview_keymaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ class EnterExitGroupNodes(bpy.types.Operator):
def execute(self, context):
node = context.active_node
if node and hasattr(node, 'node_tree'):
bpy.ops.node.edit_group_tree({'node': node})
if bpy.app.version >= (3, 2):
with context.temp_override(node=node):
bpy.ops.node.edit_group_tree()
else:
bpy.ops.node.edit_group_tree({'node': node})
elif node and hasattr(node, 'gn_tree'): # GN Viewer
bpy.ops.node.sv_edit_gn_tree(
tree_name=node.id_data.name, node_name=node.name)
Expand Down