| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732 | #Mantis Nodes Baseimport bpyfrom bpy.props import BoolProperty, StringProperty, EnumProperty, CollectionProperty, IntProperty, PointerProperty, BoolVectorPropertyfrom . import ops_nodegroupfrom bpy.types import NodeTree, Node, PropertyGroup, Operator, UIList, Panelfrom .utilities import (prRed, prGreen, prPurple, prWhite,                              prOrange,                              wrapRed, wrapGreen, wrapPurple, wrapWhite,                              wrapOrange,)from .utilities import get_socket_maps, relink_socket_map, do_relinkfrom bpy.app.handlers import persistentdef TellClasses():    #Why use a function to do this? Because I don't need every class to register.    return [ MantisTree,             SchemaTree,            #  MantisNode,            #  SchemaNode,             MantisNodeGroup,             SchemaGroup,             MantisVisualizeTree,             MantisVisualizeNode,           ]class MantisTree(NodeTree):    '''A custom node tree type that will show up in the editor type list'''    bl_idname = 'MantisTree'    bl_label = "Rigging Nodes"    bl_icon = 'OUTLINER_OB_ARMATURE'        tree_valid:BoolProperty(default=False)    do_live_update:BoolProperty(default=True) # use this to disable updates for e.g. scripts    num_links:IntProperty(default=-1)    filepath:StringProperty(default="", subtype='FILE_PATH')    is_executing:BoolProperty(default=False)    is_exporting:BoolProperty(default=False)    execution_id:StringProperty(default='')        parsed_tree={}        def interface_update(self, context):        # no idea what this does        print ("Update Interface function in MantisTree class")     if bpy.app.version >= (3, 2):  # in 3.1 this can lead to a crash        @classmethod        def valid_socket_type(cls, socket_type: str):            # https://docs.blender.org/api/master/bpy.types.NodeTree.html#bpy.types.NodeTree.valid_socket_type            from .socket_definitions import Tell_bl_idnames            return socket_type in Tell_bl_idnames()            # thank you, Sverchok                def update_tree(self, context = None):        if self.is_exporting:            return        # return        self.is_executing = True        from . import readtree        prGreen("Validating Tree: %s" % self.name)        try:            self.parsed_tree = readtree.parse_tree(self)            if context:                self.display_update(context)            self.is_executing = False            self.tree_valid = True        except GraphError as e:            prRed("Failed to update node tree due to error.")            self.tree_valid = False            self.is_executing = False            raise e        def display_update(self, context):        if self.is_exporting:            return        current_tree = bpy.context.space_data.path[-1].node_tree        for node in current_tree.nodes:            if hasattr(node, "display_update"):                try:                    node.display_update(self.parsed_tree, context)                except Exception as e:                    print("Node \"%s\" failed to update display with error: %s" %(wrapGreen(node.name), wrapRed(e)))                    # raise e        # from .utilities import all_trees_in_tree        # all_my_trees = all_trees_in_tree(self,)        # for tree in all_my_trees:        # I think using the current visible tree is actually fine        if True:            # HACK             for l in current_tree.links:                l.is_valid = True # I don't want Blender marking these for me. No warning is better than a wrong warning.            # end HACK        # in the future, fix it properly. Here is a start.        else: # this is harder to fix than I thought!            tree_path_names=[None,]            for i, path_item in enumerate(bpy.context.space_data.path[:-1]):                tree_path_names.append(path_item.node_tree.nodes.active.name)            for l in current_tree.links: # We need to loop through actual links so we can filter them                if l.is_valid == False:                    if nc_from := self.parsed_tree.get( (*tree_path_names, l.from_node.name)) is None:                        continue                    for o in nc_from.outputs.values():                        for mlink in o.links:                            if (mlink.to_socket == l.to_socket.name) and (mlink.to_node.signature[-1] == l.to_node.name):                                l.is_valid = not mlink.is_hierarchy                                # in reality, we need to trace the link UP and find a cycle - then find a link in the cycle that is non-hierarchy                                # the actual link that BLENDER marks as invalid may be a hierarchy link.        # let's see if this works                def execute_tree(self,context):        if self.is_exporting:            return        # return        prGreen("Executing Tree: %s" % self.name)        self.is_executing = True        from . import readtree        try:            readtree.execute_tree(self.parsed_tree, self, context)        except RecursionError as e:            prRed("Recursion error while parsing tree.")            # prRed(e); node_tree.do_live_update = False        # except Exception:        #     pass        finally: # first time I have ever used a finally block in my life.            self.is_executing = False    # class SchemaPropertyGroup(bpy.types.PropertyGroup):class SchemaTree(NodeTree):    '''A node tree representing a schema to generate a Mantis tree'''    bl_idname = 'SchemaTree'    bl_label = "Rigging Nodes Schema"    bl_icon = 'RIGID_BODY_CONSTRAINT'    tree_valid:BoolProperty(default=False)    do_live_update:BoolProperty(default=True) # use this to disable updates for e.g. scripts    is_executing:BoolProperty(default=False)    num_links:IntProperty(default=-1)    # filepath:StringProperty(default="", subtype='FILE_PATH')        parsed_tree={}    # def update(self):    #     for n in self.nodes:    #         if hasattr(n, "update"): n.update()    # def update_tree(self, context):    #     prRed("update tree for Schema Tree!")    #     # self.tree_valid = True    #     # return    #     from . import readtree    #     prGreen("Validating Tree: %s" % self.name)    #     parsed_tree = readtree.parse_tree(self)    #     self.parsed_tree=parsed_tree    #     current_tree = bpy.context.space_data.path[-1].node_tree    #     self.tree_valid = True    #     prWhite("Number of Nodes: %s" % (len(self.parsed_tree)))    #     self.display_update(context)        # def display_update(self, context):    #     prRed("display update for Schema Tree!")    #     return    #     current_tree = bpy.context.space_data.path[-1].node_tree    #     for node in current_tree.nodes:    #         if hasattr(node, "display_update"):    #             try:    #                 node.display_update(self.parsed_tree, context)    #             except Exception as e:    #                 print("Node \"%s\" failed to update display with error: %s" %(wrapGreen(node.name), wrapRed(e)))    #                 # raise e    # def execute_tree(self,context):    #     self.is_executing = True    #     prRed("executing Schema Tree!")    #     self.tree_valid = False    #     self.is_executing = False    #     return    #     prGreen("Executing Tree: %s" % self.name)    #     from . import readtree    #     try:    #         readtree.execute_tree(self.parsed_tree, self, context)    #     except RecursionError as e:    #         prRed("Recursion error while parsing tree.")    #         prRed(e); node_tree.do_live_update = False    if bpy.app.version >= (3, 2):  # in 3.1 this can lead to a crash        @classmethod        def valid_socket_type(cls, socket_type: str):            # https://docs.blender.org/api/master/bpy.types.NodeTree.html#bpy.types.NodeTree.valid_socket_type            from .socket_definitions import Tell_bl_idnames            return socket_type in Tell_bl_idnames()            # thank you, Sverchok            class MantisNode:    # num_links:IntProperty(default=-1) # is this used anywhere?    # is_triggering_execute:BoolProperty(default=False)    # do_display_update:BoolProperty(default=False)    @classmethod    def poll(cls, ntree):        return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])                    def insert_link(self, link):        context = bpy.context        if context.space_data:            node_tree = context.space_data.path[0].node_tree            from . import readtree            if node_tree.do_live_update:                # prOrange("Updating from insert_link callback")                node_tree.update_tree(context)                if (link.to_socket.is_linked == False):                    node_tree.num_links+=1                elif (link.to_socket.is_multi_input):# and                     #len(link.to_socket.links) < link.to_socket.link_limit ):                    # the above doesn't work and I can't be bothered to fix it right now TODO                    node_tree.num_links+=1            class SchemaNode:    # is_triggering_execute:BoolProperty(default=False)    # do_display_update:BoolProperty(default=False)    @classmethod    def poll(cls, ntree):        return (ntree.bl_idname in ['SchemaTree'])                class LinkNode(MantisNode):    useTarget : BoolProperty(default=False)    @classmethod    def poll(cls, ntree):        return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])class xFormNode(MantisNode):    @classmethod    def poll(cls, ntree):        return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])class DeformerNode(MantisNode):    @classmethod    def poll(cls, ntree):        return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])# from bpy.types import NodeCustomGroupdef poll_node_tree(self, object):    if isinstance(object, MantisTree):        return True    return False# TODO this should check ID's instead of namedef node_group_update(node):    toggle_update = node.id_data.do_live_update    node.id_data.do_live_update = False    # prWhite (node.name, len(node.inputs), len(node.outputs))    identifiers_in={socket.identifier:socket for socket in node.inputs}    identifiers_out={socket.identifier:socket for socket in node.outputs}    if node.node_tree is None:        node.inputs.clear(); node.outputs.clear()        node.id_data.do_live_update = toggle_update        return    found_in, found_out = [], []    update_input, update_output = False, False    for item in node.node_tree.interface.items_tree:        if item.item_type != "SOCKET": continue        if item.in_out == 'OUTPUT':            if s:= identifiers_out.get(item.identifier): # if the requested output doesn't exist, update                found_out.append(item.identifier)                if update_output: continue # done here                if s.bl_idname != item.socket_type: update_output = True; continue            else: update_output = True; continue # prRed(f"Not found: {item.name}");         else:            if s:= identifiers_in.get(item.identifier): # if the requested input doesn't exist, update                found_in.append(item.identifier)                if update_input: continue # done here                if s.bl_idname != item.socket_type: update_input = True; continue            else: update_input = True; continue # prGreen(f"Not found: {item.name}");        # Schema has an extra input for Length and for Extend.    if node.bl_idname == 'MantisSchemaGroup':        found_in.extend(['Schema Length', ''])    # if we have too many elements, just get rid of the ones we don't need    if len(node.inputs) > len(found_in):#        for inp in node.inputs:            if inp.identifier in found_in: continue            node.inputs.remove(inp)    if len(node.outputs) > len(found_out):        for out in node.outputs:            if out.identifier in found_out: continue            node.outputs.remove(out)    #    if len(node.inputs) > 0 and (inp := node.inputs[-1]).bl_idname == 'WildcardSocket' and inp.is_linked:        # prPurple("oink! I am a piggy!")        update_input = True    if len(node.outputs) > 0 and  (out := node.outputs[-1]).bl_idname == 'WildcardSocket' and out.is_linked:        # prPurple("woof! I am a doggy!")        update_output = True    #    if not (update_input or update_output):        node.id_data.do_live_update = toggle_update        return    if update_input or update_output:        socket_map_in, socket_map_out = get_socket_maps(node)        if update_input :            if node.bl_idname == 'MantisSchemaGroup':                schema_length=0                if sl := node.inputs.get("Schema Length"):                    schema_length = sl.default_value                # sometimes this isn't available yet # TODO not happy about this solution            node.inputs.clear()            if node.bl_idname == 'MantisSchemaGroup':                node.inputs.new("IntSocket", "Schema Length", identifier='Schema Length')                node.inputs['Schema Length'].default_value = schema_length        if update_output: node.outputs.clear()        for item in node.node_tree.interface.items_tree:            if item.item_type != "SOCKET": continue            if (item.in_out == 'INPUT' and update_input):                relink_socket_map(node, node.inputs, socket_map_in, item)            if (item.in_out == 'OUTPUT' and update_output):                relink_socket_map(node, node.outputs, socket_map_out, item)                # at this point there is no wildcard socket        if '__extend__' in socket_map_in.keys():            do_relink(node, None, socket_map_in, in_out='INPUT', parent_name='Constant' )        node.id_data.do_live_update = toggle_updatedef node_tree_prop_update(self, context):    if self.is_updating: # this looks dumb... but update() can be called from update() in a sort of accidental way.        return    # prGreen("updating me...")    self.is_updating = True    node_group_update(self)    self.is_updating = False    if self.bl_idname in ['MantisSchemaGroup'] and self.node_tree is not None:        if len(self.inputs) == 0:            self.inputs.new("IntSocket", "Schema Length", identifier='Schema Length')        if self.inputs[-1].bl_idname != "WildcardSocket":            self.inputs.new("WildcardSocket", "", identifier="__extend__")from bpy.types import NodeCustomGroupclass MantisNodeGroup(Node, MantisNode):    bl_idname = "MantisNodeGroup"    bl_label = "Node Group"    node_tree:PointerProperty(type=NodeTree, poll=poll_node_tree, update=node_tree_prop_update,)    is_updating:BoolProperty(default=False)        def update(self):        if self.is_updating: # this looks dumb... but update() can be called from update() in a sort of accidental way.            return        self.is_updating = True        node_group_update(self)        self.is_updating = False    def draw_buttons(self, context, layout):        row = layout.row(align=True)        row.prop(self, "node_tree", text="")        row.operator("mantis.edit_group", text="", icon='NODETREE', emboss=True)        class CircularDependencyError(Exception):    passclass GraphError(Exception):    passdef get_signature_from_edited_tree(node, context):    sig_path=[None,]    for item in context.space_data.path[:-1]:        sig_path.append(item.node_tree.nodes.active.name)    return tuple(sig_path+[node.name])def poll_node_tree_schema(self, object):    if isinstance(object, SchemaTree):        return True    return False# def update_schema_length(self, context):#     pass # for now# TODO tiny UI problem - inserting new links into the tree will not place them in the right place.#this is a schema node in a mantis tree... kinda confusingclass SchemaGroup(Node, MantisNode):    bl_idname = "MantisSchemaGroup"    bl_label = "Node Schema"        node_tree:PointerProperty(type=NodeTree, poll=poll_node_tree_schema, update=node_tree_prop_update,)    # schema_length:IntProperty(default=5, update=update_schema_length)    is_updating:BoolProperty(default=False)    # incoming link    # from-node = name    # from socket = index or identifier or something    # property is unset as soon as it is used    # so the update function checks this property and handles the incoming link in an allowed place    # and actually the handle-link function can work as a switch - when the property is unset, it allows new links    # otherwise it unsets the property and returns.        # def init(self, context):    #     self.inputs.new("IntSocket", "Schema Length")    #     self.inputs.new("WildcardSocket", "")    def draw_buttons(self, context, layout):        row = layout.row(align=True)        row.prop(self, "node_tree", text="")        row.operator("mantis.edit_group", text="", icon='NODETREE', emboss=True)        # layout.prop(self, "schema_length", text="n=")        # WHAT IF:    #   - the function that creates the input/output map returns to a property in the node    #   - then the node handles the update in its update function.    def update(self):        if self.is_updating: # this looks dumb... but update() can be called from update() in a sort of accidental way.            return        self.is_updating = True        node_group_update(self)        # kinda dumb but necessary since update doesn't always fix this        if self.node_tree:            if len(self.inputs) == 0:                self.inputs.new("IntSocket", "Schema Length", identifier='Schema Length')            if self.inputs[-1].bl_idname != "WildcardSocket":                self.inputs.new("WildcardSocket", "", identifier="__extend__")        self.is_updating = False    # def insert_link(self, link):    #     if self.node_tree is None:    #         link.is_valid = False    #         return    #     sock_type = link.from_socket.bl_idname    #     for i, sock in enumerate(self.inputs):    #         if sock == link.to_socket: # dumb but whatever    #             identifier = link.to_socket.identifier    #             if sock.bl_idname not in ["WildcardSocket"]:    #                 if sock.is_linked == True:    #                     links = [ getattr(l, "from_socket") for l in sock.links ]    #                     name = sock.name    #                     # self.inputs.remove(sock)    #                     # new_input = self.inputs.new(sock_type, name, identifier=identifier, use_multi_input=True); self.inputs.move(-1, i)    #                     sock.display_shape = 'SQUARE_DOT'    #                     interface_item = self.node_tree.interface.items_tree[name]    #                     if not (interface_parent := self.node_tree.interface.items_tree.get('Array')):    #                         interface_parent = self.node_tree.interface.new_panel(name='Array')    #                     self.node_tree.interface.move_to_parent(interface_item, interface_parent, len(interface_parent.interface_items))    #                     # sock.link_limit = self.schema_length TODO this will be very hard to get at this point    #                     # self.id_data.links.new()    #             else: #if link.to_socket  == self.inputs[-1]:    #                     self.inputs.remove(sock)#self.inputs[-1])    #                     #    #                     name_stem = link.from_socket.bl_idname.replace('Socket',''); num=0    #                     if hasattr(link.from_socket, "default_value"):    #                         name_stem = type(link.from_socket.default_value).__name__    #                     for n in self.inputs:    #                         if name_stem in n.name: num+=1    #                     name = name_stem + '.' + str(num).zfill(3)    #                     #    #                     new_input = self.inputs.new(sock_type, name, identifier=identifier, use_multi_input=False); self.inputs.move(-1, i+1)    #                     new_input.link_limit = 1    #                     # link.to_socket = new_input    #                     # this seems to work    #                     self.inputs.new("WildcardSocket", "")    #                     if not (interface_parent := self.node_tree.interface.items_tree.get('Constant')):    #                         interface_parent = self.node_tree.interface.new_panel(name='Constant')    #                     self.node_tree.interface.new_socket(name=name,in_out='INPUT', socket_type=sock_type, parent=interface_parent)    #                     return                # TODO: investigate whether this is necessary    # @classmethod    # def poll(cls, ntree):    #     return (ntree.bl_idname in ['MantisTree'])# handlers!#annoyingly these have to be persistent@persistentdef update_handler(scene):    context=bpy.context    if context.space_data:        if not hasattr(context.space_data, "path"):            return        trees = [p.node_tree for p in context.space_data.path]        if not trees: return        if (node_tree := trees[0]).bl_idname in ['MantisTree']:            if node_tree.do_live_update and not (node_tree.is_executing or node_tree.is_exporting):                prev_links = node_tree.num_links                node_tree.num_links = len(node_tree.links)                if (prev_links == -1):                    return                if prev_links != node_tree.num_links:                    node_tree.tree_valid = False                if node_tree.tree_valid == False:                        node_tree.update_tree(context)@persistentdef execute_handler(scene):    context = bpy.context    if context.space_data:        if not hasattr(context.space_data, "path"):            return        trees = [p.node_tree for p in context.space_data.path]        if not trees: return        if (node_tree := trees[0]).bl_idname in ['MantisTree']:            if node_tree.tree_valid and node_tree.do_live_update and not (node_tree.is_executing or node_tree.is_exporting):                node_tree.execute_tree(context)                node_tree.tree_valid = False# @persistent# def load_post_handler(scene):#     print ("cuss and darn")#     # import bpy#     import sys#     def wrapRed(skk):    return "\033[91m{}\033[00m".format(skk)#     def intercept(fn, *args):#         print(wrapRed("Intercepting:"), fn)#         sys.stdout.flush()#         fn(*args)#         print(wrapRed("... done"))#         sys.stdout.flush()#     for attr in dir(bpy.app.handlers):#         if attr.startswith("_"):#             continue#         handler_list = getattr(bpy.app.handlers, attr)#         if attr =='load_post_handler':#             continue#         if not isinstance(handler_list, list):#             continue#         if not handler_list:#             continue#         print("Intercept Setup:", attr)#         handler_list[:] = [lambda *args: intercept(fn, *args) for fn in handler_list]#         # import cProfile#         from os import environ#         do_profile=False#         print (environ.get("DOPROFILE"))#         if environ.get("DOPROFILE"):#             do_profile=True#         if do_profile:#             # cProfile.runctx("tree.update_tree(context)", None, locals())#             # cProfile.runctx("tree.execute_tree(context)", None, locals())#             import hunter#             hunter.trace(stdlib=False, action=hunter.CallPrinter(force_colors=False))# #     def msgbus_callback(*args):# #         # print("something changed!")# #         print("Something changed!", args)# #     owner = object()#     subscribe_to = (bpy.types.Node, "location")#     subscribe_to = (bpy.types.Node, "color")#     subscribe_to = (bpy.types.Node, "dimensions")#     subscribe_to = (bpy.types.Node, "height")#     subscribe_to = (bpy.types.Node, "width")#     subscribe_to = (bpy.types.Node, "inputs")#     subscribe_to = (bpy.types.Node, "outputs")#     subscribe_to = (bpy.types.Node, "select")#     subscribe_to = (bpy.types.Node, "name")#     subscribe_to = (bpy.types.NodeSocket, "name")#     subscribe_to = (bpy.types.NodeSocket, "display_shape")#     bpy.msgbus.subscribe_rna(#         key=subscribe_to,#         owner=owner,#         args=(1, 2, 3),#         notify=msgbus_callback,#     )# print ("cuss and darn")# bpy.app.handlers.load_post.append(set_tree_invalid)bpy.app.handlers.depsgraph_update_pre.append(update_handler)bpy.app.handlers.depsgraph_update_post.append(execute_handler)# bpy.app.handlers.load_post.append(load_post_handler)# # import bpy# import sys# def wrapRed(skk):    return "\033[91m{}\033[00m".format(skk)# def intercept(fn, *args):#     print(wrapRed("Intercepting:"), fn)#     sys.stdout.flush()#     fn(*args)#     print(wrapRed("... done"))#     sys.stdout.flush()# for attr in dir(bpy.app.handlers):#     if attr.startswith("_"):#         continue#     handler_list = getattr(bpy.app.handlers, attr)#     if attr =='load_post_handler':#         continue#     if not isinstance(handler_list, list):#         continue#     if not handler_list:#         continue#     print("Intercept Setup:", attr)#     handler_list[:] = [lambda *args: intercept(fn, *args) for fn in handler_list]class MantisVisualizeTree(NodeTree):    '''A custom node tree type that will show up in the editor type list'''    bl_idname = 'MantisVisualizeTree'    bl_label = "mantis output"    bl_icon = 'HIDE_OFF'class MantisVisualizeNode(Node):    bl_idname = "MantisVisualizeNode"    bl_label = "Node"    @classmethod    def poll(cls, ntree):        return (ntree.bl_idname in ['MantisVisualizeTree'])        def init(self, context):        pass        def gen_data(self, nc, np = None):        self.use_custom_color = True        if nc.node_type == 'XFORM':            self.color = (1.0 ,0.5, 0.0)        if nc.node_type == 'LINK':            self.color = (0.4 ,0.2, 1.0)        if nc.node_type == 'UTILITY':            self.color = (0.2 ,0.2, 0.2)        if nc.node_type == 'SCHEMA':            self.color = (0.85 ,0.95, 0.9)        if nc.node_type == 'DUMMY':            self.color = (0.05 ,0.05, 0.15)        self.name = ''.join(nc.signature[1:])        if np:            if np.label:                self.label=np.label            else:                self.label=np.name            for inp in nc.inputs:                s = self.inputs.new('WildcardSocket', inp)                try:                    if sock := np.inputs.get(inp):                        s.color = inp.color                except AttributeError: #default bl_idname types like Float and Vector, no biggie                    pass                except KeyError:                    pass            for out in nc.outputs:                s = self.outputs.new('WildcardSocket', out)                try:                    if sock := np.outputs.get(out):                        s.color = out.color                except AttributeError: #default bl_idname types like Float and Vector, no biggie                    pass                except KeyError:                    pass            self.location = np.location # will get overwritten by Grandalf later.        else:            self.label = nc.signature[-1] # which is be the unique name.            for inp in nc.inputs:                self.inputs.new('WildcardSocket', inp)            for out in nc.outputs:                self.outputs.new('WildcardSocket', out)                # replace names with bl_idnames for reading the tree and solving schemas.replace_types = ["NodeGroupInput", "NodeGroupOutput", "SchemaIncomingConnection",                 "SchemaArrayInput", "SchemaConstInput", "SchemaConstOutput", "SchemaIndex",                 "SchemaOutgoingConnection", "SchemaConstantOutput", "SchemaArrayOutput",                 "SchemaArrayInputGet",]# anything that gets properties added in the graph... this is a clumsy approach but I need to watch for this#   in schema generation and this is the easiest way to do it for now.custom_props_types = ["LinkArmature", "UtilityKeyframe", "UtilityFCurve", "UtilityDriver", "xFormBone"]from_name_filter = ["Driver",]to_name_filter = [                   "Custom Object xForm Override",                   "Custom Object",                   "Deform Bones",                 ]
 |