ops_nodegroup.py 28 KB

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