| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 | #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_relinkdef TellClasses():    #Why use a function to do this? Because I don't need every class to register.    return [ MantisTree,             SchemaTree,             MantisNodeGroup,             SchemaGroup,           ]def error_popup_draw(self, context):    self.layout.label(text="Error executing tree. See Console.")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={}    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        finally:            self.is_executing = False        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                # TODO: deal with invalid links properly.        #    - Non-hierarchy links should be ignored in the circle-check and so the links should be marked valid in such a circle        #    - hierarchy-links should be marked invalid and prevent the tree from executing.                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.")        finally:            self.is_executing = Falseclass 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'    # these are only needed for consistent interface, but should not be used    do_live_update:BoolProperty(default=True) # default to true so that updates work    is_executing:BoolProperty(default=False)    is_exporting:BoolProperty(default=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:    @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:                node_tree.update_tree(context)                if (link.to_socket.is_linked == False):                    node_tree.num_links+=1                elif (link.to_socket.is_multi_input):                    node_tree.num_links+=1            class SchemaNode:    @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'])def poll_node_tree(self, object):    if isinstance(object, MantisTree):        return True    return False# TODO: try to check identifiers instead of name.def node_group_update(node):    if (node.id_data.do_live_update == False) or (node.id_data.is_executing == True):        return    # note: if (node.id_data.is_exporting == True) I need to be able to update so I can make links.    toggle_update = node.id_data.do_live_update    node.id_data.do_live_update = False    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                if s.bl_idname != item.socket_type: update_output = True; continue            else: update_output = True; continue        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        # 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:        update_input = True    if len(node.outputs) > 0 and  (out := node.outputs[-1]).bl_idname == 'WildcardSocket' and out.is_linked:        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: # update() can be called from update() and that leads to an infinite loop.        return           # so we check if an update is currently running.    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: # update() can be called from update() and that leads to an infinite loop.            return           # so we check if an update is currently running.        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 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# TODO tiny UI problem - inserting new links into the tree will not place them in the right place.class 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,)    is_updating:BoolProperty(default=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)    def update(self):        if self.is_updating: # update() can be called from update() and that leads to an infinite loop.            return           # so we check if an update is currently running.        self.is_updating = True        node_group_update(self)        # reset things if necessary:        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# 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"]# filters for determining if a link is a hierarchy link or a non-hierarchy (cyclic) link.from_name_filter = ["Driver",]to_name_filter = [                   "Custom Object xForm Override",                   "Custom Object",                   "Deform Bones",                 ]
 |