ops_nodegroup.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  1. import bpy
  2. from bpy.types import Operator
  3. from mathutils import Vector
  4. from .utilities import (prRed, prGreen, prPurple, prWhite,
  5. prOrange,
  6. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  7. wrapOrange,)
  8. def TellClasses():
  9. return [
  10. MantisGroupNodes,
  11. MantisEditGroup,
  12. ExecuteNodeTree,
  13. # CreateMetaGroup,
  14. QueryNodeSockets,
  15. ForceDisplayUpdate,
  16. CleanUpNodeGraph,
  17. MantisMuteNode,
  18. SelectNodesOfType,
  19. ConnectNodeToInput,
  20. # xForm
  21. AddCustomProperty,
  22. EditCustomProperty,
  23. RemoveCustomProperty,
  24. # EditFCurveNode,
  25. FcurveAddKeyframeInput,
  26. FcurveRemoveKeyframeInput,
  27. # Driver
  28. DriverAddDriverVariableInput,
  29. DriverRemoveDriverVariableInput,
  30. # Armature Link Node
  31. LinkArmatureAddTargetInput,
  32. LinkArmatureRemoveTargetInput,]
  33. # ExportNodeTreeToJSON,]
  34. def mantis_tree_poll_op(context):
  35. space = context.space_data
  36. if hasattr(space, "node_tree"):
  37. if (space.node_tree):
  38. return (space.tree_type in ["MantisTree", "SchemaTree"])
  39. return False
  40. def any_tree_poll(context):
  41. space = context.space_data
  42. if hasattr(space, "node_tree"):
  43. return True
  44. return False
  45. #########################################################################3
  46. class MantisGroupNodes(Operator):
  47. """Create node-group from selected nodes"""
  48. bl_idname = "mantis.group_nodes"
  49. bl_label = "Group Nodes"
  50. bl_options = {'REGISTER', 'UNDO'}
  51. @classmethod
  52. def poll(cls, context):
  53. return mantis_tree_poll_op(context)
  54. def execute(self, context):
  55. base_tree=context.space_data.path[-1].node_tree
  56. for path_item in context.space_data.path:
  57. path_item.node_tree.is_exporting = True
  58. from .i_o import export_to_json, do_import
  59. from random import random
  60. grp_name = "".join([chr(int(random()*30)+35) for i in range(20)])
  61. trees=[base_tree]
  62. selected_nodes=export_to_json(trees, write_file=False, only_selected=True)
  63. # this snippet of confusing indirection edits the name of the base tree in the JSON data
  64. selected_nodes[base_tree.name][0]["name"]=grp_name
  65. do_import(selected_nodes, context)
  66. affected_links_in = []
  67. affected_links_out = []
  68. for l in base_tree.links:
  69. if l.from_node.select and not l.to_node.select: affected_links_out.append(l)
  70. if not l.from_node.select and l.to_node.select: affected_links_in.append(l)
  71. delete_me = []
  72. all_nodes_bounding_box=[Vector((float("inf"),float("inf"))), Vector((-float("inf"),-float("inf")))]
  73. for n in base_tree.nodes:
  74. if n.select:
  75. node_loc = (0,0,0)
  76. if bpy.app.version <= (4, 4):
  77. node_loc = n.location
  78. parent = n.parent
  79. while (parent): # accumulate parent offset
  80. node_loc += parent.location
  81. parent = parent.parent
  82. else: # there is a new location_absolute property in 4.4
  83. node_loc = n.location_absolute
  84. if node_loc.x < all_nodes_bounding_box[0].x:
  85. all_nodes_bounding_box[0].x = node_loc.x
  86. if node_loc.y < all_nodes_bounding_box[0].y:
  87. all_nodes_bounding_box[0].y = node_loc.y
  88. #
  89. if node_loc.x > all_nodes_bounding_box[1].x:
  90. all_nodes_bounding_box[1].x = node_loc.x
  91. if node_loc.y > all_nodes_bounding_box[1].y:
  92. all_nodes_bounding_box[1].y = node_loc.y
  93. delete_me.append(n)
  94. grp_node = base_tree.nodes.new('MantisNodeGroup')
  95. grp_node.node_tree = bpy.data.node_groups[grp_name]
  96. bb_center = all_nodes_bounding_box[0].lerp(all_nodes_bounding_box[1],0.5)
  97. for n in grp_node.node_tree.nodes:
  98. n.location -= bb_center
  99. 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))
  100. from .base_definitions import node_group_update
  101. node_group_update(grp_node, force=True)
  102. # for each node in the JSON
  103. for n in selected_nodes[base_tree.name][2].values():
  104. for s in n["sockets"].values(): # for each socket in the node
  105. if source := s.get("source"):
  106. prGreen (s["name"], source[0], source[1])
  107. base_tree_node=base_tree.nodes.get(source[0])
  108. if s["is_output"]:
  109. for output in base_tree_node.outputs:
  110. if output.identifier == source[1]:
  111. break
  112. else:
  113. raise RuntimeError(wrapRed("Socket not found when grouping"))
  114. base_tree.links.new(input=output, output=grp_node.inputs[s["name"]])
  115. else:
  116. for s_input in base_tree_node.inputs:
  117. if s_input.identifier == source[1]:
  118. break
  119. else:
  120. raise RuntimeError(wrapRed("Socket not found when grouping"))
  121. base_tree.links.new(input=grp_node.outputs[s["name"]], output=s_input)
  122. for n in delete_me: base_tree.nodes.remove(n)
  123. base_tree.nodes.active = grp_node
  124. for path_item in context.space_data.path:
  125. path_item.node_tree.is_exporting = False
  126. grp_node.node_tree.name = "Group_Node.000"
  127. return {'FINISHED'}
  128. class MantisEditGroup(Operator):
  129. """Edit the group referenced by the active node (or exit the current node-group)"""
  130. bl_idname = "mantis.edit_group"
  131. bl_label = "Edit Group"
  132. bl_options = {'REGISTER', 'UNDO'}
  133. @classmethod
  134. def poll(cls, context):
  135. return (
  136. mantis_tree_poll_op(context)
  137. )
  138. def execute(self, context):
  139. space = context.space_data
  140. path = space.path
  141. node = path[len(path)-1].node_tree.nodes.active
  142. if hasattr(node, "node_tree"):
  143. if (node.node_tree):
  144. path.append(node.node_tree, node=node)
  145. path[0].node_tree.display_update(context)
  146. return {"FINISHED"}
  147. elif len(path) > 1:
  148. path.pop()
  149. # get the active node in the current path
  150. active = path[len(path)-1].node_tree.nodes.active
  151. from .base_definitions import node_group_update
  152. active.is_updating = True
  153. node_group_update(active, force = True)
  154. active.is_updating = False
  155. base_tree = path[0].node_tree
  156. base_tree.do_live_update = False
  157. # call update to force the node group to check if its tree has changed
  158. # now we need to loop through the tree and update all node groups of this type.
  159. from .utilities import get_all_nodes_of_type
  160. for g in get_all_nodes_of_type(base_tree, "MantisNodeGroup"):
  161. if g.node_tree == active.node_tree:
  162. g.is_updating = False
  163. node_group_update(g, force = True)
  164. g.is_updating = True
  165. path[0].node_tree.display_update(context)
  166. base_tree.do_live_update = True
  167. return {"FINISHED"}
  168. return {"CANCELLED"}
  169. class ExecuteNodeTree(Operator):
  170. """Execute this node tree"""
  171. bl_idname = "mantis.execute_node_tree"
  172. bl_label = "Execute Node Tree"
  173. bl_options = {'REGISTER', 'UNDO'}
  174. @classmethod
  175. def poll(cls, context):
  176. return (mantis_tree_poll_op(context))
  177. def execute(self, context):
  178. from time import time
  179. from .utilities import wrapGreen
  180. tree=context.space_data.path[0].node_tree
  181. import cProfile
  182. from os import environ
  183. start_time = time()
  184. do_profile=False
  185. print (environ.get("DOPROFILE"))
  186. if environ.get("DOPROFILE"):
  187. do_profile=True
  188. if do_profile:
  189. import pstats, io
  190. from pstats import SortKey
  191. with cProfile.Profile() as pr:
  192. tree.update_tree(context)
  193. tree.execute_tree(context)
  194. # from the Python docs at https://docs.python.org/3/library/profile.html#module-cProfile
  195. s = io.StringIO()
  196. sortby = SortKey.TIME
  197. # sortby = SortKey.CUMULATIVE
  198. ps = pstats.Stats(pr, stream=s).strip_dirs().sort_stats(sortby)
  199. ps.print_stats(20) # print the top 20
  200. print(s.getvalue())
  201. else:
  202. tree.update_tree(context)
  203. tree.execute_tree(context)
  204. prGreen("Finished executing tree in %f seconds" % (time() - start_time))
  205. return {"FINISHED"}
  206. class SelectNodesOfType(Operator):
  207. """Selects all nodes of same type as active node."""
  208. bl_idname = "mantis.select_nodes_of_type"
  209. bl_label = "Select Nodes of Same Type as Active"
  210. bl_options = {'REGISTER', 'UNDO'}
  211. @classmethod
  212. def poll(cls, context):
  213. return (any_tree_poll(context))
  214. def execute(self, context):
  215. active_node = context.active_node
  216. tree = active_node.id_data
  217. if not hasattr(active_node, "node_tree"):
  218. for node in tree.nodes:
  219. node.select = (active_node.bl_idname == node.bl_idname)
  220. else:
  221. for node in tree.nodes:
  222. node.select = (active_node.bl_idname == node.bl_idname) and (active_node.node_tree == node.node_tree)
  223. return {"FINISHED"}
  224. def get_parent_tree_interface_enum(operator, context):
  225. ret = []; i = -1
  226. tree = bpy.data.node_groups[operator.tree_invoked]
  227. for sock in tree.interface.items_tree:
  228. if sock.item_type == 'PANEL': continue
  229. if sock.in_out == "OUTPUT": continue
  230. ret.append( (sock.identifier, sock.name, "Socket from Node Group Input", i := i + 1), )
  231. return ret
  232. def get_node_inputs_enum(operator, context):
  233. ret = []; i = -1
  234. n = bpy.data.node_groups[operator.tree_invoked].nodes[operator.node_invoked]
  235. for inp in n.inputs:
  236. ret.append( (inp.identifier, inp.name, "Socket of node to connect to.", i := i + 1), )
  237. return ret
  238. class ConnectNodeToInput(Operator):
  239. """Connects a Node Group Input socket to specified socket of active node and all selected same-type nodes."""
  240. bl_idname = "mantis.connect_nodes_to_input"
  241. bl_label = "Connect Socket to Input for Selected Nodes"
  242. bl_options = {'REGISTER', 'UNDO'}
  243. group_output : bpy.props.EnumProperty(
  244. items=get_parent_tree_interface_enum,
  245. name="Node Group Input Socket",
  246. description="Select which socket from the Node Group Input to connect to this node",)
  247. node_input : bpy.props.EnumProperty(
  248. items=get_node_inputs_enum,
  249. name="Node Input Socket",
  250. description="Select which of this node's sockets to recieve the connection",)
  251. tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  252. node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  253. @classmethod
  254. def poll(cls, context):
  255. return (any_tree_poll(context))
  256. def invoke(self, context, event):
  257. self.tree_invoked = context.active_node.id_data.name
  258. self.node_invoked = context.active_node.name
  259. # we use active_node here ^ because we are comparing the active node to the selection.
  260. wm = context.window_manager
  261. return wm.invoke_props_dialog(self)
  262. def execute(self, context):
  263. t = bpy.data.node_groups[self.tree_invoked]
  264. if hasattr(t, "is_executing"): # for Mantis trees, but this function should just work anywhere.
  265. t.is_executing = True
  266. n = t.nodes[self.node_invoked]
  267. for node in t.nodes:
  268. if n.bl_idname == node.bl_idname and node.select:
  269. # the bl_idname is the same so they both have node_tree
  270. if hasattr(n, "node_tree") and n.node_tree != node.node_tree: continue
  271. # TODO: maybe I should try and find a nearby input node and reuse it
  272. # doing these identifier lookups again and again is slow, whatever. faster than doing it by hand
  273. for connect_to_me in node.inputs:
  274. if connect_to_me.identifier == self.node_input: break
  275. if connect_to_me.is_linked: connect_to_me = None
  276. if connect_to_me: # only make the node if the socket is there and free
  277. inp = t.nodes.new("NodeGroupInput")
  278. connect_me = None
  279. for s in inp.outputs:
  280. if s.identifier != self.group_output: s.hide = True
  281. else: connect_me = s
  282. inp.location = node.location
  283. inp.location.x-=200
  284. t.links.new(input=connect_me, output=connect_to_me)
  285. if hasattr(t, "is_executing"):
  286. t.is_executing = False
  287. return {"FINISHED"}
  288. class QueryNodeSockets(Operator):
  289. """Utility Operator for querying the data in a socket"""
  290. bl_idname = "mantis.query_sockets"
  291. bl_label = "Query Node Sockets"
  292. bl_options = {'REGISTER', 'UNDO'}
  293. @classmethod
  294. def poll(cls, context):
  295. return (mantis_tree_poll_op(context))
  296. def execute(self, context):
  297. active_node = context.active_node
  298. tree = active_node.id_data
  299. for node in tree.nodes:
  300. if not node.select: continue
  301. return {"FINISHED"}
  302. class ForceDisplayUpdate(Operator):
  303. """Utility Operator for querying the data in a socket"""
  304. bl_idname = "mantis.force_display_update"
  305. bl_label = "Force Mantis Display Update"
  306. bl_options = {'REGISTER', 'UNDO'}
  307. @classmethod
  308. def poll(cls, context):
  309. return (mantis_tree_poll_op(context))
  310. def execute(self, context):
  311. base_tree = bpy.context.space_data.path[0].node_tree
  312. base_tree.display_update(context)
  313. return {"FINISHED"}
  314. class CleanUpNodeGraph(bpy.types.Operator):
  315. """Clean Up Node Graph"""
  316. bl_idname = "mantis.nodes_cleanup"
  317. bl_label = "Clean Up Node Graph"
  318. bl_options = {'REGISTER', 'UNDO'}
  319. # num_iterations=bpy.props.IntProperty(default=8)
  320. @classmethod
  321. def poll(cls, context):
  322. return hasattr(context, 'active_node')
  323. def execute(self, context):
  324. base_tree=context.space_data.path[-1].node_tree
  325. from .utilities import SugiyamaGraph
  326. SugiyamaGraph(base_tree, 12)
  327. return {'FINISHED'}
  328. class MantisMuteNode(Operator):
  329. """Mantis Test Operator"""
  330. bl_idname = "mantis.mute_node"
  331. bl_label = "Mute Node"
  332. bl_options = {'REGISTER', 'UNDO'}
  333. @classmethod
  334. def poll(cls, context):
  335. return (mantis_tree_poll_op(context))
  336. def execute(self, context):
  337. path = context.space_data.path
  338. node = path[len(path)-1].node_tree.nodes.active
  339. node.mute = not node.mute
  340. # There should only be one of these
  341. if (enable := node.inputs.get("Enable")):
  342. # annoyingly, 'mute' and 'enable' are opposites
  343. enable.default_value = not node.mute
  344. if (hide := node.inputs.get("Hide")):
  345. hide.default_value = node.mute
  346. return {"FINISHED"}
  347. ePropertyType =(
  348. ('BOOL' , "Boolean", "Boolean", 0),
  349. ('INT' , "Integer", "Integer", 1),
  350. ('FLOAT' , "Float" , "Float" , 2),
  351. ('VECTOR', "Vector" , "Vector" , 3),
  352. ('STRING', "String" , "String" , 4),
  353. #('ENUM' , "Enum" , "Enum" , 5),
  354. )
  355. from .base_definitions import xFormNode
  356. class AddCustomProperty(bpy.types.Operator):
  357. """Add Custom Property to xForm Node"""
  358. bl_idname = "mantis.add_custom_property"
  359. bl_label = "Add Custom Property"
  360. bl_options = {'REGISTER', 'UNDO'}
  361. prop_type : bpy.props.EnumProperty(
  362. items=ePropertyType,
  363. name="New Property Type",
  364. description="Type of data for new Property",
  365. default = 'BOOL',)
  366. prop_name : bpy.props.StringProperty(default='Prop')
  367. min:bpy.props.FloatProperty(default = 0)
  368. max:bpy.props.FloatProperty(default = 1)
  369. soft_min:bpy.props.FloatProperty(default = 0)
  370. soft_max:bpy.props.FloatProperty(default = 1)
  371. description:bpy.props.StringProperty(default = "")
  372. tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  373. node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  374. @classmethod
  375. def poll(cls, context):
  376. return True #( hasattr(context, 'node') )
  377. def invoke(self, context, event):
  378. self.tree_invoked = context.node.id_data.name
  379. self.node_invoked = context.node.name
  380. wm = context.window_manager
  381. return wm.invoke_props_dialog(self)
  382. def execute(self, context):
  383. n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
  384. # For whatever reason, context.node doesn't exist anymore
  385. # (probably because I use a window to execute)
  386. # so as a sort of dumb workaround I am saving it to a hidden
  387. # property of the operator... it works but Blender complains.
  388. socktype = ''
  389. if not (self.prop_name):
  390. self.report({'ERROR_INVALID_INPUT'}, "Must name the property.")
  391. return {'CANCELLED'}
  392. if self.prop_type == 'BOOL':
  393. socktype = 'ParameterBoolSocket'
  394. if self.prop_type == 'INT':
  395. socktype = 'ParameterIntSocket'
  396. if self.prop_type == 'FLOAT':
  397. socktype = 'ParameterFloatSocket'
  398. if self.prop_type == 'VECTOR':
  399. socktype = 'ParameterVectorSocket'
  400. if self.prop_type == 'STRING':
  401. socktype = 'ParameterStringSocket'
  402. #if self.prop_type == 'ENUM':
  403. # sock_type = 'ParameterStringSocket'
  404. if (s := n.inputs.get(self.prop_name)):
  405. try:
  406. number = int(self.prop_name[-3:])
  407. # see if it has a number
  408. number+=1
  409. self.prop_name = self.prop_name[:-3] + str(number).zfill(3)
  410. except ValueError:
  411. self.prop_name+='.001'
  412. # WRONG # HACK # TODO # BUG #
  413. new_prop = n.inputs.new( socktype, self.prop_name)
  414. if self.prop_type in ['INT','FLOAT']:
  415. new_prop.min = self.min
  416. new_prop.max = self.max
  417. new_prop.soft_min = self.soft_min
  418. new_prop.soft_max = self.soft_max
  419. new_prop.description = self.description
  420. # now do the output
  421. n.outputs.new( socktype, self.prop_name)
  422. return {'FINISHED'}
  423. def main_get_existing_custom_properties(operator, context):
  424. ret = []; i = -1
  425. n = bpy.data.node_groups[operator.tree_invoked].nodes[operator.node_invoked]
  426. for inp in n.inputs:
  427. if 'Parameter' in inp.bl_idname:
  428. ret.append( (inp.identifier, inp.name, "Custom Property to Modify", i := i + 1), )
  429. return ret
  430. class EditCustomProperty(bpy.types.Operator):
  431. """Edit Custom Property"""
  432. bl_idname = "mantis.edit_custom_property"
  433. bl_label = "Edit Custom Property"
  434. bl_options = {'REGISTER', 'UNDO'}
  435. def get_existing_custom_properties(self, context):
  436. return main_get_existing_custom_properties(self, context)
  437. prop_edit : bpy.props.EnumProperty(
  438. items=get_existing_custom_properties,
  439. name="Property to Edit?",
  440. description="Select which property to edit",)
  441. prop_type : bpy.props.EnumProperty(
  442. items=ePropertyType,
  443. name="New Property Type",
  444. description="Type of data for new Property",
  445. default = 'BOOL',)
  446. prop_name : bpy.props.StringProperty(default='Prop')
  447. min:bpy.props.FloatProperty(default = 0)
  448. max:bpy.props.FloatProperty(default = 1)
  449. soft_min:bpy.props.FloatProperty(default = 0)
  450. soft_max:bpy.props.FloatProperty(default = 1)
  451. description:bpy.props.StringProperty(default = "") # TODO: use getters to fill these automatically
  452. tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  453. node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  454. @classmethod
  455. def poll(cls, context):
  456. return True #( hasattr(context, 'node') )
  457. def invoke(self, context, event):
  458. self.tree_invoked = context.node.id_data.name
  459. self.node_invoked = context.node.name
  460. wm = context.window_manager
  461. return wm.invoke_props_dialog(self)
  462. def execute(self, context):
  463. n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
  464. prop = n.inputs.get( self.prop_edit )
  465. if prop:
  466. prop.name = self.prop_name
  467. if (s := n.inputs.get(self.prop_edit)):
  468. if self.prop_type in ['INT','FLOAT']:
  469. prop.min = self.min
  470. prop.max = self.max
  471. prop.soft_min = self.soft_min
  472. prop.soft_max = self.soft_max
  473. prop.description = self.description
  474. return {'FINISHED'}
  475. else:
  476. self.report({'ERROR_INVALID_INPUT'}, "Cannot edit a property that does not exist.")
  477. class RemoveCustomProperty(bpy.types.Operator):
  478. """Remove a Custom Property from an xForm Node"""
  479. bl_idname = "mantis.remove_custom_property"
  480. bl_label = "Remove Custom Property"
  481. bl_options = {'REGISTER', 'UNDO'}
  482. def get_existing_custom_properties(self, context):
  483. return main_get_existing_custom_properties(self, context)
  484. prop_remove : bpy.props.EnumProperty(
  485. items=get_existing_custom_properties,
  486. name="Property to remove?",
  487. description="Select which property to remove",)
  488. tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  489. node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  490. @classmethod
  491. def poll(cls, context):
  492. return True #(hasattr(context, 'active_node') )
  493. def invoke(self, context, event):
  494. self.tree_invoked = context.node.id_data.name
  495. self.node_invoked = context.node.name
  496. t = context.node.id_data
  497. # HACK the props dialog makes this necesary
  498. # because context.node only exists during the event that
  499. # was created by clicking on the node.
  500. t.nodes.active = context.node # HACK
  501. context.node.select = True # HACK
  502. # I need this bc of the callback for the enum property.
  503. # for whatever reason I can't use tree_invoked there
  504. wm = context.window_manager
  505. return wm.invoke_props_dialog(self)
  506. def execute(self, context):
  507. n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
  508. # For whatever reason, context.node doesn't exist anymore
  509. # (probably because I use a window to execute)
  510. # so as a sort of dumb workaround I am saving it to a hidden
  511. # property of the operator... it works.
  512. for i, inp in enumerate(n.inputs):
  513. if inp.identifier == self.prop_remove:
  514. break
  515. else:
  516. self.report({'ERROR'}, "Input not found")
  517. return {'CANCELLED'}
  518. # it's possible that the output property's identifier isn't the
  519. # exact same... but I don' care. Shouldn't ever happen. TODO
  520. for j, out in enumerate(n.outputs):
  521. if out.identifier == self.prop_remove:
  522. break
  523. else:
  524. self.report({'ERROR'}, "Output not found")
  525. raise RuntimeError("This should not happen!")
  526. n.inputs.remove ( n.inputs [i] )
  527. n.outputs.remove( n.outputs[j] )
  528. return {'FINISHED'}
  529. # SIMPLE node operators...
  530. # May rewrite these in a more generic way later
  531. class FcurveAddKeyframeInput(bpy.types.Operator):
  532. """Add a keyframe input to the fCurve node"""
  533. bl_idname = "mantis.fcurve_node_add_kf"
  534. bl_label = "Add Keyframe"
  535. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  536. @classmethod
  537. def poll(cls, context):
  538. return (hasattr(context, 'active_node') )
  539. def execute(self, context):
  540. num_keys = len( context.node.inputs)
  541. context.node.inputs.new("KeyframeSocket", "Keyframe."+str(num_keys).zfill(3))
  542. return {'FINISHED'}
  543. class FcurveRemoveKeyframeInput(bpy.types.Operator):
  544. """Remove a keyframe input from the fCurve node"""
  545. bl_idname = "mantis.fcurve_node_remove_kf"
  546. bl_label = "Remove Keyframe"
  547. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  548. @classmethod
  549. def poll(cls, context):
  550. return (hasattr(context, 'active_node') )
  551. def execute(self, context):
  552. n = context.node
  553. n.inputs.remove(n.inputs[-1])
  554. return {'FINISHED'}
  555. class DriverAddDriverVariableInput(bpy.types.Operator):
  556. """Add a Driver Variable input to the Driver node"""
  557. bl_idname = "mantis.driver_node_add_variable"
  558. bl_label = "Add Driver Variable"
  559. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  560. @classmethod
  561. def poll(cls, context):
  562. return (hasattr(context, 'active_node') )
  563. def execute(self, context): # unicode for 'a'
  564. i = len (context.node.inputs) - 2 + 96
  565. context.node.inputs.new("DriverVariableSocket", chr(i))
  566. return {'FINISHED'}
  567. class DriverRemoveDriverVariableInput(bpy.types.Operator):
  568. """Remove a DriverVariable input from the active Driver node"""
  569. bl_idname = "mantis.driver_node_remove_variable"
  570. bl_label = "Remove Driver Variable"
  571. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  572. @classmethod
  573. def poll(cls, context):
  574. return (hasattr(context, 'active_node') )
  575. def execute(self, context):
  576. n = context.node
  577. n.inputs.remove(n.inputs[-1])
  578. return {'FINISHED'}
  579. class LinkArmatureAddTargetInput(bpy.types.Operator):
  580. """Add a Driver Variable input to the Driver node"""
  581. bl_idname = "mantis.link_armature_node_add_target"
  582. bl_label = "Add Target"
  583. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  584. @classmethod
  585. def poll(cls, context):
  586. return hasattr(context, 'node')
  587. def execute(self, context): # unicode for 'a'
  588. num_targets = len( list(context.node.inputs)[6:])//2
  589. context.node.inputs.new("xFormSocket", "Target."+str(num_targets).zfill(3))
  590. context.node.inputs.new("FloatFactorSocket", "Weight."+str(num_targets).zfill(3))
  591. return {'FINISHED'}
  592. class LinkArmatureRemoveTargetInput(bpy.types.Operator):
  593. """Remove a DriverVariable input from the active Driver node"""
  594. bl_idname = "mantis.link_armature_node_remove_target"
  595. bl_label = "Remove Target"
  596. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  597. @classmethod
  598. def poll(cls, context):
  599. return hasattr(context, 'node')
  600. def execute(self, context):
  601. n = context.node
  602. n.inputs.remove(n.inputs[-1]); n.inputs.remove(n.inputs[-1])
  603. return {'FINISHED'}