| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062 |
- 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
- #########################################################################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, 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 s in n["sockets"].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 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)
-
- 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
- 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] )
- 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,
- 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
|