Skip to content

ShaderNodes

Bases: Tree

Source code in core/shadernodes.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
class ShaderNodes(Tree):
    def __init__(self, 
            tree_name        : str,
            *,
            fake_user        : bool = False, 
            is_group         : bool = False, 
            prefix           : Optional[str] = None,
            replace_material : bool = False,
            rebuild          : bool = True,
            color_tag        : str = 'NONE',
            ):
        """ > ShaderNodes

        Parameters
        ----------
        tree_name : str
            Shader Nodes name

        fake_user : bool, optional
            set fake_user flag default=False.

        is_group : bool, optional
            tree is a group default=False.

        prefix : str, optional
            name prefix default=None.

        replace_material : bool, optional
            replace material if already exists (is_group=False), default=False.

        rebuild : bool, default=True
            rebuild an existing group (is_group=True); new groups are always built.

        color_tag : str, default='NONE'
            group or modifier color_tag attribute (groups only)
        """
        super().__init__(
            tree_name, 
            tree_type='ShaderNodeTree', 
            fake_user=fake_user, 
            is_group=is_group, 
            prefix=prefix, 
            rebuild=rebuild if is_group else replace_material,
            color_tag = color_tag,
            )

    # =============================================================================================================================
    # Input Node

    @property
    def input_node(self) -> Node:
        if not self._is_group:
            raise NodeError(f"ShaderNodes '{self._material.name}' is not a group, it doesn't have an input node")
        return super().input_node

    # ====================================================================================================
    # Tree output

    @property
    def output_node(self) -> Node:
        if self._is_group:
            return super().output_node

        for node in self._nodes.values():
            if node._bnode.bl_idname ==  'ShaderNodeOutputMaterial':
                return node

        return Node('ShaderNodeOutputMaterial')

    def get_output_node(self, target: str = 'ALL') -> Node:
        """ Output node

        Standard behavior if Group or within a zone
        """

        # ---------------------------------------------------------------------------
        # Group or within a zone
        # ---------------------------------------------------------------------------

        if self._is_group:
            return super().get_output_node()

        if len(self._output_stack):
            return self._output_stack[-1]

        # ---------------------------------------------------------------------------
        # Output to shader outpout
        # ---------------------------------------------------------------------------

        for node in self._nodes.values():
            if node._bnode.bl_idname == 'ShaderNodeOutputMaterial' and node._bnode.target == target:
                return node
        node = Node('ShaderNodeOutputMaterial')
        node._bnode.target = target
        return node

    def set_surface(self, value: Any, target: str ='ALL') -> None:
        node = self.get_output_node(target=target)
        node.set_input_socket('Surface', value)

    def set_volume(self, value: Any, target: str ='ALL') -> None:
        node = self.get_output_node(target=target)
        node.set_input_socket('Volume', value)

    def set_displacement(self, value: Any, target: str ='ALL') -> None:
        node = self.get_output_node(target=target)
        node.set_input_socket('Displacement', value)

    def set_thickness(self, value: Any, target: str ='ALL') -> None:
        node = self.get_output_node(target=target)
        node.set_input_socket('Thickness', value)

    @property
    def surface(self) -> None:
        raise NodeError(f"Material 'surface' is write only")

    @surface.setter
    def surface(self, value: Any) -> None:
        if isinstance(value, tuple):
            self.set_surface(value[0], target=value[1])
        else:
            self.set_surface(value)

    @property
    def volume(self) -> None:
        raise NodeError(f"Material 'volume' is write only")

    @volume.setter
    def volume(self, value: Any) -> None:
        if isinstance(value, tuple):
            self.set_volume(value[0], target=value[1])
        else:
            self.set_volume(value)

    @property
    def displacement(self) -> None:
        raise NodeError(f"Material 'displacement' is write only")

    @displacement.setter
    def displacement(self, value: Any) -> None:
        if isinstance(value, tuple):
            self.set_displacement(value[0], target=value[1])
        else:
            self.set_displacement(value)

    @property
    def thickness(self) -> None:
        raise NodeError(f"Material 'thickness' is write only")

    @thickness.setter
    def thickness(self, value: Any) -> None:
        if isinstance(value, tuple):
            self.set_thickness(value[0], target=value[1])
        else:
            self.set_thickness(value)

    def aov_output(self, name: Optional[str] =None, color: Optional[Any] =None, value: Optional[Any] =None) -> None:
        node = Node('AOV Output', {'Color': color, 'Value': value}, aov_name=name)

__init__(tree_name, *, fake_user=False, is_group=False, prefix=None, replace_material=False, rebuild=True, color_tag='NONE')

ShaderNodes

Parameters:

Name Type Description Default
tree_name str

Shader Nodes name

required
fake_user bool

set fake_user flag default=False.

False
is_group bool

tree is a group default=False.

False
prefix str

name prefix default=None.

None
replace_material bool

replace material if already exists (is_group=False), default=False.

False
rebuild bool

rebuild an existing group (is_group=True); new groups are always built.

True
color_tag str

group or modifier color_tag attribute (groups only)

'NONE'
Source code in core/shadernodes.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def __init__(self, 
        tree_name        : str,
        *,
        fake_user        : bool = False, 
        is_group         : bool = False, 
        prefix           : Optional[str] = None,
        replace_material : bool = False,
        rebuild          : bool = True,
        color_tag        : str = 'NONE',
        ):
    """ > ShaderNodes

    Parameters
    ----------
    tree_name : str
        Shader Nodes name

    fake_user : bool, optional
        set fake_user flag default=False.

    is_group : bool, optional
        tree is a group default=False.

    prefix : str, optional
        name prefix default=None.

    replace_material : bool, optional
        replace material if already exists (is_group=False), default=False.

    rebuild : bool, default=True
        rebuild an existing group (is_group=True); new groups are always built.

    color_tag : str, default='NONE'
        group or modifier color_tag attribute (groups only)
    """
    super().__init__(
        tree_name, 
        tree_type='ShaderNodeTree', 
        fake_user=fake_user, 
        is_group=is_group, 
        prefix=prefix, 
        rebuild=rebuild if is_group else replace_material,
        color_tag = color_tag,
        )

_display_counter(title) classmethod

Print cumulative counts and wall time since the last reset.

Source code in core/treeclass.py
966
967
968
969
970
971
972
973
974
975
@classmethod
def _display_counter(cls, title):
    """Print cumulative counts and wall time since the last reset."""
    if Tree._counter_started is not None:
        Tree._total_time = perf_counter() - Tree._counter_started
    print(f"{title}: Built: {Tree._total_built}, Existing: {Tree._total_existing}")
    print(f"  {Tree._total_nodes} nodes, {Tree._total_links} links, "
          f"{Tree._total_warnings} warnings in {Tree._total_time:.2f} s "
          f"(arrange: {Tree._total_arrange_time:.2f} s)")
    print()

_init_package(title) classmethod

Print a package heading and start its build statistics session.

Source code in core/treeclass.py
945
946
947
948
949
950
951
952
@classmethod
def _init_package(cls, title):
    """Print a package heading and start its build statistics session."""
    print()
    print('-' * 80)
    print(title)
    print()
    cls._reset_counters()

_reset_counters() classmethod

Start a shared statistics session for successive tree builds.

Source code in core/treeclass.py
954
955
956
957
958
959
960
961
962
963
964
@classmethod
def _reset_counters(cls):
    """Start a shared statistics session for successive tree builds."""
    Tree._total_nodes = 0
    Tree._total_links = 0
    Tree._total_built = 0
    Tree._total_existing = 0
    Tree._total_warnings = 0
    Tree._total_time = 0.0
    Tree._total_arrange_time = 0.0
    Tree._counter_started = perf_counter()

add_method(target_class, func_name=None, self_attr=None, ret_class=None, **fixed)

Add a method calling the Group.

Parameters:

Name Type Description Default
target_class type

class to add the method to

required
func_name str

name of the method to create (snae case version of group name if None) default=None.

None
self_attr str

self name attribute name default=None.

None
ret_class type

class to use to transtype the output socket default=None.

None
fixed dict

fixed values for sockets

{}
Source code in core/treeclass.py
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
def add_method(self, 
    target_class    : type, 
    func_name       : str = None,
    self_attr       : str = None, 
    ret_class       : type = None, 
    **fixed):
    """ Add a method calling the Group.

    Parameters
    ----------
    target_class : type
        class to add the method to

    func_name : str, optional
        name of the method to create (snae case version of group name if None) default=None.

    self_attr : str, optional
        self name attribute name default=None.

    ret_class : type, optional
        class to use to transtype the output socket default=None.

    fixed : dict
        fixed values for sockets        

    """
    from .nodeclass import Group

    group_name = self._btree.name
    if self._prefix != "":
        group_name = group_name[len(self._prefix) + 1:]

    Group.add_method(group_name, target_class,
        func_name       = func_name,
        self_attr       = self_attr, 
        ret_class       = ret_class, 
        prefix          = self._prefix,
        **fixed)

arrange()

Arrange the nodes in the editor.

Try to arrange properly the nodes from left to right.

This method is called when the Tree is poped from the stack.

Returns:

Type Description
None
Source code in core/treeclass.py
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
def arrange(self):
    """ > Arrange the nodes in the editor.

    Try to arrange properly the nodes from left to right.

    This method is called when the Tree is poped from the stack.

    Returns
    -------
    None
    """
    if self._use_arrange:
        started = perf_counter()
        try:
            treearrange.arrange(self._btree)
        finally:
            elapsed = perf_counter() - started
            self._arrange_time += elapsed
            Tree._total_arrange_time += elapsed

clear(keep_nodes=True)

Clear the content of the Tree.

Remove all the nodes in the Tree.

Source code in core/treeclass.py
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
def clear(self, keep_nodes: bool = True):
    """ Clear the content of the Tree.

    Remove all the nodes in the Tree.
    """
    self._nodes.clear()

    # Keep menu switch nodes to preserver input links
    # They will be deleted at the end

    if keep_nodes:
        del_nodes = []
        self._kept_nodes = []
        for bnode in self._btree.nodes:
            if bnode.bl_idname in ['GeometryNodeMenuSwitch', 'NodeGroupInput']:
                self._kept_nodes.append(bnode)
            else:
                del_nodes.append(bnode)

        for bnode in del_nodes:
            self._btree.nodes.remove(bnode)

    else:
        self._btree.links.clear()
        self._btree.nodes.clear()

create_input_socket(bl_idname, name, panel='', **props)

Create a new input socket.

This is an input socket of the zone, hence an output socket of the input node.

Parameters:

Name Type Description Default
bl_idname str

Socket bl_idname

required
name str

Socket name

required
panel str

Panel to place the socket in

''
**props dict

Properties specific to interface socket

{}

Returns:

Type Description
Socket
Source code in core/treeclass.py
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
def create_input_socket(self, bl_idname, name, panel="", **props):
    """ Create a new input socket.

    This is an **input socket** of the zone, hence an **output socket** of the input node.

    Parameters
    ----------
    bl_idname : str
        Socket bl_idname

    name : str
        Socket name

    panel : str, optional
        Panel to place the socket in

    **props : dict, optional
        Properties specific to interface socket


    Returns
    -------
    Socket
    """        
    input_node = self.get_input_node()
    socket = input_node.create_socket('OUTPUT', bl_idname, name, panel=panel, **props)
    def_val = props.get('default_value')
    if def_val is not None:
        try:
            bsocket = utils.get_bsocket(socket)
            if bsocket.type == 'VECTOR' and isinstance(def_val, tuple):
                dimensions = len(bsocket.default_value)
                def_val = (
                    def_val[:dimensions]
                    + (0,) * max(0, dimensions - len(def_val))
                )
            bsocket.default_value = def_val
        except Exception as e:
            print(f"WARNING Tree.create_input_socket: {str(e)}")

    return socket

current_tree() staticmethod

Get the Current Tree.

Returns None if no tree is currently open

Returns:

Type Description
Tree

current tree or None

Source code in core/treeclass.py
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
@staticmethod
def current_tree():
    """ > Get the Current Tree.

    Returns None if no tree is currently open

    Returns
    -------
    Tree
        current tree or None

    """
    if len(Tree.TREE_STACK):
        return Tree.TREE_STACK[-1]
    else:
        return None

get_output_node(target='ALL')

Output node

Standard behavior if Group or within a zone

Source code in core/shadernodes.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def get_output_node(self, target: str = 'ALL') -> Node:
    """ Output node

    Standard behavior if Group or within a zone
    """

    # ---------------------------------------------------------------------------
    # Group or within a zone
    # ---------------------------------------------------------------------------

    if self._is_group:
        return super().get_output_node()

    if len(self._output_stack):
        return self._output_stack[-1]

    # ---------------------------------------------------------------------------
    # Output to shader outpout
    # ---------------------------------------------------------------------------

    for node in self._nodes.values():
        if node._bnode.bl_idname == 'ShaderNodeOutputMaterial' and node._bnode.target == target:
            return node
    node = Node('ShaderNodeOutputMaterial')
    node._bnode.target = target
    return node

get_signature(include=None, exclude=[], enabled_only=True, with_sockets=False)

Build the closure signature of the tree.

The closure signature is the tuple made of the output signature of the input node and the input signature of the output node

Returns:

Type Description
Signature
Source code in core/treeclass.py
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
def get_signature(self, include: list = None, exclude: list = [], enabled_only=True, with_sockets: bool = False):
    """ Build the closure signature of the tree.

    The closure signature is the tuple made of the output signature of the input node
    and the input signature of the output node

    Returns
    -------
    Signature
    """
    return Signature(
        self.input_node.get_signature(
            include         = include,
            exclude         = exclude,
            enabled_only    = enabled_only,
            with_sockets    = with_sockets).outputs,
        self.output_node.get_signature(
            enabled_only    = enabled_only,
            with_sockets    = with_sockets).inputs)

is_geonodes() classmethod

Current Tree is Geometry Nodes.

Returns:

Type Description
True if Tree is GeoNodes, False otherwise
Source code in core/treeclass.py
985
986
987
988
989
990
991
992
993
@classmethod
def is_geonodes(cls):
    """ > Current Tree is Geometry Nodes.

    Returns
    -------
    True if Tree is GeoNodes, False otherwise
    """
    return cls.current_tree()._btree.bl_idname == 'GeometryNodeTree'

is_shader() classmethod

Current Tree is Shader Nodes.

Returns:

Type Description
True if Tree is ShaderNodes, False otherwise
Source code in core/treeclass.py
 995
 996
 997
 998
 999
1000
1001
1002
1003
@classmethod
def is_shader(cls):
    """ > Current Tree is Shader Nodes.

    Returns
    -------
    True if Tree is ShaderNodes, False otherwise
    """
    return cls.current_tree()._btree.bl_idname == 'ShaderNodeTree'

Create a link between two sockets.

Parameters:

Name Type Description Default
out_socket socket

a node output socket

required
in_socket socket

input socket from another node

required

Returns:

Type Description
Link
Source code in core/treeclass.py
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
def link(self, out_socket, in_socket, handle_dynamic_sockets=False):
    """ > Create a link between two sockets.

    Parameters
    ----------
    out_socket : socket
        a node output socket

    in_socket : socket
        input socket from another node


    Returns
    -------
    Link
    """

    #if hasattr(out_socket, '_bsocket'):
    if '_bsocket' in dir(out_socket):
        out_socket = out_socket._bsocket

    #if hasattr(in_socket, '_bsocket'):
    if '_bsocket' in dir(in_socket):
        in_socket = in_socket._bsocket

    for socket in (out_socket, in_socket):
        if socket.id_data != self._btree:
            raise NodeError(
                f"Socket '{socket.node.name}.{socket.name}' "
                f"belongs to tree '{socket.id_data.name}', "
                f"but current tree is '{self._btree.name}'"
            )

    link = self._btree.links.new(out_socket, in_socket, handle_dynamic_sockets=handle_dynamic_sockets)

    return link

pop(error=False)

Remove this tree from the stack

Important

This methods shouldn't be called directly, better use a with context block.

Raises:

Type Description
NodeError

if this tree is not the current one

``` python
with Tree("My Name"):

pass

```
Source code in core/treeclass.py
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
def pop(self, error=False):
    """ > Remove this tree from the stack

    !!! important
        This methods shouldn't be called directly, better use a **with** context block.

    Raises
    ------
    NodeError
        if this tree is not the current one


    ``` python
    with Tree("My Name"):
        pass
    ```
    """
    # ---------------------------------------------------------------------------
    # Ignore changes
    # ---------------------------------------------------------------------------

    if not Tree.TREE_STACK or Tree.TREE_STACK[-1] is not self:
        raise RuntimeError("Error in tree stack management")

    if not self._rebuild or self._loaded:
        Tree.TREE_STACK.pop()
        Tree._total_existing += 1
        return

    # ---------------------------------------------------------------------------
    # Remove kept nodes
    # ---------------------------------------------------------------------------

    for bnode in self._kept_nodes:
        self._btree.nodes.remove(bnode)

    # ---------------------------------------------------------------------------
    # Update modifiers inputs
    # ---------------------------------------------------------------------------

    if self._has_input_node:

        modifiers = self.get_modifiers()

        for bsock in self.input_node._bnode.outputs:

            isock = self._interface.by_identifier(bsock.identifier)
            if not hasattr(isock, 'default_value'):
                continue

            # ----------------------------------------
            # Menu socket
            # ----------------------------------------

            if bsock.bl_idname == 'NodeSocketMenu':
                for mod_name, mod in modifiers.items():

                    current = None

                    mod_vals = self._mod_vals.get(mod_name)

                    if mod_vals is not None:
                        current = mod_vals.get(bsock.identifier, None)

                    if current is None:
                        current = isock.default_value

                    mod_input = getattr(mod.properties.inputs, bsock.identifier)

                    try:
                        mod_input.value = current
                    except Exception as e:
                        try:
                            mod_input.value = isock.default_value
                        except:
                            pass

                continue

            # ----------------------------------------
            # General
            # ----------------------------------------

            for mod_name, mod in modifiers.items():

                mod_vals = self._mod_vals.get(mod_name)
                if mod_vals is None:
                    continue

                # Nothing to do if identifier exists
                if bsock.identifier in mod_vals:
                    continue

                # Nen identifier : let's set the default value
                mod_input = getattr(mod.properties.inputs, bsock.identifier)
                if not hasattr(mod_input, 'value'):
                    continue

                try:
                    mod_input.value = isock.default_value
                except Exception as e:
                    print(f"CAUTION: error when setting default <{isock.default_value}> value to socket '{bsock.name}'.\n -> error is: {str(e)}")

    # ---------------------------------------------------------------------------
    # Finalize
    # ---------------------------------------------------------------------------

    # Check dead ends
    warnings = 0
    if not error:
        utils.check_zones(self._btree)
        # Socket structures, notably inside nested closures, may still be
        # unresolved immediately after a link is created. Validate links
        # only once Blender has inferred the completed tree.
        for link in self._btree.links:
            utils.check_link(link)
        warnings = self.check_warnings()

    # Remove from stack
    tree = Tree.TREE_STACK.pop()
    if tree != self:
        raise RuntimeError(f"Error in tree stack management")

    # Empty the interface bin
    if (not error) and self._interface is not None:
        self._interface.empty_bin()

    # Last node in error
    if error and len(self._nodes):
        for node in reversed(self._nodes.values()):
            if node._bnode.bl_idname in ['NodeGroupOutput', 'NodeGroupInput']:
                continue
            utils.set_node_error(node._bnode)
            break

    # Arrange the nodes
    self.arrange()

    # Stats
    if self._btree.bl_idname == 'ShaderNodeTree' and self._material is not None:
        name = f"Material '{self._material.name}'"
    else:
        name = f"Tree '{self._btree.name}'"

    elapsed = perf_counter() - self._build_started
    print(f"{name} built: {self._str_stats}, warnings: {warnings}, "
          f"time: {elapsed:.2f} s (arrange: {self._arrange_time:.2f} s)")

    Tree._total_nodes += len(self._btree.nodes)
    Tree._total_links += len(self._btree.links)
    Tree._total_built += int(not error)
    Tree._total_warnings += warnings
    Tree._total_time = perf_counter() - Tree._counter_started

push()

Make this tree zone the current one

Important

This methods shouldn't be called directly, better use a with context block.

with Tree("My Name"):
    pass
Source code in core/treeclass.py
618
619
620
621
622
623
624
625
626
627
628
629
630
def push(self):
    """ > Make this tree zone the current one

    !!! important
        This methods shouldn't be called directly, better use a **with** context block.

    ``` python
    with Tree("My Name"):
        pass
    ```
    """
    Tree.TREE_STACK.append(self)
    return self

register_node(node)

Register a new Node.

This method is called by the Node class constructor. Never call directly.

Registered Nodes are stored in the dictionary : _nodes = bpy.types.Node.name -> Node

Parameters:

Name Type Description Default
node Node

the newly created Node to register

required

Returns:

Type Description
Node

the passed argument

Source code in core/treeclass.py
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
def register_node(self, node):
    """ Register a new Node.

    This method is called by the Node class constructor. Never call directly.

    Registered Nodes are stored in the dictionary : _nodes = bpy.types.Node.name -> Node

    Parameters
    ----------
    node : Node
        the newly created Node to register


    Returns
    -------
    Node
        the passed argument

    """

    self._nodes[node._bnode.name] = node
    if len(self._layouts):
        self._layouts[-1].include_node(node)

    node._stack = NodeError.stack_lines()

    if self._input_node is None and node._bnode.bl_idname == 'NodeGroupInput':
        self._input_node = node

    if self._output_node is None and node._bnode.bl_idname == 'NodeGroupOutput':
        self._output_node = node

    return node

remove_groups(names=None, prefix=None, geonodes=True, shadernodes=True) staticmethod

Remove Groups created by GeoNodes.

Important

This method can only remove groups created by GeoNodes.

Parameters:

Name Type Description Default
names str or list of strs

name of the node groups to remove (all if None)

None
prefix str

name prefix for the groups to delete

None
geonodes bool

remove geometry nodes groups

True
shadernodes bool

remove shader nodes groups

True

Returns:

Type Description
None
Source code in core/treeclass.py
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
@staticmethod
def remove_groups(names=None, prefix=None, geonodes=True, shadernodes=True):
    """ > Remove Groups created by GeoNodes.

    !!! important
         This method can only remove groups created by **GeoNodes**.

    Parameters
    ----------
    names : str or list of strs, optional
        name of the node groups to remove (all if None)

    prefix : str, optional
        name prefix for the groups to delete

    geonodes : bool, default=True
        remove geometry nodes groups

    shadernodes : bool, default=True
        remove shader nodes groups


    Returns
    -------
    None
    """

    groups = []
    if prefix is None:
        prefix_ = ""
    else:
        prefix_ = prefix + ' '

    tree_types = []
    if geonodes:
        tree_types.append('GeometryNodeTree')
    if shadernodes:
        tree_types.append('ShaderNodeTree')

    for group in bpy.data.node_groups:
        if group.bl_idname not in tree_types:
            continue

        if not group.description.startswith('GEONODES'):
            continue

        if prefix is not None and group.name[:len(prefix_)] != prefix_:
            continue

        if names is None:
            pass
        elif isinstance(names, str):
            if group.name != prefix_ + names:
                continue
        elif isinstance(names, list):
            ok = False
            for name in names:
                if group.name == prefix_ + name:
                    ok = True
                    break
            if not ok:
                continue
        else:
            raise Exception(f"Argument names '{names}' is not valid: None, str or list of strs expected")

        groups.append(group)

    print("Remove node groups")
    for group in groups:
        print(' -', group.name)
        bpy.data.node_groups.remove(group)

    print(f"{len(groups)} node groups removed")