| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090 | 
							- import bpy
 
- from bpy.types import Operator
 
- from mathutils import Vector
 
- from .utilities import (prRed, prGreen, prPurple, prWhite,
 
-                               prOrange,
 
-                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
 
-                               wrapOrange,)
 
- def mantis_tree_poll_op(context):
 
-     space = context.space_data
 
-     if hasattr(space, "node_tree"):
 
-         if (space.node_tree):
 
-             return (space.tree_type in ["MantisTree", "SchemaTree"])
 
-     return False
 
- def any_tree_poll(context):
 
-     space = context.space_data
 
-     if hasattr(space, "node_tree"):
 
-         return True
 
-     return False
 
- def tag_mantis_node_for_update(context, node):
 
-     if any_tree_poll(context):
 
-         from .base_definitions import get_signature_from_edited_tree
 
-         node_tree = context.space_data.path[0].node_tree
 
-         mantis_node = node_tree.parsed_tree.get(
 
-                     get_signature_from_edited_tree(node, context))
 
-         if mantis_node is None:
 
-             node_tree.hash = ''
 
-         else:
 
-             mantis_node.reset_execution_recursive() # tag this as a change.
 
- #########################################################################3
 
- class MantisGroupNodes(Operator):
 
-     """Create node-group from selected nodes"""
 
-     bl_idname = "mantis.group_nodes"
 
-     bl_label = "Group Nodes"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return mantis_tree_poll_op(context)
 
-     def execute(self, context):
 
-         base_tree=context.space_data.path[-1].node_tree
 
-         try:
 
-             for path_item in context.space_data.path:
 
-                 path_item.node_tree.is_exporting = True
 
-             from .i_o import export_to_json, do_import
 
-             from random import random
 
-             grp_name = "".join([chr(int(random()*30)+35) for i in range(20)])
 
-             trees=[base_tree]
 
-             selected_nodes=export_to_json(trees, base_tree=None, write_file=False, only_selected=True)
 
-             # this snippet of confusing indirection edits the name of the base tree in the JSON data
 
-             selected_nodes[base_tree.name][0]["name"]=grp_name
 
-             do_import(selected_nodes, context)
 
-             affected_links_in = []
 
-             affected_links_out = []
 
-             for l in base_tree.links:
 
-                 if l.from_node.select and not l.to_node.select: affected_links_out.append(l)
 
-                 if not l.from_node.select and l.to_node.select: affected_links_in.append(l)
 
-             delete_me = []
 
-             all_nodes_bounding_box=[Vector((float("inf"),float("inf"))), Vector((-float("inf"),-float("inf")))]
 
-             for n in base_tree.nodes:
 
-                 if n.select:
 
-                     node_loc = (0,0,0)
 
-                     if bpy.app.version <= (4, 4):
 
-                         node_loc = n.location
 
-                         parent = n.parent
 
-                         while (parent): # accumulate parent offset
 
-                             node_loc += parent.location
 
-                             parent = parent.parent
 
-                     else: # there is a new location_absolute property in 4.4
 
-                         node_loc = n.location_absolute
 
-                     if node_loc.x < all_nodes_bounding_box[0].x:
 
-                         all_nodes_bounding_box[0].x = node_loc.x
 
-                     if node_loc.y < all_nodes_bounding_box[0].y:
 
-                         all_nodes_bounding_box[0].y = node_loc.y
 
-                     #
 
-                     if node_loc.x > all_nodes_bounding_box[1].x:
 
-                         all_nodes_bounding_box[1].x = node_loc.x
 
-                     if node_loc.y > all_nodes_bounding_box[1].y:
 
-                         all_nodes_bounding_box[1].y = node_loc.y
 
-                     delete_me.append(n)
 
-             grp_node = base_tree.nodes.new('MantisNodeGroup')
 
-             grp_node.node_tree = bpy.data.node_groups[grp_name]
 
-             bb_center = all_nodes_bounding_box[0].lerp(all_nodes_bounding_box[1],0.5)
 
-             for n in grp_node.node_tree.nodes:
 
-                 n.location -= bb_center
 
-             grp_node.location = Vector((all_nodes_bounding_box[0].x+200, all_nodes_bounding_box[0].lerp(all_nodes_bounding_box[1], 0.5).y))
 
-             from .base_definitions import node_group_update
 
-             grp_node.is_updating=True
 
-             try:
 
-                 node_group_update(grp_node, force=True)
 
-             finally:
 
-                 grp_node.is_updating=False
 
-             # for each node in the JSON
 
-             for n in selected_nodes[base_tree.name][2].values():
 
-                 for socket_collection in ['inputs', 'outputs']:
 
-                     for s in n[socket_collection].values(): # for each socket in the node
 
-                         if source := s.get("source"):
 
-                             prGreen (s["name"], source[0], source[1])
 
-                             base_tree_node=base_tree.nodes.get(source[0])
 
-                             if s["is_output"]:
 
-                                 for output in base_tree_node.outputs:
 
-                                     if output.identifier == source[1]:
 
-                                         break
 
-                                 else:
 
-                                     raise RuntimeError(wrapRed("Socket not found when grouping"))
 
-                                 base_tree.links.new(input=output, output=grp_node.inputs[s["name"]])
 
-                             else:
 
-                                 for s_input in base_tree_node.inputs:
 
-                                     if s_input.identifier == source[1]:
 
-                                         break
 
-                                 else:
 
-                                     raise RuntimeError(wrapRed("Socket not found when grouping"))
 
-                                 base_tree.links.new(input=grp_node.outputs[s["name"]], output=s_input)
 
-             for n in delete_me: base_tree.nodes.remove(n)
 
-             base_tree.nodes.active = grp_node
 
-         finally: # MAKE SURE to turn it back to not exporting
 
-             for path_item in context.space_data.path:
 
-                 path_item.node_tree.is_exporting = False
 
-         grp_node.node_tree.name = "Group_Node.000"
 
-         return {'FINISHED'}
 
- class MantisEditGroup(Operator):
 
-     """Edit the group referenced by the active node (or exit the current node-group)"""
 
-     bl_idname = "mantis.edit_group"
 
-     bl_label = "Edit Group"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (
 
-             mantis_tree_poll_op(context)
 
-         )
 
-     def execute(self, context):
 
-         space = context.space_data
 
-         path = space.path
 
-         node = path[len(path)-1].node_tree.nodes.active
 
-         base_tree = path[0].node_tree
 
-         live_update_start_state=base_tree.do_live_update
 
-         base_tree.do_live_update = False
 
-         base_tree.is_executing = True
 
-         try:
 
-             if hasattr(node, "node_tree"):
 
-                 if (node.node_tree):
 
-                     path.append(node.node_tree, node=node)
 
-                     path[0].node_tree.display_update(context)
 
-                     return {"FINISHED"}
 
-             elif len(path) > 1:
 
-                 path.pop()
 
-                 # get the active node in the current path
 
-                 active = path[len(path)-1].node_tree.nodes.active
 
-                 from .base_definitions import node_group_update
 
-                 active.is_updating = True
 
-                 try:
 
-                     node_group_update(active, force = True)
 
-                 finally:
 
-                     active.is_updating = False
 
-                 # call update to force the node group to check if its tree has changed
 
-                 # now we need to loop through the tree and update all node groups of this type.
 
-                 from .utilities import get_all_nodes_of_type
 
-                 for g in get_all_nodes_of_type(base_tree, "MantisNodeGroup") + \
 
-                          get_all_nodes_of_type(base_tree, "MantisSchemaGroup"):
 
-                     if g.node_tree == active.node_tree:
 
-                         g.is_updating = True
 
-                         active.is_updating = True
 
-                         try:
 
-                             node_group_update(g, force = True)
 
-                         finally:
 
-                             g.is_updating = False
 
-                             active.is_updating = False
 
-                 base_tree.display_update(context)
 
-                 base_tree.is_executing = True
 
-                 # base_tree.is_executing = True # because it seems display_update unsets this.
 
-         finally:
 
-             base_tree.do_live_update = live_update_start_state
 
-             base_tree.is_executing = False
 
-             # HACK
 
-             base_tree.handler_flip = True # HACK
 
-             # HACK
 
-             # I have no idea why but the operator finishing causes the exeuction handler to fire
 
-             # I have no control over this since it happens after the execution returns...
 
-             # so I have to do this ridiculous hack with a Boolean flip bit.
 
-             return {"FINISHED"}
 
- class MantisNewNodeTree(Operator):
 
-     """Add a new Mantis tree."""
 
-     bl_idname = "mantis.new_node_tree"
 
-     bl_label = "New Node Group"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (
 
-             mantis_tree_poll_op(context) and \
 
-                 context.node.bl_idname in ["MantisNodeGroup", "MantisSchemaGroup"]
 
-         )
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return True #(hasattr(context, 'active_node') )
 
-     def invoke(self, context, event):
 
-         self.tree_invoked = context.node.id_data.name
 
-         self.node_invoked = context.node.name
 
-         return self.execute(context)
 
-     def execute(self, context):
 
-         node = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
 
-         if node.bl_idname == "MantisSchemaGroup":
 
-             if (node.node_tree):
 
-                 print('a')
 
-                 return {"CANCELLED"}
 
-             else:
 
-                 from bpy import data
 
-                 print('b')
 
-                 node.node_tree = data.node_groups.new(name='Schema Group', type='SchemaTree')
 
-                 return {'FINISHED'}
 
-         elif node.bl_idname == "MantisNodeGroup":
 
-             if (node.node_tree):
 
-                 print('c')
 
-                 return {"CANCELLED"}
 
-             else:
 
-                 from bpy import data
 
-                 print('d')
 
-                 node.node_tree = data.node_groups.new(name='Mantis Group', type='MantisTree')
 
-                 return {'FINISHED'}
 
-         else:
 
-             return {"CANCELLED"}
 
- class InvalidateNodeTree(Operator):
 
-     """Invalidates this node tree, forcing it to read all data again."""
 
-     bl_idname = "mantis.invalidate_node_tree"
 
-     bl_label = "Clear Node Tree Cache"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (mantis_tree_poll_op(context))
 
-     def execute(self, context):
 
-         print("Clearing Active Node Tree cache")
 
-         tree=context.space_data.path[0].node_tree
 
-         tree.execution_id=''; tree.hash=''
 
-         return {"FINISHED"}
 
- class ExecuteNodeTree(Operator):
 
-     """Execute this node tree"""
 
-     bl_idname = "mantis.execute_node_tree"
 
-     bl_label = "Execute Node Tree"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (mantis_tree_poll_op(context))
 
-     def execute(self, context):
 
-         from time import time
 
-         from .utilities import wrapGreen
 
-         tree=context.space_data.path[0].node_tree
 
-         import cProfile
 
-         from os import environ
 
-         start_time = time()
 
-         do_profile=False
 
-         if environ.get("DOPROFILE"):
 
-             do_profile=True
 
-         pass_error = True
 
-         if environ.get("DOERROR"):
 
-             pass_error=False
 
-         if do_profile:
 
-             import pstats, io
 
-             from pstats import SortKey
 
-             with cProfile.Profile() as pr:
 
-                 tree.update_tree(context, error_popups = pass_error)
 
-                 tree.execute_tree(context, error_popups = pass_error)
 
-                 # from the Python docs at https://docs.python.org/3/library/profile.html#module-cProfile
 
-                 s = io.StringIO()
 
-                 sortby = SortKey.TIME
 
-                 # sortby = SortKey.CUMULATIVE
 
-                 ps = pstats.Stats(pr, stream=s).strip_dirs().sort_stats(sortby)
 
-                 ps.print_stats(20) # print the top 20
 
-                 print(s.getvalue())
 
-         else:
 
-             tree.update_tree(context, error_popups = pass_error)
 
-             tree.execute_tree(context, error_popups = pass_error)
 
-         prGreen("Finished executing tree in %f seconds" % (time() - start_time))
 
-         return {"FINISHED"}
 
- class SelectNodesOfType(Operator):
 
-     """Selects all nodes of same type as active node."""
 
-     bl_idname = "mantis.select_nodes_of_type"
 
-     bl_label = "Select Nodes of Same Type as Active"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (any_tree_poll(context))
 
-     def execute(self, context):
 
-         active_node = context.active_node
 
-         tree = active_node.id_data
 
-         if not hasattr(active_node, "node_tree"):
 
-             for node in tree.nodes:
 
-                 node.select = (active_node.bl_idname == node.bl_idname)
 
-         else:
 
-             for node in tree.nodes:
 
-                 node.select = (active_node.bl_idname == node.bl_idname) and (active_node.node_tree == node.node_tree)
 
-         return {"FINISHED"}
 
- def get_parent_tree_interface_enum(operator, context):
 
-     ret = []; i = -1
 
-     tree = bpy.data.node_groups[operator.tree_invoked]
 
-     for sock in tree.interface.items_tree:
 
-         if sock.item_type == 'PANEL': continue
 
-         if sock.in_out == "OUTPUT": continue
 
-         ret.append( (sock.identifier, sock.name, "Socket from Node Group Input", i := i + 1), )
 
-     return ret
 
- def get_node_inputs_enum(operator, context):
 
-     ret = []; i = -1
 
-     n = bpy.data.node_groups[operator.tree_invoked].nodes[operator.node_invoked]
 
-     for inp in n.inputs:
 
-         ret.append( (inp.identifier, inp.name, "Socket of node to connect to.", i := i + 1), )
 
-     return ret
 
- class ConnectNodeToInput(Operator):
 
-     """Connects a Node Group Input socket to specified socket of active node and all selected same-type nodes."""
 
-     bl_idname = "mantis.connect_nodes_to_input"
 
-     bl_label = "Connect Socket to Input for Selected Nodes"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     group_output : bpy.props.EnumProperty(
 
-         items=get_parent_tree_interface_enum,
 
-         name="Node Group Input Socket",
 
-         description="Select which socket from the Node Group Input to connect to this node",)
 
-     node_input : bpy.props.EnumProperty(
 
-         items=get_node_inputs_enum,
 
-         name="Node Input Socket",
 
-         description="Select which of this node's sockets to recieve the connection",)
 
-     tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     world_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     material_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     @classmethod
 
-     def poll(cls, context):
 
-         is_tree = (any_tree_poll(context))
 
-         is_material = False
 
-         if hasattr(context, 'space_data') and context.space_data.type == 'NODE_EDITOR':
 
-             is_material = context.space_data.node_tree.bl_idname == 'ShaderNodeTree'
 
-         return is_tree and not is_material # doesn't work for Material right now TODO
 
-     def invoke(self, context, event):
 
-         self.material_invoked=''; self.world_invoked=''
 
-         self.tree_invoked = context.active_node.id_data.name
 
-         if context.space_data.node_tree.bl_idname == 'ShaderNodeTree':
 
-             if context.space_data.shader_type == 'WORLD':
 
-                 self.world_invoked = context.space_data.id.name
 
-             elif context.space_data.shader_type == 'OBJECT':
 
-                 self.material_invoked = context.space_data.id.name
 
-             else:
 
-                 return {'CANCELLED'} # what is the LINESTYLE menu? TODO
 
-         self.node_invoked = context.active_node.name
 
-         # we use active_node here ^ because we are comparing the active node to the selection.
 
-         wm = context.window_manager
 
-         return wm.invoke_props_dialog(self)
 
-     def execute(self, context):
 
-         if context.space_data.node_tree.bl_idname == 'ShaderNodeTree':
 
-             if context.space_data.shader_type == 'WORLD':
 
-                 t = bpy.data.worlds[self.material_invoked].node_tree
 
-             elif context.space_data.shader_type == 'OBJECT':
 
-                 t = bpy.data.materials[self.material_invoked].node_tree
 
-         else:
 
-             t = bpy.data.node_groups.get(self.tree_invoked)
 
-         if hasattr(t, "is_executing"): # for Mantis trees, but this function should just work anywhere.
 
-             t.is_executing = True
 
-         n = t.nodes[self.node_invoked]
 
-         for node in t.nodes:
 
-             if n.bl_idname == node.bl_idname and node.select:
 
-                 # the bl_idname is the same so they both have node_tree
 
-                 if hasattr(n, "node_tree") and n.node_tree != node.node_tree: continue
 
-                 # TODO: maybe I should try and find a nearby input node and reuse it
 
-                 # doing these identifier lookups again and again is slow, whatever. faster than doing it by hand
 
-                 for connect_to_me in node.inputs:
 
-                     if connect_to_me.identifier == self.node_input: break
 
-                 if connect_to_me.is_linked: connect_to_me = None
 
-                 if connect_to_me: # only make the node if the socket is there and free
 
-                     inp = t.nodes.new("NodeGroupInput")
 
-                     connect_me = None
 
-                     for s in inp.outputs:
 
-                         if s.identifier != self.group_output: s.hide = True
 
-                         else: connect_me = s
 
-                         inp.location = node.location
 
-                         inp.location.x-=200
 
-                     if connect_me is None:
 
-                         continue # I don't know how this happens.
 
-                     # TODO: this bit doesn't work in shader trees for some reason, fix it
 
-                     t.links.new(input=connect_me, output=connect_to_me)
 
-         if hasattr(t, "is_executing"):
 
-             t.is_executing = False
 
-         return {"FINISHED"}
 
- class QueryNodeSockets(Operator):
 
-     """Utility Operator for querying the data in a socket"""
 
-     bl_idname = "mantis.query_sockets"
 
-     bl_label = "Query Node Sockets"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (mantis_tree_poll_op(context))
 
-     def execute(self, context):
 
-         active_node = context.active_node
 
-         tree = active_node.id_data
 
-         for node in tree.nodes:
 
-             if not node.select: continue
 
-         return {"FINISHED"}
 
- class FoceUpdateGroup(Operator):
 
-     """Developer Tool to force a group update"""
 
-     bl_idname = "mantis.force_update_group"
 
-     bl_label = "Force Mantis Group Update"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (mantis_tree_poll_op(context))
 
-     def execute(self, context):
 
-         active_node = context.active_node
 
-         from .base_definitions import node_group_update
 
-         try:
 
-             active_node.is_updating = True
 
-             node_group_update(active_node, force=True)
 
-         finally:
 
-             active_node.is_updating = False
 
-         return {"FINISHED"}
 
- class ForceDisplayUpdate(Operator):
 
-     """Utility Operator for querying the data in a socket"""
 
-     bl_idname = "mantis.force_display_update"
 
-     bl_label = "Force Mantis Display Update"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (mantis_tree_poll_op(context))
 
-     def execute(self, context):
 
-         base_tree = bpy.context.space_data.path[0].node_tree
 
-         base_tree.display_update(context)
 
-         return {"FINISHED"}
 
- class CleanUpNodeGraph(bpy.types.Operator):
 
-     """Clean Up Node Graph"""
 
-     bl_idname = "mantis.nodes_cleanup"
 
-     bl_label = "Clean Up Node Graph"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     # num_iterations=bpy.props.IntProperty(default=8)
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return hasattr(context, 'active_node')
 
-     def execute(self, context):
 
-         base_tree=context.space_data.path[-1].node_tree
 
-         from .utilities import SugiyamaGraph
 
-         SugiyamaGraph(base_tree, 12)
 
-         return {'FINISHED'}
 
- class MantisMuteNode(Operator):
 
-     """Mantis Test Operator"""
 
-     bl_idname = "mantis.mute_node"
 
-     bl_label = "Mute Node"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (mantis_tree_poll_op(context))
 
-     def execute(self, context):
 
-         path = context.space_data.path
 
-         node = path[len(path)-1].node_tree.nodes.active
 
-         node.mute = not node.mute
 
-         # There should only be one of these
 
-         if (enable := node.inputs.get("Enable")):
 
-                 # annoyingly, 'mute' and 'enable' are opposites
 
-                 enable.default_value = not node.mute
 
-         # this one is for Deformers
 
-         elif (enable := node.inputs.get("Enable in Viewport")):
 
-                 # annoyingly, 'mute' and 'enable' are opposites
 
-                 enable.default_value = not node.mute
 
-         elif (hide := node.inputs.get("Hide")):
 
-                 hide.default_value = node.mute
 
-         elif (hide := node.inputs.get("Hide in Viewport")):
 
-                 hide.default_value = node.mute
 
-         return {"FINISHED"}
 
- ePropertyType =(
 
-         ('BOOL'  , "Boolean", "Boolean", 0),
 
-         ('INT'   , "Integer", "Integer", 1),
 
-         ('FLOAT' , "Float"  , "Float"  , 2),
 
-         ('VECTOR', "Vector" , "Vector" , 3),
 
-         ('STRING', "String" , "String" , 4),
 
-         #('ENUM'  , "Enum"   , "Enum"   , 5),
 
-     )
 
- from .base_definitions import xFormNode
 
- class AddCustomProperty(bpy.types.Operator):
 
-     """Add Custom Property to xForm Node"""
 
-     bl_idname = "mantis.add_custom_property"
 
-     bl_label = "Add Custom Property"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     prop_type : bpy.props.EnumProperty(
 
-         items=ePropertyType,
 
-         name="New Property Type",
 
-         description="Type of data for new Property",
 
-         default = 'BOOL',)
 
-     prop_name  : bpy.props.StringProperty(default='Prop')
 
-     min:bpy.props.FloatProperty(default = 0)
 
-     max:bpy.props.FloatProperty(default = 1)
 
-     soft_min:bpy.props.FloatProperty(default = 0)
 
-     soft_max:bpy.props.FloatProperty(default = 1)
 
-     description:bpy.props.StringProperty(default = "")
 
-     tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return True #( hasattr(context, 'node') )
 
-     def invoke(self, context, event):
 
-         self.tree_invoked = context.node.id_data.name
 
-         self.node_invoked = context.node.name
 
-         wm = context.window_manager
 
-         return wm.invoke_props_dialog(self)
 
-     def execute(self, context):
 
-         n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
 
-         # For whatever reason, context.node doesn't exist anymore
 
-         #   (probably because I use a window to execute)
 
-         # so as a sort of dumb workaround I am saving it to a hidden
 
-         #  property of the operator... it works but Blender complains.
 
-         socktype = ''
 
-         if not (self.prop_name):
 
-             self.report({'ERROR_INVALID_INPUT'}, "Must name the property.")
 
-             return {'CANCELLED'}
 
-         if self.prop_type == 'BOOL':
 
-             socktype = 'ParameterBoolSocket'
 
-         if self.prop_type == 'INT':
 
-             socktype = 'ParameterIntSocket'
 
-         if self.prop_type == 'FLOAT':
 
-             socktype = 'ParameterFloatSocket'
 
-         if self.prop_type == 'VECTOR':
 
-             socktype = 'ParameterVectorSocket'
 
-         if self.prop_type == 'STRING':
 
-             socktype = 'ParameterStringSocket'
 
-         #if self.prop_type == 'ENUM':
 
-         #    sock_type = 'ParameterStringSocket'
 
-         if (s := n.inputs.get(self.prop_name)):
 
-             try:
 
-                 number = int(self.prop_name[-3:])
 
-                 # see if it has a number
 
-                 number+=1
 
-                 self.prop_name = self.prop_name[:-3] + str(number).zfill(3)
 
-             except ValueError:
 
-                 self.prop_name+='.001'
 
-                 # WRONG # HACK # TODO # BUG #
 
-         new_prop = n.inputs.new( socktype, self.prop_name)
 
-         if self.prop_type in ['INT','FLOAT']:
 
-             new_prop.min = self.min
 
-             new_prop.max = self.max
 
-             new_prop.soft_min = self.soft_min
 
-             new_prop.soft_max = self.soft_max
 
-         new_prop.description = self.description
 
-         # now do the output
 
-         n.outputs.new( socktype, self.prop_name)
 
-         tag_mantis_node_for_update(context, n)
 
-         return {'FINISHED'}
 
- def main_get_existing_custom_properties(operator, context):
 
-     ret = []; i = -1
 
-     n = bpy.data.node_groups[operator.tree_invoked].nodes[operator.node_invoked]
 
-     for inp in n.inputs:
 
-         if 'Parameter' in inp.bl_idname:
 
-             ret.append( (inp.identifier, inp.name, "Custom Property to Modify", i := i + 1), )
 
-     return ret
 
- class EditCustomProperty(bpy.types.Operator):
 
-     """Edit Custom Property"""
 
-     bl_idname = "mantis.edit_custom_property"
 
-     bl_label = "Edit Custom Property"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     def get_existing_custom_properties(self, context):
 
-         return main_get_existing_custom_properties(self, context)
 
-     prop_edit : bpy.props.EnumProperty(
 
-         items=get_existing_custom_properties,
 
-         name="Property to Edit?",
 
-         description="Select which property to edit",)
 
-     prop_type : bpy.props.EnumProperty(
 
-         items=ePropertyType,
 
-         name="New Property Type",
 
-         description="Type of data for new Property",
 
-         default = 'BOOL',)
 
-     prop_name  : bpy.props.StringProperty(default='Prop')
 
-     min:bpy.props.FloatProperty(default = 0)
 
-     max:bpy.props.FloatProperty(default = 1)
 
-     soft_min:bpy.props.FloatProperty(default = 0)
 
-     soft_max:bpy.props.FloatProperty(default = 1)
 
-     description:bpy.props.StringProperty(default = "") # TODO: use getters to fill these automatically
 
-     tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return True #( hasattr(context, 'node') )
 
-     def invoke(self, context, event):
 
-         self.tree_invoked = context.node.id_data.name
 
-         self.node_invoked = context.node.name
 
-         wm = context.window_manager
 
-         return wm.invoke_props_dialog(self)
 
-     def execute(self, context):
 
-         n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
 
-         prop = n.inputs.get( self.prop_edit )
 
-         if prop:
 
-             prop.name = self.prop_name
 
-             if (s := n.inputs.get(self.prop_edit)):
 
-                 if self.prop_type in ['INT','FLOAT']:
 
-                     prop.min = self.min
 
-                     prop.max = self.max
 
-                     prop.soft_min = self.soft_min
 
-                     prop.soft_max = self.soft_max
 
-                 prop.description = self.description
 
-             tag_mantis_node_for_update(context, n)
 
-             return {'FINISHED'}
 
-         else:
 
-             self.report({'ERROR_INVALID_INPUT'}, "Cannot edit a property that does not exist.")
 
- class RemoveCustomProperty(bpy.types.Operator):
 
-     """Remove a Custom Property from an xForm Node"""
 
-     bl_idname = "mantis.remove_custom_property"
 
-     bl_label = "Remove Custom Property"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     def get_existing_custom_properties(self, context):
 
-         return main_get_existing_custom_properties(self, context)
 
-     prop_remove : bpy.props.EnumProperty(
 
-         items=get_existing_custom_properties,
 
-         name="Property to remove?",
 
-         description="Select which property to remove",)
 
-     tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return True #(hasattr(context, 'active_node') )
 
-     def invoke(self, context, event):
 
-         self.tree_invoked = context.node.id_data.name
 
-         self.node_invoked = context.node.name
 
-         t = context.node.id_data
 
-         # HACK the props dialog makes this necesary
 
-         #  because context.node only exists during the event that
 
-         #  was created by clicking on the node.
 
-         t.nodes.active = context.node # HACK
 
-         context.node.select = True # HACK
 
-         # I need this bc of the callback for the enum property.
 
-         #  for whatever reason I can't use tree_invoked there
 
-         wm = context.window_manager
 
-         return wm.invoke_props_dialog(self)
 
-     def execute(self, context):
 
-         n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
 
-         # For whatever reason, context.node doesn't exist anymore
 
-         #   (probably because I use a window to execute)
 
-         # so as a sort of dumb workaround I am saving it to a hidden
 
-         #  property of the operator... it works.
 
-         for i, inp in enumerate(n.inputs):
 
-             if inp.identifier == self.prop_remove:
 
-                 break
 
-         else:
 
-             self.report({'ERROR'}, "Input not found")
 
-             return {'CANCELLED'}
 
-         # it's possible that the output property's identifier isn't the
 
-         #   exact same... but I don' care. Shouldn't ever happen. TODO
 
-         for j, out in enumerate(n.outputs):
 
-             if out.identifier == self.prop_remove:
 
-                 break
 
-         else:
 
-             self.report({'ERROR'}, "Output not found")
 
-             raise RuntimeError("This should not happen!")
 
-         n.inputs.remove ( n.inputs [i] )
 
-         n.outputs.remove( n.outputs[j] )
 
-         tag_mantis_node_for_update(context, n)
 
-         return {'FINISHED'}
 
- # SIMPLE node operators...
 
- # May rewrite these in a more generic way later
 
- class FcurveAddKeyframeInput(bpy.types.Operator):
 
-     """Add a keyframe input to the fCurve node"""
 
-     bl_idname = "mantis.fcurve_node_add_kf"
 
-     bl_label = "Add Keyframe"
 
-     bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (hasattr(context, 'active_node') )
 
-     def execute(self, context):
 
-         num_keys = len( context.node.inputs)-1
 
-         context.node.inputs.new("KeyframeSocket", "Keyframe."+str(num_keys).zfill(3))
 
-         return {'FINISHED'}
 
- class FcurveRemoveKeyframeInput(bpy.types.Operator):
 
-     """Remove a keyframe input from the fCurve node"""
 
-     bl_idname = "mantis.fcurve_node_remove_kf"
 
-     bl_label = "Remove Keyframe"
 
-     bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (hasattr(context, 'active_node') )
 
-     def execute(self, context):
 
-         n = context.node
 
-         n.inputs.remove(n.inputs[-1])
 
-         return {'FINISHED'}
 
- class DriverAddDriverVariableInput(bpy.types.Operator):
 
-     """Add a Driver Variable input to the Driver node"""
 
-     bl_idname = "mantis.driver_node_add_variable"
 
-     bl_label = "Add Driver Variable"
 
-     bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (hasattr(context, 'active_node') )
 
-     def execute(self, context):           # unicode for 'a'
 
-         i = len (context.node.inputs) - 2 + 96
 
-         context.node.inputs.new("DriverVariableSocket", chr(i))
 
-         return {'FINISHED'}
 
- class DriverRemoveDriverVariableInput(bpy.types.Operator):
 
-     """Remove a DriverVariable input from the active Driver node"""
 
-     bl_idname = "mantis.driver_node_remove_variable"
 
-     bl_label = "Remove Driver Variable"
 
-     bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (hasattr(context, 'active_node') )
 
-     def execute(self, context):
 
-         n = context.node
 
-         n.inputs.remove(n.inputs[-1])
 
-         return {'FINISHED'}
 
- class LinkArmatureAddTargetInput(bpy.types.Operator):
 
-     """Add a Driver Variable input to the Driver node"""
 
-     bl_idname = "mantis.link_armature_node_add_target"
 
-     bl_label = "Add Target"
 
-     bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return hasattr(context, 'node')
 
-     def execute(self, context):           # unicode for 'a'
 
-         num_targets = len( list(context.node.inputs)[6:])//2
 
-         context.node.inputs.new("xFormSocket", "Target."+str(num_targets).zfill(3))
 
-         context.node.inputs.new("FloatFactorSocket", "Weight."+str(num_targets).zfill(3))
 
-         return {'FINISHED'}
 
- class LinkArmatureRemoveTargetInput(bpy.types.Operator):
 
-     """Remove a DriverVariable input from the active Driver node"""
 
-     bl_idname = "mantis.link_armature_node_remove_target"
 
-     bl_label = "Remove Target"
 
-     bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return hasattr(context, 'node')
 
-     def execute(self, context):
 
-         n = context.node
 
-         n.inputs.remove(n.inputs[-1]); n.inputs.remove(n.inputs[-1])
 
-         return {'FINISHED'}
 
- class CollectionAddNewOutput(bpy.types.Operator):
 
-     """Add a new Collection output to the Driver node"""
 
-     bl_idname = "mantis.collection_add_new"
 
-     bl_label = "+ Child"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     collection_name : bpy.props.StringProperty(default='Collection')
 
-     tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     socket_invoked : bpy.props.StringProperty(options ={'HIDDEN'}) # set by caller
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return True #(hasattr(context, 'active_node') )
 
-     # DUPLICATED CODE HERE, DUMMY
 
-     def invoke(self, context, event):
 
-         self.tree_invoked = context.node.id_data.name
 
-         self.node_invoked = context.node.name
 
-         t = context.node.id_data
 
-         t.nodes.active = context.node
 
-         context.node.select = True
 
-         wm = context.window_manager
 
-         return wm.invoke_props_dialog(self)
 
-     def execute(self, context):
 
-         if not self.collection_name:
 
-             return {'CANCELLED'}
 
-         n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
 
-         # we need to know which socket it is called from...
 
-         s = None
 
-         for socket in n.outputs:
 
-             if socket.identifier == self.socket_invoked:
 
-                 s=socket; break
 
-         parent_path = ''
 
-         if s is not None and s.collection_path:
 
-                 parent_path = s.collection_path + '>'
 
-         outer_dict = n.read_declarations_from_json()
 
-         current_data = outer_dict
 
-         if parent_path:
 
-             for name_elem in parent_path.split('>'):
 
-                 if name_elem == '': continue # HACK around being a bad programmer
 
-                 current_data = current_data[name_elem]
 
-         current_data[self.collection_name] = {}
 
-         n.push_declarations_to_json(outer_dict)
 
-         n.update_interface()
 
-         return {'FINISHED'}
 
- # TODO: should this prune the children, too?
 
- class CollectionRemoveOutput(bpy.types.Operator):
 
-     """Remove a Collection output to the Driver node"""
 
-     bl_idname = "mantis.collection_remove"
 
-     bl_label = "X"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     socket_invoked : bpy.props.StringProperty(options ={'HIDDEN'}) # set by caller
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return True #(hasattr(context, 'active_node') )
 
-     # DUPLICATED CODE HERE, DUMMY
 
-     def invoke(self, context, event):
 
-         self.tree_invoked = context.node.id_data.name
 
-         self.node_invoked = context.node.name
 
-         t = context.node.id_data
 
-         t.nodes.active = context.node
 
-         context.node.select = True
 
-         return self.execute(context)
 
-     def execute(self, context):
 
-         n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
 
-         s = None
 
-         for socket in n.outputs:
 
-             if socket.identifier == self.socket_invoked:
 
-                 s=socket; break
 
-         if not s:
 
-             return {'CANCELLED'}
 
-         parent_path = ''
 
-         if s is not None and s.collection_path:
 
-                 parent_path = s.collection_path + '>'
 
-         outer_dict = n.read_declarations_from_json()
 
-         current_data = outer_dict
 
-         print(parent_path)
 
-         if parent_path:
 
-             for name_elem in parent_path.split('>')[:-2]: # just skip the last one
 
-                 print(name_elem)
 
-                 if name_elem == '': continue # HACK around being a bad programmer
 
-                 current_data = current_data[name_elem]
 
-         del current_data[s.name]
 
-         n.push_declarations_to_json(outer_dict)
 
-         n.update_interface()
 
-         return {'FINISHED'}
 
- def get_socket_enum(operator, context):
 
-     valid_types = []; i = -1
 
-     from .socket_definitions import TellClasses, MantisSocket
 
-     for cls in TellClasses():
 
-         if cls.is_valid_interface_type:
 
-             valid_types.append( (cls.bl_idname, cls.bl_label, "Socket Type", i := i + 1), )
 
-     return valid_types
 
- class B4_4_0_Workaround_NodeTree_Interface_Update(Operator):
 
-     """Selects all nodes of same type as active node."""
 
-     bl_idname = "mantis.node_tree_interface_update_4_4_0_workaround"
 
-     bl_label = "Add Socket to Node Tree"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     socket_name : bpy.props.StringProperty()
 
-     output : bpy.props.BoolProperty()
 
-     socket_type  : bpy.props.EnumProperty(
 
-         name="Socket Type",
 
-         description="Socket Type",
 
-         items=get_socket_enum,
 
-         default=0,)
 
-     tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (any_tree_poll(context))
 
-     def invoke(self, context, event):
 
-         self.tree_invoked = context.active_node.id_data.name
 
-         # we use active_node here ^ because we are comparing the active node to the selection.
 
-         wm = context.window_manager
 
-         return wm.invoke_props_dialog(self)
 
-     def execute(self, context):
 
-         tree = bpy.data.node_groups[self.tree_invoked]
 
-         in_out = 'OUTPUT' if self.output else 'INPUT'
 
-         tree.interface.new_socket(self.socket_name, in_out=in_out, socket_type=self.socket_type)
 
-         # try to prevent the next execution
 
-         # because updating the interface triggers a depsgraph update.
 
-         # this doesn't actually work though...TODO
 
-         if tree.bl_idname == "MantisTree":
 
-             tree.prevent_next_exec=True
 
-         return {"FINISHED"}
 
- class ConvertBezierCurveToNURBS(Operator):
 
-     """Converts all bezier splines of curve to NURBS."""
 
-     bl_idname = "mantis.convert_bezcrv_to_nurbs"
 
-     bl_label = "Convert Bezier Curve to NURBS"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return (context.active_object is not None) and (context.active_object.type=='CURVE')
 
-     def execute(self, context):
 
-         from .utilities import nurbs_copy_bez_spline
 
-         curve = context.active_object
 
-         bez=[]
 
-         for spl in curve.data.splines:
 
-             if spl.type=='BEZIER':
 
-                 bez.append(spl)
 
-         for bez_spline in bez:
 
-             new_spline=nurbs_copy_bez_spline(curve, bez_spline)
 
-             curve.data.splines.remove(bez_spline)
 
-         return {"FINISHED"}
 
- def get_component_library_items(self, context):
 
-     import json
 
-     from .preferences import get_bl_addon_object
 
-     bl_mantis_addon = get_bl_addon_object()
 
-     return_value = [('NONE', 'None', 'None', 'ERROR', 0)]
 
-     component_name_and_path={}
 
-     if bl_mantis_addon:
 
-         widgets_path = bl_mantis_addon.preferences.ComponentsLibraryFolder
 
-         import os
 
-         for path_root, dirs, files, in os.walk(widgets_path):
 
-             for file in files:
 
-                 if file.endswith('.rig'):
 
-                     filepath = os.path.join(path_root, file)
 
-                     with open(filepath, 'r', encoding='utf-8') as f:
 
-                         json_data = json.load(f)
 
-                         for tree_name in json_data.keys():
 
-                             component_name_and_path[tree_name] = os.path.join(path_root, file)
 
-     if component_name_and_path.keys():
 
-         return_value=[]
 
-         for i, (name, path) in enumerate(component_name_and_path.items()):
 
-             return_value.append( ((path+"|"+name).upper(), name, path + ': ' + name, 'NODE_TREE', i) )
 
-     return return_value
 
- class ImportFromComponentLibrary(Operator):
 
-     """Import a Mantis Component from your Component Library."""
 
-     bl_idname = "mantis.import_from_component_library"
 
-     bl_label = "Import From Component Library"
 
-     bl_options = {'REGISTER', 'UNDO'}
 
-     default_value  : bpy.props.EnumProperty(
 
-             items=get_component_library_items,
 
-             name="Component",
 
-             description="Which Component to Import",
 
-             default = 0,
 
-         )
 
-     @classmethod
 
-     def poll(cls, context):
 
-         return True # probably not best to do a True for this LOL
 
-     def execute(self, context):
 
-         return {"FINISHED"}
 
- # this has to be down here for some reason. what a pain
 
- classes = [
 
-         MantisGroupNodes,
 
-         MantisEditGroup,
 
-         MantisNewNodeTree,
 
-         InvalidateNodeTree,
 
-         ExecuteNodeTree,
 
-         # CreateMetaGroup,
 
-         QueryNodeSockets,
 
-         FoceUpdateGroup,
 
-         ForceDisplayUpdate,
 
-         CleanUpNodeGraph,
 
-         MantisMuteNode,
 
-         SelectNodesOfType,
 
-         ConnectNodeToInput,
 
-         # xForm
 
-         AddCustomProperty,
 
-         EditCustomProperty,
 
-         RemoveCustomProperty,
 
-         # EditFCurveNode,
 
-         FcurveAddKeyframeInput,
 
-         FcurveRemoveKeyframeInput,
 
-         # Driver
 
-         DriverAddDriverVariableInput,
 
-         DriverRemoveDriverVariableInput,
 
-         # Armature Link Node
 
-         LinkArmatureAddTargetInput,
 
-         LinkArmatureRemoveTargetInput,
 
-         # managing collections
 
-         CollectionAddNewOutput,
 
-         CollectionRemoveOutput,
 
-         # rigging utilities
 
-         ConvertBezierCurveToNURBS,
 
-         # component library
 
-         ImportFromComponentLibrary,
 
-         ]
 
- if (bpy.app.version >= (4, 4, 0)):
 
-     classes.append(B4_4_0_Workaround_NodeTree_Interface_Update)
 
- def TellClasses():
 
-     return classes
 
 
  |