Exemplo n.º 1
0
    def check_reroutes_sockets(self):
        """
        Fix reroute sockets type
        For now it does work properly in first update
        because all new sockets even if they have links have `is_linked` attribute with False value
        at next update events all works perfectly (skip first update?)

        There is hope this will be fixed https://developer.blender.org/T82390
        """
        tree = Tree(self)
        socket_job = []
        Requirements = namedtuple('Requirements', [
            'left_n_i', 'left_s_i', 'left_t', 'reroute_n_i', 'right_n_is',
            'right_s_is'
        ])
        # analytical part, it's impossible to use Tree structure and modify the tree
        for node in tree.sorted_walk(tree.output_nodes):
            # walk should be sorted in case if reroute nodes are going one after other
            if node.bl_tween.bl_idname == 'NodeReroute':
                rer_in_s = node.inputs[0]
                rer_out_s = node.outputs[0]
                if rer_in_s.links:
                    left_s = rer_in_s.linked_sockets[0]
                    left_type = left_s.type if hasattr(
                        left_s, 'type') else left_s.bl_tween.bl_idname
                    if left_type != rer_in_s.bl_tween.bl_idname:
                        rer_out_s.type = left_type
                        socket_job.append(
                            Requirements(
                                left_s.node.index, left_s.index, left_type,
                                node.index, [
                                    s.node.index
                                    for s in rer_out_s.linked_sockets
                                ], [s.index
                                    for s in rer_out_s.linked_sockets]))

        # regenerating sockets
        for props in socket_job:
            left_s = self.nodes[props.left_n_i].outputs[props.left_s_i]
            reroute = self.nodes[props.reroute_n_i]

            # handle input socket
            in_s = reroute.inputs.new(props.left_t, left_s.name)
            self.links.new(in_s, left_s)
            reroute.inputs.remove(reroute.inputs[0])

            # handle output sockets
            out_s = reroute.outputs.new(props.left_t, left_s.name)
            for right_n_i, right_s_i in zip(props.right_n_is,
                                            props.right_s_is):
                left_s = self.nodes[right_n_i].inputs[right_s_i]
                self.links.new(left_s, out_s)
            reroute.outputs.remove(reroute.outputs[0])
Exemplo n.º 2
0
    def replace_nodes_id(cls, tree: Union[SvGroupTree, Tree], path: Path = ''):
        """
        The idea is to replace nodes ID before evaluating the tree
        in this case sockets will get unique identifiers relative to base group node

        format of new nodes ID -> "group_node_id.node_id" ("group_node_id." is replaceable part unlike "node_id")
        but nodes which is not connected with input should not change their ID
        because the result of their process method will be constant between different group nodes

        group_node_id also can consist several paths -> "base_group_id.current_group_id"
        in case when the group is inside another group
        max length of path should be no more then number of base trees of most nested group node + 1
        """
        if hasattr(tree, 'bl_idname'):  # it's Blender tree
            tree = Tree(tree)

        # todo should be cashed for optimization?
        input_linked_nodes = {n for n in tree.bfs_walk([tree.nodes.active_input] if tree.nodes.active_output else [])}

        for node in tree.nodes:
            node_id = cls.extract_node_id(node.bl_tween)

            if cut_mk_suffix(node.bl_tween.bl_idname) not in DEBUGGER_NODES and node in input_linked_nodes:
                node.bl_tween.n_id = path + '.' + node_id
            else:
                node.bl_tween.n_id = node_id
Exemplo n.º 3
0
 def get(cls, bl_tree: SvTree, path: Path):
     """Return caught tree with filled `is_updated` attribute according last statistic"""
     tree = cls._trees.get(bl_tree.tree_id)
     if tree is None:
         tree = Tree(bl_tree)
         cls._trees[bl_tree.tree_id] = tree
     for node in tree.nodes:
         node.is_updated = NodesStatuses.get(node.bl_tween, path).is_updated  # good place to do this?
     return tree
Exemplo n.º 4
0
 def can_be_grouped(tree) -> bool:
     """True if selected nodes can be putted into group (does not produce cyclic links)"""
     # if there is one or more unselected nodes between nodes to be grouped
     # then current selection can't be grouped
     py_tree = Tree(tree)
     [
         setattr(py_tree.nodes[n.name], 'select', n.select)
         for n in tree.nodes
     ]
     for node in py_tree.nodes:
         if not node.select:
             continue
         for neighbour_node in node.next_nodes:
             if neighbour_node.select:
                 continue
             for next_node in py_tree.bfs_walk([neighbour_node]):
                 if next_node.select:
                     return False
     return True
Exemplo n.º 5
0
    def _update_tree(cls, bl_tree: SvTree):
        """
        This method will generate new tree and update 'is_input_changed' node attribute
        according topological changes relatively previous call
        """
        new_tree = Tree(bl_tree)

        # update is_input_changed attribute
        cls._update_topology_status(new_tree)

        return new_tree
Exemplo n.º 6
0
def group_global_handler() -> Generator[Node]:
    """
    It should find changes and update group nodes
    After that update system of main trees should update themselves
    meanwhile group nodes should be switched off because they already was updated
    """
    for bl_tree in (t for t in bpy.data.node_groups if t.bl_idname == 'SvGroupTree'):
        # for now it always update all trees todo should be optimized later (keep in mind, trees can become outdated)
        ContextTrees.update_tree(bl_tree)

    for bl_tree in (t for t in bpy.data.node_groups if t.bl_idname == 'SverchCustomTreeType'):
        outdated_group_nodes = set()
        tree = Tree(bl_tree)
        for node in tree.sorted_walk(tree.output_nodes):
            if hasattr(node.bl_tween, 'updater'):

                group_updater = node.bl_tween.updater(is_input_changed=False)  # just searching inner changes
                try:
                    # it should return only nodes which should be updated
                    while True:
                        yield next(group_updater)
                except CancelError:
                    group_updater.throw(CancelError)
                except StopIteration as stop_error:
                    sub_tree_changed, error = stop_error.value
                    if sub_tree_changed:
                        outdated_group_nodes.add(node.bl_tween)
        # passing running to update system of main tree
        if outdated_group_nodes:
            outdated_group_nodes = list(outdated_group_nodes)
            active_states = [n.is_active for n in outdated_group_nodes]
            try:
                [n.toggle_active(False) for n in outdated_group_nodes]
                bl_tree.update_nodes(list(outdated_group_nodes))
            except Exception:
                traceback.print_exc()
            finally:
                [n.toggle_active(s, to_update=False) for s, n in zip(active_states, outdated_group_nodes)]
Exemplo n.º 7
0
    def get(cls, bl_tree):
        """Return caught tree or new if the tree was not build yet"""
        tree = cls._trees.get(bl_tree.tree_id)

        # new tree, all nodes are outdated
        if tree is None:
            tree = Tree(bl_tree)
            cls._trees[bl_tree.tree_id] = tree

        # topology of the tree was changed and should be updated
        elif not tree.is_updated:
            tree = cls._update_tree(bl_tree)
            cls._trees[bl_tree.tree_id] = tree

        return tree
Exemplo n.º 8
0
    def _update_tree(cls, bl_tree):
        """
        This method will generate new tree, copy is_updates status from previous tree
        and update 'is_input_changed' node attribute according topological changes relatively previous call
        Two reasons why always new tree is generated - it's simpler and new tree keeps fresh references to the nodes
        """
        new_tree = Tree(bl_tree)

        # copy is_updated attribute
        if new_tree.id in cls._trees:
            old_tree = cls._trees[new_tree.id]
            for node in new_tree.nodes:
                if node.name in old_tree.nodes:
                    node.is_updated = old_tree.nodes[node.name].is_updated

        # update is_input_changed attribute
        cls._update_topology_status(new_tree)

        return new_tree
Exemplo n.º 9
0
    def get(cls, bl_tree: SvTree, path: Path):
        """Return caught tree with filled `is_updated` attribute according last statistic"""
        tree = cls._trees.get(bl_tree.tree_id)

        # new tree, all nodes are outdated
        if tree is None:
            tree = Tree(bl_tree)
            cls._trees[bl_tree.tree_id] = tree

        # topology of the tree was changed and should be updated
        elif not tree.is_updated:
            tree = cls._update_tree(bl_tree)
            cls._trees[bl_tree.tree_id] = tree

        # we have to always update is_updated status because the tree does not keep them properly
        for node in tree.nodes:
            node.is_updated = NodesStatuses.get(
                node.bl_tween,
                path).is_updated  # fill in actual is_updated state

        return tree
Exemplo n.º 10
0
    def execute(self, context):
        """Similar to AddGroupTreeFromSelected operator but in backward direction (from sub tree to tree)"""

        # 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})
        [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:
            node.select = True
            node[
                'sub_node_name'] = node.name  # this will be copied within the nodes

        # the attribute should be empty in destination tree
        tree = context.space_data.path[-2].node_tree
        for node in tree.nodes:
            if 'sub_node_name' in node:
                del node['sub_node_name']

        # Frames can't be just copied because they does not have absolute location, but they can be recreated
        frame_names = {
            n.name
            for n in sub_tree.nodes if n.select and n.bl_idname == 'NodeFrame'
        }
        [
            setattr(n, 'select', False) for n in sub_tree.nodes
            if n.bl_idname == 'NodeFrame'
        ]

        with tree.throttle_update():
            if any(n for n in sub_tree.nodes if
                   n.select):  # if no selection copy operator will raise error
                # copy and past nodes into group tree
                bpy.ops.node.clipboard_copy()
                context.space_data.path.pop()
                bpy.ops.node.clipboard_paste(
                )  # this will deselect all and select only pasted nodes

                # move nodes in group node center
                tree_select_nodes = [n for n in tree.nodes if n.select]
                center = reduce(
                    lambda v1, v2: v1 + v2,
                    [Vector(n.absolute_location)
                     for n in tree_select_nodes]) / len(tree_select_nodes)
                [
                    setattr(n, 'location',
                            n.location - (center - group_node.location))
                    for n in tree_select_nodes
                ]

                # recreate frames
                node_name_mapping = {
                    n['sub_node_name']: n.name
                    for n in tree.nodes if 'sub_node_name' in n
                }
                AddGroupTreeFromSelected.recreate_frames(
                    sub_tree, tree, frame_names, node_name_mapping)
            else:
                context.space_data.path.pop(
                )  # should exit from sub tree anywhere

            # recreate py tree structure
            sub_py_tree = Tree(sub_tree)
            [
                setattr(sub_py_tree.nodes[n.name], 'type', n.bl_idname)
                for n in sub_tree.nodes
            ]
            py_tree = Tree(tree)
            [
                setattr(py_tree.nodes[n.name], 'select', n.select)
                for n in tree.nodes
            ]
            group_py_node = py_tree.nodes[group_node.name]
            for node in tree.nodes:
                if 'sub_node_name' in node:
                    sub_py_tree.nodes[
                        node['sub_node_name']].twin = py_tree.nodes[node.name]
                    py_tree.nodes[node.name].twin = sub_py_tree.nodes[
                        node['sub_node_name']]

            # create in links
            for group_input_py_node in [
                    n for n in sub_py_tree.nodes if n.type == 'NodeGroupInput'
            ]:
                for group_in_s, input_out_s in zip(
                        group_py_node.inputs, group_input_py_node.outputs):
                    if group_in_s.links and input_out_s.links:
                        link_out_s = group_in_s.linked_sockets[0]
                        for twin_in_s in input_out_s.linked_sockets:
                            if twin_in_s.node.type == 'NodeGroupOutput':  # node should be searched in above tree
                                group_out_s = group_py_node.outputs[
                                    twin_in_s.index]
                                for link_in_s in group_out_s.linked_sockets:
                                    tree.links.new(
                                        link_in_s.get_bl_socket(tree),
                                        link_out_s.get_bl_socket(tree))
                            else:
                                link_in_s = twin_in_s.node.twin.inputs[
                                    twin_in_s.index]
                                tree.links.new(link_in_s.get_bl_socket(tree),
                                               link_out_s.get_bl_socket(tree))

            # create out links
            for group_output_py_node in [
                    n for n in sub_py_tree.nodes if n.type == 'NodeGroupOutput'
            ]:
                for group_out_s, output_in_s in zip(
                        group_py_node.outputs, group_output_py_node.inputs):
                    if group_out_s.links and output_in_s.links:
                        twin_out_s = output_in_s.linked_sockets[0]
                        if twin_out_s.node.type == 'NodeGroupInput':
                            continue  # we already added this link
                        for link_in_s in group_out_s.linked_sockets:
                            link_out_s = twin_out_s.node.twin.outputs[
                                twin_out_s.index]
                            tree.links.new(link_in_s.get_bl_socket(tree),
                                           link_out_s.get_bl_socket(tree))

            # delete group node
            tree.nodes.remove(group_node)
            for node in tree.nodes:
                if 'sub_node_name' in node:
                    del node['sub_node_name']

        tree.update()

        return {'FINISHED'}
Exemplo n.º 11
0
    def execute(self, context):
        """
        Add group tree from selected:
        01. Deselect group Input and Output nodes
        02. Copy nodes into clipboard
        03. Create group tree and move into one
        04. Past nodes from clipboard
        05. Move nodes into tree center
        06. Add group "input" and "output" outside of bounding box of the nodes
        07. Connect "input" and "output" sockets with group nodes
        08. Add Group tree node in center of selected node in initial tree
        09. Link the node with appropriate sockets
        10. Cleaning
        """
        base_tree = context.space_data.path[-1].node_tree
        if not self.can_be_grouped(base_tree):
            self.report({'WARNING'},
                        'Current selection can not be converted to group')
            return {'CANCELLED'}
        sub_tree: SvGroupTree = bpy.data.node_groups.new(
            'Sverchok group', SvGroupTree.bl_idname)

        # deselect group nodes if selected
        [
            setattr(n, 'select', False) for n in base_tree.nodes if n.select
            and n.bl_idname in {'NodeGroupInput', 'NodeGroupOutput'}
        ]

        # Frames can't be just copied because they does not have absolute location, but they can be recreated
        frame_names = {
            n.name
            for n in base_tree.nodes if n.select and n.bl_idname == 'NodeFrame'
        }
        [
            setattr(n, 'select', False) for n in base_tree.nodes
            if n.bl_idname == 'NodeFrame'
        ]

        with base_tree.throttle_update():
            # copy and past nodes into group tree
            bpy.ops.node.clipboard_copy()
            context.space_data.path.append(sub_tree)
            bpy.ops.node.clipboard_paste()
            context.space_data.path.pop()  # will enter later via operator

            # move nodes in tree center
            sub_tree_nodes = self.filter_selected_nodes(sub_tree)
            center = reduce(lambda v1, v2: v1 + v2,
                            [n.location
                             for n in sub_tree_nodes]) / len(sub_tree_nodes)
            [
                setattr(n, 'location', n.location - center)
                for n in sub_tree_nodes
            ]

            # recreate frames
            node_name_mapping = {
                n.name: n.name
                for n in sub_tree.nodes
            }  # all nodes have the same name as in base tree
            self.recreate_frames(base_tree, sub_tree, frame_names,
                                 node_name_mapping)

            # add group input and output nodes
            min_x = min(n.location[0] for n in sub_tree_nodes)
            max_x = max(n.location[0] for n in sub_tree_nodes)
            input_node = sub_tree.nodes.new('NodeGroupInput')
            input_node.location = (min_x - 250, 0)
            output_node = sub_tree.nodes.new('NodeGroupOutput')
            output_node.location = (max_x + 250, 0)

            # add group tree node
            initial_nodes = self.filter_selected_nodes(base_tree)
            center = reduce(
                lambda v1, v2: v1 + v2,
                [Vector(n.absolute_location)
                 for n in initial_nodes]) / len(initial_nodes)
            group_node = base_tree.nodes.new(SvGroupTreeNode.bl_idname)
            group_node.select = False
            group_node.group_tree = sub_tree
            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)
            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
                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))

                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])

            # delete selected nodes and copied frames without children
            [
                base_tree.nodes.remove(n)
                for n in self.filter_selected_nodes(base_tree)
            ]
            with_children_frames = {
                n.parent.name
                for n in base_tree.nodes if n.parent
            }
            [
                base_tree.nodes.remove(n) for n in base_tree.nodes
                if n.name in frame_names and n.name not in with_children_frames
            ]

        base_tree.update_nodes([group_node])
        bpy.ops.node.edit_group_tree({'node': group_node})

        return {'FINISHED'}
Exemplo n.º 12
0
 def update_tree(cls, bl_tree: SvTree):
     """
     This method will generate new tree and update 'link_changed' node attribute
     according topological changes relatively previous call
     """
     cls._update_topology_status(Tree(bl_tree))