ops_nodegroup.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  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. if do_profile:
  206. import pstats, io
  207. from pstats import SortKey
  208. with cProfile.Profile() as pr:
  209. tree.update_tree(context)
  210. tree.execute_tree(context, error_popups = True)
  211. # from the Python docs at https://docs.python.org/3/library/profile.html#module-cProfile
  212. s = io.StringIO()
  213. sortby = SortKey.TIME
  214. # sortby = SortKey.CUMULATIVE
  215. ps = pstats.Stats(pr, stream=s).strip_dirs().sort_stats(sortby)
  216. ps.print_stats(20) # print the top 20
  217. print(s.getvalue())
  218. else:
  219. tree.update_tree(context)
  220. tree.execute_tree(context, error_popups = True)
  221. prGreen("Finished executing tree in %f seconds" % (time() - start_time))
  222. return {"FINISHED"}
  223. class SelectNodesOfType(Operator):
  224. """Selects all nodes of same type as active node."""
  225. bl_idname = "mantis.select_nodes_of_type"
  226. bl_label = "Select Nodes of Same Type as Active"
  227. bl_options = {'REGISTER', 'UNDO'}
  228. @classmethod
  229. def poll(cls, context):
  230. return (any_tree_poll(context))
  231. def execute(self, context):
  232. active_node = context.active_node
  233. tree = active_node.id_data
  234. if not hasattr(active_node, "node_tree"):
  235. for node in tree.nodes:
  236. node.select = (active_node.bl_idname == node.bl_idname)
  237. else:
  238. for node in tree.nodes:
  239. node.select = (active_node.bl_idname == node.bl_idname) and (active_node.node_tree == node.node_tree)
  240. return {"FINISHED"}
  241. def get_parent_tree_interface_enum(operator, context):
  242. ret = []; i = -1
  243. tree = bpy.data.node_groups[operator.tree_invoked]
  244. for sock in tree.interface.items_tree:
  245. if sock.item_type == 'PANEL': continue
  246. if sock.in_out == "OUTPUT": continue
  247. ret.append( (sock.identifier, sock.name, "Socket from Node Group Input", i := i + 1), )
  248. return ret
  249. def get_node_inputs_enum(operator, context):
  250. ret = []; i = -1
  251. n = bpy.data.node_groups[operator.tree_invoked].nodes[operator.node_invoked]
  252. for inp in n.inputs:
  253. ret.append( (inp.identifier, inp.name, "Socket of node to connect to.", i := i + 1), )
  254. return ret
  255. class ConnectNodeToInput(Operator):
  256. """Connects a Node Group Input socket to specified socket of active node and all selected same-type nodes."""
  257. bl_idname = "mantis.connect_nodes_to_input"
  258. bl_label = "Connect Socket to Input for Selected Nodes"
  259. bl_options = {'REGISTER', 'UNDO'}
  260. group_output : bpy.props.EnumProperty(
  261. items=get_parent_tree_interface_enum,
  262. name="Node Group Input Socket",
  263. description="Select which socket from the Node Group Input to connect to this node",)
  264. node_input : bpy.props.EnumProperty(
  265. items=get_node_inputs_enum,
  266. name="Node Input Socket",
  267. description="Select which of this node's sockets to recieve the connection",)
  268. tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  269. node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  270. @classmethod
  271. def poll(cls, context):
  272. return (any_tree_poll(context))
  273. def invoke(self, context, event):
  274. self.tree_invoked = context.active_node.id_data.name
  275. self.node_invoked = context.active_node.name
  276. # we use active_node here ^ because we are comparing the active node to the selection.
  277. wm = context.window_manager
  278. return wm.invoke_props_dialog(self)
  279. def execute(self, context):
  280. t = bpy.data.node_groups[self.tree_invoked]
  281. if hasattr(t, "is_executing"): # for Mantis trees, but this function should just work anywhere.
  282. t.is_executing = True
  283. n = t.nodes[self.node_invoked]
  284. for node in t.nodes:
  285. if n.bl_idname == node.bl_idname and node.select:
  286. # the bl_idname is the same so they both have node_tree
  287. if hasattr(n, "node_tree") and n.node_tree != node.node_tree: continue
  288. # TODO: maybe I should try and find a nearby input node and reuse it
  289. # doing these identifier lookups again and again is slow, whatever. faster than doing it by hand
  290. for connect_to_me in node.inputs:
  291. if connect_to_me.identifier == self.node_input: break
  292. if connect_to_me.is_linked: connect_to_me = None
  293. if connect_to_me: # only make the node if the socket is there and free
  294. inp = t.nodes.new("NodeGroupInput")
  295. connect_me = None
  296. for s in inp.outputs:
  297. if s.identifier != self.group_output: s.hide = True
  298. else: connect_me = s
  299. inp.location = node.location
  300. inp.location.x-=200
  301. t.links.new(input=connect_me, output=connect_to_me)
  302. if hasattr(t, "is_executing"):
  303. t.is_executing = False
  304. return {"FINISHED"}
  305. class QueryNodeSockets(Operator):
  306. """Utility Operator for querying the data in a socket"""
  307. bl_idname = "mantis.query_sockets"
  308. bl_label = "Query Node Sockets"
  309. bl_options = {'REGISTER', 'UNDO'}
  310. @classmethod
  311. def poll(cls, context):
  312. return (mantis_tree_poll_op(context))
  313. def execute(self, context):
  314. active_node = context.active_node
  315. tree = active_node.id_data
  316. for node in tree.nodes:
  317. if not node.select: continue
  318. return {"FINISHED"}
  319. class ForceDisplayUpdate(Operator):
  320. """Utility Operator for querying the data in a socket"""
  321. bl_idname = "mantis.force_display_update"
  322. bl_label = "Force Mantis Display Update"
  323. bl_options = {'REGISTER', 'UNDO'}
  324. @classmethod
  325. def poll(cls, context):
  326. return (mantis_tree_poll_op(context))
  327. def execute(self, context):
  328. base_tree = bpy.context.space_data.path[0].node_tree
  329. base_tree.display_update(context)
  330. return {"FINISHED"}
  331. class CleanUpNodeGraph(bpy.types.Operator):
  332. """Clean Up Node Graph"""
  333. bl_idname = "mantis.nodes_cleanup"
  334. bl_label = "Clean Up Node Graph"
  335. bl_options = {'REGISTER', 'UNDO'}
  336. # num_iterations=bpy.props.IntProperty(default=8)
  337. @classmethod
  338. def poll(cls, context):
  339. return hasattr(context, 'active_node')
  340. def execute(self, context):
  341. base_tree=context.space_data.path[-1].node_tree
  342. from .utilities import SugiyamaGraph
  343. SugiyamaGraph(base_tree, 12)
  344. return {'FINISHED'}
  345. class MantisMuteNode(Operator):
  346. """Mantis Test Operator"""
  347. bl_idname = "mantis.mute_node"
  348. bl_label = "Mute Node"
  349. bl_options = {'REGISTER', 'UNDO'}
  350. @classmethod
  351. def poll(cls, context):
  352. return (mantis_tree_poll_op(context))
  353. def execute(self, context):
  354. path = context.space_data.path
  355. node = path[len(path)-1].node_tree.nodes.active
  356. node.mute = not node.mute
  357. # There should only be one of these
  358. if (enable := node.inputs.get("Enable")):
  359. # annoyingly, 'mute' and 'enable' are opposites
  360. enable.default_value = not node.mute
  361. if (hide := node.inputs.get("Hide")):
  362. hide.default_value = node.mute
  363. return {"FINISHED"}
  364. ePropertyType =(
  365. ('BOOL' , "Boolean", "Boolean", 0),
  366. ('INT' , "Integer", "Integer", 1),
  367. ('FLOAT' , "Float" , "Float" , 2),
  368. ('VECTOR', "Vector" , "Vector" , 3),
  369. ('STRING', "String" , "String" , 4),
  370. #('ENUM' , "Enum" , "Enum" , 5),
  371. )
  372. from .base_definitions import xFormNode
  373. class AddCustomProperty(bpy.types.Operator):
  374. """Add Custom Property to xForm Node"""
  375. bl_idname = "mantis.add_custom_property"
  376. bl_label = "Add Custom Property"
  377. bl_options = {'REGISTER', 'UNDO'}
  378. prop_type : bpy.props.EnumProperty(
  379. items=ePropertyType,
  380. name="New Property Type",
  381. description="Type of data for new Property",
  382. default = 'BOOL',)
  383. prop_name : bpy.props.StringProperty(default='Prop')
  384. min:bpy.props.FloatProperty(default = 0)
  385. max:bpy.props.FloatProperty(default = 1)
  386. soft_min:bpy.props.FloatProperty(default = 0)
  387. soft_max:bpy.props.FloatProperty(default = 1)
  388. description:bpy.props.StringProperty(default = "")
  389. tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  390. node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  391. @classmethod
  392. def poll(cls, context):
  393. return True #( hasattr(context, 'node') )
  394. def invoke(self, context, event):
  395. self.tree_invoked = context.node.id_data.name
  396. self.node_invoked = context.node.name
  397. wm = context.window_manager
  398. return wm.invoke_props_dialog(self)
  399. def execute(self, context):
  400. n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
  401. # For whatever reason, context.node doesn't exist anymore
  402. # (probably because I use a window to execute)
  403. # so as a sort of dumb workaround I am saving it to a hidden
  404. # property of the operator... it works but Blender complains.
  405. socktype = ''
  406. if not (self.prop_name):
  407. self.report({'ERROR_INVALID_INPUT'}, "Must name the property.")
  408. return {'CANCELLED'}
  409. if self.prop_type == 'BOOL':
  410. socktype = 'ParameterBoolSocket'
  411. if self.prop_type == 'INT':
  412. socktype = 'ParameterIntSocket'
  413. if self.prop_type == 'FLOAT':
  414. socktype = 'ParameterFloatSocket'
  415. if self.prop_type == 'VECTOR':
  416. socktype = 'ParameterVectorSocket'
  417. if self.prop_type == 'STRING':
  418. socktype = 'ParameterStringSocket'
  419. #if self.prop_type == 'ENUM':
  420. # sock_type = 'ParameterStringSocket'
  421. if (s := n.inputs.get(self.prop_name)):
  422. try:
  423. number = int(self.prop_name[-3:])
  424. # see if it has a number
  425. number+=1
  426. self.prop_name = self.prop_name[:-3] + str(number).zfill(3)
  427. except ValueError:
  428. self.prop_name+='.001'
  429. # WRONG # HACK # TODO # BUG #
  430. new_prop = n.inputs.new( socktype, self.prop_name)
  431. if self.prop_type in ['INT','FLOAT']:
  432. new_prop.min = self.min
  433. new_prop.max = self.max
  434. new_prop.soft_min = self.soft_min
  435. new_prop.soft_max = self.soft_max
  436. new_prop.description = self.description
  437. # now do the output
  438. n.outputs.new( socktype, self.prop_name)
  439. return {'FINISHED'}
  440. def main_get_existing_custom_properties(operator, context):
  441. ret = []; i = -1
  442. n = bpy.data.node_groups[operator.tree_invoked].nodes[operator.node_invoked]
  443. for inp in n.inputs:
  444. if 'Parameter' in inp.bl_idname:
  445. ret.append( (inp.identifier, inp.name, "Custom Property to Modify", i := i + 1), )
  446. return ret
  447. class EditCustomProperty(bpy.types.Operator):
  448. """Edit Custom Property"""
  449. bl_idname = "mantis.edit_custom_property"
  450. bl_label = "Edit Custom Property"
  451. bl_options = {'REGISTER', 'UNDO'}
  452. def get_existing_custom_properties(self, context):
  453. return main_get_existing_custom_properties(self, context)
  454. prop_edit : bpy.props.EnumProperty(
  455. items=get_existing_custom_properties,
  456. name="Property to Edit?",
  457. description="Select which property to edit",)
  458. prop_type : bpy.props.EnumProperty(
  459. items=ePropertyType,
  460. name="New Property Type",
  461. description="Type of data for new Property",
  462. default = 'BOOL',)
  463. prop_name : bpy.props.StringProperty(default='Prop')
  464. min:bpy.props.FloatProperty(default = 0)
  465. max:bpy.props.FloatProperty(default = 1)
  466. soft_min:bpy.props.FloatProperty(default = 0)
  467. soft_max:bpy.props.FloatProperty(default = 1)
  468. description:bpy.props.StringProperty(default = "") # TODO: use getters to fill these automatically
  469. tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  470. node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  471. @classmethod
  472. def poll(cls, context):
  473. return True #( hasattr(context, 'node') )
  474. def invoke(self, context, event):
  475. self.tree_invoked = context.node.id_data.name
  476. self.node_invoked = context.node.name
  477. wm = context.window_manager
  478. return wm.invoke_props_dialog(self)
  479. def execute(self, context):
  480. n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
  481. prop = n.inputs.get( self.prop_edit )
  482. if prop:
  483. prop.name = self.prop_name
  484. if (s := n.inputs.get(self.prop_edit)):
  485. if self.prop_type in ['INT','FLOAT']:
  486. prop.min = self.min
  487. prop.max = self.max
  488. prop.soft_min = self.soft_min
  489. prop.soft_max = self.soft_max
  490. prop.description = self.description
  491. return {'FINISHED'}
  492. else:
  493. self.report({'ERROR_INVALID_INPUT'}, "Cannot edit a property that does not exist.")
  494. class RemoveCustomProperty(bpy.types.Operator):
  495. """Remove a Custom Property from an xForm Node"""
  496. bl_idname = "mantis.remove_custom_property"
  497. bl_label = "Remove Custom Property"
  498. bl_options = {'REGISTER', 'UNDO'}
  499. def get_existing_custom_properties(self, context):
  500. return main_get_existing_custom_properties(self, context)
  501. prop_remove : bpy.props.EnumProperty(
  502. items=get_existing_custom_properties,
  503. name="Property to remove?",
  504. description="Select which property to remove",)
  505. tree_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  506. node_invoked : bpy.props.StringProperty(options ={'HIDDEN'})
  507. @classmethod
  508. def poll(cls, context):
  509. return True #(hasattr(context, 'active_node') )
  510. def invoke(self, context, event):
  511. self.tree_invoked = context.node.id_data.name
  512. self.node_invoked = context.node.name
  513. t = context.node.id_data
  514. # HACK the props dialog makes this necesary
  515. # because context.node only exists during the event that
  516. # was created by clicking on the node.
  517. t.nodes.active = context.node # HACK
  518. context.node.select = True # HACK
  519. # I need this bc of the callback for the enum property.
  520. # for whatever reason I can't use tree_invoked there
  521. wm = context.window_manager
  522. return wm.invoke_props_dialog(self)
  523. def execute(self, context):
  524. n = bpy.data.node_groups[self.tree_invoked].nodes[self.node_invoked]
  525. # For whatever reason, context.node doesn't exist anymore
  526. # (probably because I use a window to execute)
  527. # so as a sort of dumb workaround I am saving it to a hidden
  528. # property of the operator... it works.
  529. for i, inp in enumerate(n.inputs):
  530. if inp.identifier == self.prop_remove:
  531. break
  532. else:
  533. self.report({'ERROR'}, "Input not found")
  534. return {'CANCELLED'}
  535. # it's possible that the output property's identifier isn't the
  536. # exact same... but I don' care. Shouldn't ever happen. TODO
  537. for j, out in enumerate(n.outputs):
  538. if out.identifier == self.prop_remove:
  539. break
  540. else:
  541. self.report({'ERROR'}, "Output not found")
  542. raise RuntimeError("This should not happen!")
  543. n.inputs.remove ( n.inputs [i] )
  544. n.outputs.remove( n.outputs[j] )
  545. return {'FINISHED'}
  546. # SIMPLE node operators...
  547. # May rewrite these in a more generic way later
  548. class FcurveAddKeyframeInput(bpy.types.Operator):
  549. """Add a keyframe input to the fCurve node"""
  550. bl_idname = "mantis.fcurve_node_add_kf"
  551. bl_label = "Add Keyframe"
  552. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  553. @classmethod
  554. def poll(cls, context):
  555. return (hasattr(context, 'active_node') )
  556. def execute(self, context):
  557. num_keys = len( context.node.inputs)
  558. context.node.inputs.new("KeyframeSocket", "Keyframe."+str(num_keys).zfill(3))
  559. return {'FINISHED'}
  560. class FcurveRemoveKeyframeInput(bpy.types.Operator):
  561. """Remove a keyframe input from the fCurve node"""
  562. bl_idname = "mantis.fcurve_node_remove_kf"
  563. bl_label = "Remove Keyframe"
  564. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  565. @classmethod
  566. def poll(cls, context):
  567. return (hasattr(context, 'active_node') )
  568. def execute(self, context):
  569. n = context.node
  570. n.inputs.remove(n.inputs[-1])
  571. return {'FINISHED'}
  572. class DriverAddDriverVariableInput(bpy.types.Operator):
  573. """Add a Driver Variable input to the Driver node"""
  574. bl_idname = "mantis.driver_node_add_variable"
  575. bl_label = "Add Driver Variable"
  576. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  577. @classmethod
  578. def poll(cls, context):
  579. return (hasattr(context, 'active_node') )
  580. def execute(self, context): # unicode for 'a'
  581. i = len (context.node.inputs) - 2 + 96
  582. context.node.inputs.new("DriverVariableSocket", chr(i))
  583. return {'FINISHED'}
  584. class DriverRemoveDriverVariableInput(bpy.types.Operator):
  585. """Remove a DriverVariable input from the active Driver node"""
  586. bl_idname = "mantis.driver_node_remove_variable"
  587. bl_label = "Remove Driver Variable"
  588. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  589. @classmethod
  590. def poll(cls, context):
  591. return (hasattr(context, 'active_node') )
  592. def execute(self, context):
  593. n = context.node
  594. n.inputs.remove(n.inputs[-1])
  595. return {'FINISHED'}
  596. class LinkArmatureAddTargetInput(bpy.types.Operator):
  597. """Add a Driver Variable input to the Driver node"""
  598. bl_idname = "mantis.link_armature_node_add_target"
  599. bl_label = "Add Target"
  600. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  601. @classmethod
  602. def poll(cls, context):
  603. return hasattr(context, 'node')
  604. def execute(self, context): # unicode for 'a'
  605. num_targets = len( list(context.node.inputs)[6:])//2
  606. context.node.inputs.new("xFormSocket", "Target."+str(num_targets).zfill(3))
  607. context.node.inputs.new("FloatFactorSocket", "Weight."+str(num_targets).zfill(3))
  608. return {'FINISHED'}
  609. class LinkArmatureRemoveTargetInput(bpy.types.Operator):
  610. """Remove a DriverVariable input from the active Driver node"""
  611. bl_idname = "mantis.link_armature_node_remove_target"
  612. bl_label = "Remove Target"
  613. bl_options = {'INTERNAL', 'REGISTER', 'UNDO'}
  614. @classmethod
  615. def poll(cls, context):
  616. return hasattr(context, 'node')
  617. def execute(self, context):
  618. n = context.node
  619. n.inputs.remove(n.inputs[-1]); n.inputs.remove(n.inputs[-1])
  620. return {'FINISHED'}