Skip to content

Commit

Permalink
Creating node groups in Blender 3.5+ (#4926)
Browse files Browse the repository at this point in the history
* Generate sockets for group nodes when their subtrees was changed

* fix way to generate new sockets for Blender 3.5+
  • Loading branch information
Durman authored Jun 28, 2023
1 parent 9f25dae commit 7919be9
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 20 deletions.
58 changes: 38 additions & 20 deletions core/node_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# from __future__ import annotations <- Don't use it here, `group node` will loose its `group tree` attribute
import time
from collections import namedtuple
from collections import namedtuple, defaultdict
from functools import reduce
from typing import Tuple, List, Set, Dict, Iterator, Optional

Expand All @@ -23,7 +23,7 @@
from sverchok.core.update_system import ERROR_KEY
from sverchok.utils.tree_structure import Tree
from sverchok.utils.sv_node_utils import recursive_framed_location_finder
from sverchok.utils.handle_blender_data import BlTrees
from sverchok.utils.handle_blender_data import BlTrees, BlSockets
from sverchok.node_tree import SvNodeTreeCommon, SverchCustomTreeNode


Expand Down Expand Up @@ -439,6 +439,17 @@ def active_output(self) -> Optional[bpy.types.Node]:
return node

def sv_update(self):
"""This method is also called when interface of the subtree is changed"""
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

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)

# 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):
Expand Down Expand Up @@ -636,33 +647,40 @@ def execute(self, context):
group_node.location = center
sub_tree.group_node_name = group_node.name

# linking, linking should be ordered from first socket to last (in case like `join list` nodes)
# generate new sockets
py_base_tree = Tree(base_tree)
[setattr(py_base_tree.nodes[n.name], 'select', n.select) for n in base_tree.nodes]
input_node['connected_sockets'] = dict() # Dict[node.name + socket.identifier, socket index of input node]
for py_node in py_base_tree.nodes: # is selected
from_sockets, to_sockets = defaultdict(set), defaultdict(set)
for py_node in py_base_tree.nodes:
if not py_node.select:
continue
for in_s in py_node.inputs:
for out_s in in_s.linked_sockets: # only one link always
if out_s.node.select:
continue
out_s_key = out_s.node.name + out_s.identifier
if out_s_key in input_node['connected_sockets']: # protect from creating extra input sockets
input_out_s_index = input_node['connected_sockets'][out_s_key]
sub_tree.links.new(in_s.get_bl_socket(sub_tree), input_node.outputs[input_out_s_index])
else:
input_out_s_index = len(input_node.outputs) - 1
input_node['connected_sockets'][out_s_key] = input_out_s_index
sub_tree.links.new(in_s.get_bl_socket(sub_tree), input_node.outputs[input_out_s_index])
base_tree.links.new(group_node.inputs[-1], out_s.get_bl_socket(base_tree))

if not out_s.node.select:
from_sockets[out_s.bl_tween].add(in_s.get_bl_socket(sub_tree))
for out_py_socket in py_node.outputs:
if any(not s.node.select for s in out_py_socket.linked_sockets):
sub_tree.links.new(output_node.inputs[-1], out_py_socket.get_bl_socket(sub_tree))
for in_py_socket in out_py_socket.linked_sockets:
if not in_py_socket.node.select:
base_tree.links.new(in_py_socket.get_bl_socket(base_tree), group_node.outputs[-1])
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)
for ts in to_sockets.keys():
sub_tree.outputs.new(ts.bl_idname, ts.name)
if bpy.app.version >= (3, 5): # generate also sockets of group nodes
for fs in sub_tree.inputs:
group_node.inputs.new(fs.bl_socket_idname, fs.name, identifier=fs.identifier)
for ts in sub_tree.outputs:
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)
for i, (from_s, first_ss) in enumerate(from_sockets.items()):
base_tree.links.new(group_node.inputs[i], from_s)
for first_s in first_ss:
sub_tree.links.new(first_s, input_node.outputs[i])
for i, (to_s, last_ss) in enumerate(to_sockets.items()):
base_tree.links.new(to_s, group_node.outputs[i])
for last_s in last_ss:
sub_tree.links.new(output_node.inputs[i], last_s)

# delete selected nodes and copied frames without children
[base_tree.nodes.remove(n) for n in self.filter_selected_nodes(base_tree)]
Expand Down
46 changes: 46 additions & 0 deletions utils/handle_blender_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from typing import Any, List, Union, TYPE_CHECKING, Optional

import bpy
from bpy.types import NodeInputs, NodeOutputs, NodeSocket

from sverchok.data_structure import fixed_iter

if TYPE_CHECKING:
Expand Down Expand Up @@ -355,6 +357,50 @@ def base_idname(self) -> str:
return id_name


class BlSockets:
def __init__(self, sockets: Union[NodeInputs, NodeOutputs]):
self._sockets = sockets

def copy_sockets(self, sockets_from: Iterable):
"""Copy sockets from one collection to another. Also, it can be used
to refresh `to` collection to be equal to `from` collection and in this
case only new socket will be added and old one removed.
It can copy properties:
sv socket -> sv socket
sv interface socket -> sv socket
"""
sockets_to = self._sockets
# remove sockets which are not presented in from collection
identifiers_from = {s.identifier for s in sockets_from}
for s_to in sockets_to:
if s_to.identifier not in identifiers_from:
sockets_to.remove(s_to)

# add new sockets
sock_indexes_to = {s.identifier: i for i, s in enumerate(sockets_to)}
for s_from in sockets_from:
if s_from.identifier in sock_indexes_to:
continue
id_name = getattr(s_from, 'bl_socket_idname', s_from.bl_idname)
s_to = sockets_to.new(id_name, s_from.name, identifier=s_from.identifier)
sock_indexes_to[s_to.identifier] = len(sockets_to) - 1

# fix existing sockets
for s_from in sockets_from:
s_to = sockets_to[sock_indexes_to[s_from.identifier]]
id_name = getattr(s_from, 'bl_socket_idname', s_from.bl_idname)
if id_name != s_to.bl_idname:
s_to = s_to.replace_socket(id_name)

# fix socket positions
for new_pos, s_from in enumerate(sockets_from):
current_pos = sock_indexes_to[s_from.identifier]
if current_pos != new_pos:
sockets_to.move(current_pos, new_pos)
sock_indexes_to = {
s.identifier: i for i, s in enumerate(sockets_to)}


class BlSocket:
_attr_types = {
'VECTOR': 'FLOAT_VECTOR',
Expand Down

0 comments on commit 7919be9

Please sign in to comment.