ops_nodegroup.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  1. import bpy
  2. from bpy.types import Operator
  3. from mathutils import Vector
  4. def TellClasses():
  5. return [
  6. MantisGroupNodes,
  7. MantisEditGroup,
  8. ExecuteNodeTree,
  9. # CreateMetaGroup,
  10. QueryNodeSockets,
  11. CleanUpNodeGraph,
  12. MantisMuteNode,
  13. TestOperator,
  14. # xForm
  15. AddCustomProperty,
  16. RemoveCustomProperty,
  17. # Fcurve
  18. EditFCurveNode,
  19. FcurveAddKeyframeInput,
  20. FcurveRemoveKeyframeInput,
  21. # Driver
  22. DriverAddDriverVariableInput,
  23. DriverRemoveDriverVariableInput,
  24. # Armature Link Node
  25. LinkArmatureAddTargetInput,
  26. LinkArmatureRemoveTargetInput,]
  27. def mantis_tree_poll_op(context):
  28. # return True
  29. space = context.space_data
  30. if hasattr(space, "node_tree"):
  31. if (space.node_tree):
  32. return (space.tree_type == "MantisTree")
  33. return False
  34. def get_override(active=None, edit=False, selected=[], type='VIEW_3D'):
  35. #no clue what this does... taken from sorcar
  36. override = bpy.context.copy()
  37. if (type == 'VIEW_3D'):
  38. override["active_object"] = active
  39. if (edit):
  40. override["edit_object"] = active
  41. if (active not in selected):
  42. selected.append(active)
  43. override["selected_object"] = selected
  44. flag = False
  45. for window in bpy.data.window_managers[0].windows:
  46. for area in window.screen.areas:
  47. if area.type == type:
  48. override["area"] = area
  49. override["region"] = [i for i in area.regions if i.type == 'WINDOW'][0]
  50. flag = True
  51. break
  52. if (flag):
  53. break
  54. return override
  55. def ChooseNodeGroupNode(treetype):
  56. #I don't need this anymore... but I'm leaving it here
  57. # because this is a useful thing to separate
  58. # in case I add multiple tree types in the future
  59. if (treetype == "MantisTree"):
  60. return "MantisNodeGroup"
  61. # if (treetype == "LinkTree"):
  62. # return "linkGroupNode"
  63. #########################################################################3
  64. class MantisGroupNodes(Operator):
  65. """Create node-group from selected nodes"""
  66. bl_idname = "mantis.group_nodes"
  67. bl_label = "Group Nodes"
  68. @classmethod
  69. def poll(cls, context):
  70. return mantis_tree_poll_op(context)
  71. # source is https://github.com/aachman98/Sorcar/blob/master/operators/ScGroupNodes.py
  72. # checc here: https://github.com/nortikin/sverchok/blob/9002fd4af9ec8603e86f86ed7e567a4ed0d2e07c/core/node_group.py#L568
  73. def execute(self, context):
  74. # Get space, path, current nodetree, selected nodes and a newly created group
  75. space = context.space_data
  76. path = space.path
  77. node_tree = space.path[len(path)-1].node_tree
  78. node_group = bpy.data.node_groups.new(ChooseNodeGroupNode(space.tree_type), space.tree_type)
  79. selected_nodes = [i for i in node_tree.nodes if i.select]
  80. nodes_len = len(selected_nodes)
  81. # Store all links (internal/external) for the selected nodes to be created as group inputs/outputs
  82. links_external_in = []
  83. links_external_out = []
  84. for n in selected_nodes:
  85. for i in n.inputs:
  86. if (i.is_linked):
  87. l = i.links[0]
  88. if (not l.from_node in selected_nodes):
  89. if (not l in links_external_in):
  90. links_external_in.append(l)
  91. for o in n.outputs:
  92. if (o.is_linked):
  93. for l in o.links:
  94. if (not l.to_node in selected_nodes):
  95. if (not l in links_external_out):
  96. links_external_out.append(l)
  97. # Calculate the required locations for placement of grouped node and input/output nodes
  98. loc_x_in = 0
  99. loc_x_out = 0
  100. loc_avg = Vector((0, 0))
  101. for n in selected_nodes:
  102. loc_avg += n.location/nodes_len
  103. if (n.location[0] < loc_x_in):
  104. loc_x_in = n.location[0]
  105. if (n.location[0] > loc_x_out):
  106. loc_x_out = n.location[0]
  107. # Create and relocate group input & output nodes in the newly created group
  108. group_input = node_group.nodes.new("NodeGroupInput")
  109. group_output = node_group.nodes.new("NodeGroupOutput")
  110. group_input.location = Vector((loc_x_in-200, loc_avg[1]))
  111. group_output.location = Vector((loc_x_out+200, loc_avg[1]))
  112. # Copy the selected nodes from current nodetree
  113. if (nodes_len > 0):
  114. bpy.ops.node.clipboard_copy(get_override(type='NODE_EDITOR'))
  115. # Create a grouped node with correct location and assign newly created group
  116. group_node = node_tree.nodes.new(ChooseNodeGroupNode(space.tree_type))
  117. node_tree.nodes.active = group_node
  118. group_node.location = loc_avg
  119. group_node.node_tree = node_group
  120. # Add overlay to node editor for the newly created group
  121. path.append(node_group, node=group_node)
  122. # Paste the copied nodes to newly created group
  123. if (nodes_len > 0):
  124. bpy.ops.node.clipboard_paste(get_override(type='NODE_EDITOR'))
  125. # Create group input/output links in the newly created group
  126. o = group_input.outputs
  127. for link in links_external_in:
  128. # node_group.links.new(o.get(link.from_socket.name, o[len(o)-1]), node_group.nodes[link.to_node.name].inputs[link.to_socket.name])
  129. node_group.links.new(group_input.outputs[''], node_group.nodes[link.to_node.name].inputs[link.to_socket.name])
  130. i = group_output.inputs
  131. for link in links_external_out:
  132. # node_group.links.new(node_group.nodes[link.from_node.name].outputs[link.from_socket.name], i.get(link.to_socket.name, i[len(i)-1]))
  133. node_group.links.new(node_group.nodes[link.from_node.name].outputs[link.from_socket.name], group_output.inputs[''])
  134. # Add new links to grouped node from original external links
  135. for i in range(0, len(links_external_in)):
  136. link = links_external_in[i]
  137. node_tree.links.new(link.from_node.outputs[link.from_socket.name], group_node.inputs[i])
  138. for i in range(0, len(links_external_out)):
  139. link = links_external_out[i]
  140. node_tree.links.new(group_node.outputs[i], link.to_node.inputs[link.to_socket.name])
  141. # Remove redundant selected nodes
  142. for n in selected_nodes:
  143. node_tree.nodes.remove(n)
  144. return {"FINISHED"}
  145. class MantisEditGroup(Operator):
  146. """Edit the group referenced by the active node (or exit the current node-group)"""
  147. bl_idname = "mantis.edit_group"
  148. bl_label = "Edit Group"
  149. @classmethod
  150. def poll(cls, context):
  151. return (
  152. mantis_tree_poll_op(context)
  153. )
  154. def execute(self, context):
  155. space = context.space_data
  156. path = space.path
  157. node = path[len(path)-1].node_tree.nodes.active
  158. if hasattr(node, "node_tree"):
  159. if (node.node_tree):
  160. path.append(node.node_tree, node=node)
  161. path[0].node_tree.display_update(context)
  162. return {"FINISHED"}
  163. elif len(path) > 1:
  164. path.pop()
  165. path[0].node_tree.display_update(context)
  166. return {"CANCELLED"}
  167. class ExecuteNodeTree(Operator):
  168. """Execute this node tree"""
  169. bl_idname = "mantis.execute_node_tree"
  170. bl_label = "Execute Node Tree"
  171. @classmethod
  172. def poll(cls, context):
  173. return (mantis_tree_poll_op(context))
  174. def execute(self, context):
  175. from .utilities import parse_node_tree, print_lines
  176. from time import time
  177. from .utilities import wrapGreen
  178. tree=context.space_data.path[0].node_tree
  179. import cProfile
  180. from os import environ
  181. do_profile=False
  182. print (environ.get("DOPROFILE"))
  183. if environ.get("DOPROFILE"):
  184. do_profile=True
  185. if do_profile:
  186. cProfile.runctx("tree.update_tree(context)", None, locals())
  187. cProfile.runctx("tree.execute_tree(context)", None, locals())
  188. else:
  189. tree.update_tree(context)
  190. tree.execute_tree(context)
  191. return {"FINISHED"}
  192. # class CreateMetaGroup(Operator):
  193. # """Create Meta Rig group node"""
  194. # bl_idname = "mantis.create_meta_group"
  195. # bl_label = "Create Meta Rig group node"
  196. # @classmethod
  197. # def poll(cls, context):
  198. # return (mantis_tree_poll_op(context))
  199. # def execute(self, context):
  200. # space = context.space_data
  201. # path = space.path
  202. # node_tree = space.path[len(path)-1].node_tree
  203. # # selected_nodes = [i for i in node_tree.nodes if i.select]
  204. # ob = bpy.context.active_object
  205. # matrices_build = []
  206. # if (ob):
  207. # if (ob.type == 'ARMATURE'):
  208. # for pb in ob.pose.bones:
  209. # matrices_build.append((pb.name, pb.matrix, pb.length))
  210. # xloc = -400
  211. # yloc = 400
  212. # loops = 0
  213. # node_group = bpy.data.node_groups.new(ob.name, space.tree_type)
  214. # group_node = node_tree.nodes.new("MantisNodeGroup")
  215. # group_output = node_group.nodes.new("NodeGroupOutput")
  216. # path.append(node_group, node=group_node)
  217. # group_node.node_tree = node_group
  218. # gTree = group_node.node_tree
  219. # for name, m, length in matrices_build:
  220. # n = gTree.nodes.new("MetaRigMatrixNode")
  221. # n.first_row = m[0]
  222. # n.second_row = m[1]
  223. # n.third_row = m[2]
  224. # n.fourth_row = [m[3][0], m[3][1], m[3][2], length]
  225. # print (n.fourth_row[3])
  226. # n.name = name
  227. # n.label = name
  228. # n.location = (xloc + loops*250, yloc)
  229. # if (yloc > -800):
  230. # yloc-=55
  231. # else:
  232. # loops+=1
  233. # yloc = 400
  234. # node_group.links.new(n.outputs["Matrix"], group_output.inputs[''])
  235. # node_group.outputs["Matrix"].name = name
  236. # return {"FINISHED"}
  237. class QueryNodeSockets(Operator):
  238. """Utility Operator for querying the data in a socket"""
  239. bl_idname = "mantis.query_sockets"
  240. bl_label = "Query Node Sockets"
  241. @classmethod
  242. def poll(cls, context):
  243. return (mantis_tree_poll_op(context))
  244. def execute(self, context):
  245. node = context.active_node
  246. print ("Node type: ", node.bl_idname)
  247. # This is useful. Todo: reimplement this eventually.
  248. return {"FINISHED"}
  249. class CleanUpNodeGraph(bpy.types.Operator):
  250. """Clean Up Node Graph"""
  251. bl_idname = "mantis.nodes_cleanup"
  252. bl_label = "Clean Up Node Graph"
  253. @classmethod
  254. def poll(cls, context):
  255. return hasattr(context, 'active_node')
  256. def execute(self, context):
  257. base_tree=context.space_data.path[-1].node_tree
  258. from mantis.grandalf.graphs import Vertex, Edge, Graph, graph_core
  259. class defaultview(object):
  260. w,h = 1,1
  261. xz = (0,0)
  262. verts = {}
  263. for n in base_tree.nodes:
  264. has_links=False
  265. for inp in n.inputs:
  266. if inp.is_linked:
  267. has_links=True
  268. break
  269. for out in n.outputs:
  270. if out.is_linked:
  271. has_links=True
  272. break
  273. if not has_links:
  274. continue
  275. v = Vertex(n.name)
  276. v.view = defaultview()
  277. v.view.xy = n.location
  278. v.view.h = n.height*3
  279. v.view.w = n.width*3
  280. verts[n.name] = v
  281. edges = []
  282. for link in base_tree.links:
  283. weight = 1 # maybe this is useful
  284. edges.append(Edge(verts[link.from_node.name], verts[link.to_node.name], weight) )
  285. graph = Graph(verts.values(), edges)
  286. from mantis.grandalf.layouts import SugiyamaLayout
  287. sug = SugiyamaLayout(graph.C[0]) # no idea what .C[0] is
  288. roots=[]
  289. for node in base_tree.nodes:
  290. has_links=False
  291. for inp in node.inputs:
  292. if inp.is_linked:
  293. has_links=True
  294. break
  295. for out in node.outputs:
  296. if out.is_linked:
  297. has_links=True
  298. break
  299. if not has_links:
  300. continue
  301. if len(node.inputs)==0:
  302. roots.append(verts[node.name])
  303. else:
  304. for inp in node.inputs:
  305. if inp.is_linked==True:
  306. break
  307. else:
  308. roots.append(verts[node.name])
  309. sug.init_all(roots=roots,)
  310. sug.draw(8)
  311. for v in graph.C[0].sV:
  312. for n in base_tree.nodes:
  313. if n.name == v.data:
  314. n.location.x = v.view.xy[1]
  315. n.location.y = v.view.xy[0]
  316. return {'FINISHED'}
  317. class MantisMuteNode(Operator):
  318. """Mantis Test Operator"""
  319. bl_idname = "mantis.mute_node"
  320. bl_label = "Mute Node"
  321. @classmethod
  322. def poll(cls, context):
  323. return (mantis_tree_poll_op(context))
  324. def execute(self, context):
  325. path = context.space_data.path
  326. node = path[len(path)-1].node_tree.nodes.active
  327. node.mute = not node.mute
  328. # There should only be one of these
  329. if (enable := node.inputs.get("Enable")):
  330. # annoyingly, 'mute' and 'enable' are opposites
  331. enable.default_value = not node.mute
  332. if (hide := node.inputs.get("Hide")):
  333. hide.default_value = node.mute
  334. return {"FINISHED"}
  335. class TestOperator(Operator):
  336. """Mantis Test Operator"""
  337. bl_idname = "mantis.test_operator"
  338. bl_label = "Mantis Test Operator"
  339. @classmethod
  340. def poll(cls, context):
  341. return (mantis_tree_poll_op(context))
  342. def execute(self, context):
  343. path = context.space_data.path
  344. node = path[len(path)-1].node_tree.nodes.active
  345. print("Inputs:")
  346. for sock in node.inputs:
  347. print(sock.identifier)
  348. print("Outputs:")
  349. for sock in node.outputs:
  350. print(sock.identifier)
  351. print ("\n")
  352. # if (not node):
  353. # return {"FINISHED"}
  354. # for out in node.outputs:
  355. # utilities.lines_from_socket(out)
  356. # import bpy
  357. # c = bpy.context
  358. # print (c.space_data.path)
  359. return {"FINISHED"}
  360. ePropertyType =(
  361. ('BOOL' , "Boolean", "Boolean", 0),
  362. ('INT' , "Integer", "Integer", 1),
  363. ('FLOAT' , "Float" , "Float" , 2),
  364. ('VECTOR', "Vector" , "Vector" , 3),
  365. ('STRING', "String" , "String" , 4),
  366. #('ENUM' , "Enum" , "Enum" , 5),
  367. )
  368. from .base_definitions import xFormNode
  369. class AddCustomProperty(bpy.types.Operator):
  370. """Add Custom Property to xForm Node"""
  371. bl_idname = "mantis.add_custom_property"
  372. bl_label = "Add Custom Property"
  373. prop_type : bpy.props.EnumProperty(
  374. items=ePropertyType,
  375. name="New Property Type",
  376. description="Type of data for new Property",
  377. default = 'BOOL',)
  378. prop_name : bpy.props.StringProperty(default='Prop')
  379. min:bpy.props.FloatProperty(default = 0)
  380. max:bpy.props.FloatProperty(default = 1)
  381. soft_min:bpy.props.FloatProperty(default = 0)
  382. soft_max:bpy.props.FloatProperty(default = 1)
  383. description:bpy.props.StringProperty(default = "")
  384. node_invoked : bpy.props.PointerProperty(type=bpy.types.Node,
  385. options ={'HIDDEN'}) # note this seems to affect all
  386. # subsequent properties
  387. @classmethod
  388. def poll(cls, context):
  389. return True #( hasattr(context, 'node') )
  390. def invoke(self, context, event):
  391. self.node_invoked = context.node
  392. wm = context.window_manager
  393. return wm.invoke_props_dialog(self)
  394. def execute(self, context):
  395. n = self.node_invoked
  396. # For whatever reason, context.node doesn't exist anymore
  397. # (probably because I use a window to execute)
  398. # so as a sort of dumb workaround I am saving it to a hidden
  399. # property of the operator... it works.
  400. socktype = ''
  401. if not (self.prop_name):
  402. self.report({'ERROR_INVALID_INPUT'}, "Must name the property.")
  403. return {'CANCELLED'}
  404. if self.prop_type == 'BOOL':
  405. socktype = 'ParameterBoolSocket'
  406. if self.prop_type == 'INT':
  407. socktype = 'ParameterIntSocket'
  408. if self.prop_type == 'FLOAT':
  409. socktype = 'ParameterFloatSocket'
  410. if self.prop_type == 'VECTOR':
  411. socktype = 'ParameterVectorSocket'
  412. if self.prop_type == 'STRING':
  413. socktype = 'ParameterStringSocket'
  414. #if self.prop_type == 'ENUM':
  415. # sock_type = 'ParameterStringSocket'
  416. if (s := n.inputs.get(self.prop_name)):
  417. try:
  418. number = int(self.prop_name[-3:])
  419. # see if it has a number
  420. number+=1
  421. self.prop_name = self.prop_name[:-3] + str(number).zfill(3)
  422. except ValueError:
  423. self.prop_name+='.001'
  424. # WRONG
  425. new_prop = n.inputs.new( socktype, self.prop_name)
  426. if self.prop_type in ['INT','FLOAT']:
  427. new_prop.min = self.min
  428. new_prop.max = self.max
  429. new_prop.soft_min = self.soft_min
  430. new_prop.soft_max = self.soft_max
  431. new_prop.description = self.description
  432. # now do the output
  433. n.outputs.new( socktype, self.prop_name)
  434. if (False):
  435. print (new_prop.is_property_set("default_value"))
  436. ui_data = new_prop.id_properties_ui("default_value")
  437. ui_data.update(
  438. description=new_prop.description,
  439. default=0,) # for now
  440. #if a number
  441. for num_type in ['Float', 'Int', 'Bool']:
  442. if num_type in new_prop.bl_idname:
  443. ui_data.update(
  444. min = new_prop.min,
  445. max = new_prop.max,
  446. soft_min = new_prop.soft_min,
  447. soft_max = new_prop.soft_max,)
  448. return {'FINISHED'}
  449. class RemoveCustomProperty(bpy.types.Operator):
  450. """Remove a Custom Property from an xForm Node"""
  451. bl_idname = "mantis.remove_custom_property"
  452. bl_label = "Remove Custom Property"
  453. def get_existing_custom_properties(self, context):
  454. ret = []; i = -1
  455. n = context.active_node
  456. for inp in n.inputs:
  457. if 'Parameter' in inp.bl_idname:
  458. ret.append( (inp.identifier, inp.name, "Parameter to remove", i := i + 1), )
  459. if ret:
  460. return ret
  461. return None
  462. prop_remove : bpy.props.EnumProperty(
  463. items=get_existing_custom_properties,
  464. name="Property to remove?",
  465. description="Select which property to remove",)
  466. node_invoked : bpy.props.PointerProperty(type=bpy.types.Node,
  467. options ={'HIDDEN'}) # note this seems to affect all
  468. # subsequent properties
  469. @classmethod
  470. def poll(cls, context):
  471. return True #(hasattr(context, 'active_node') )
  472. def invoke(self, context, event):
  473. print (context.node)
  474. self.node_invoked = context.node
  475. t = context.node.id_data
  476. # HACK the props dialog makes this necesary
  477. # because context.node only exists during the event that
  478. # was created by clicking on the node.
  479. t.nodes.active = context.node # HACK
  480. context.node.select = True # HACK
  481. # I need this bc of the callback for the enum property.
  482. # for whatever reason I can't use node_invoked there
  483. wm = context.window_manager
  484. return wm.invoke_props_dialog(self)
  485. def execute(self, context):
  486. n = self.node_invoked
  487. # For whatever reason, context.node doesn't exist anymore
  488. # (probably because I use a window to execute)
  489. # so as a sort of dumb workaround I am saving it to a hidden
  490. # property of the operator... it works.
  491. for i, inp in enumerate(n.inputs):
  492. if inp.identifier == self.prop_remove:
  493. break
  494. else:
  495. self.report({'ERROR'}, "Input not found")
  496. raise RuntimeError("This should not happen!")
  497. # it's possible that the output property's identifier isn't the
  498. # exact same... but I don' care. Shouldn't ever happen. TODO
  499. for j, out in enumerate(n.outputs):
  500. if out.identifier == self.prop_remove:
  501. break
  502. else:
  503. self.report({'ERROR'}, "Output not found")
  504. raise RuntimeError("This should not happen!")
  505. n.inputs.remove ( n.inputs [i] )
  506. n.outputs.remove( n.outputs[j] )
  507. return {'FINISHED'}
  508. # TODO: not a priority
  509. # This one will remove the old socket and add a new one
  510. # and it'll put it back in place and reconnect the links
  511. # It's OK to just ask the user to do this manually for now
  512. #
  513. # class EditCustomProperty(bpy.types.Operator):
  514. # """Edit Custom Property in xForm Node"""
  515. # bl_idname = "mantis.edit_custom_property"
  516. # bl_label = "Edit Custom Property"
  517. # prop_type : bpy.props.EnumProperty(
  518. # items=ePropertyType,
  519. # name="New Property Type",
  520. # description="Type of data for new Property",
  521. # default = 'BOOL',)
  522. # prop_name : bpy.props.StringProperty(default='Prop')
  523. # min:bpy.props.FloatProperty(default = 0)
  524. # max:bpy.props.FloatProperty(default = 1)
  525. # soft_min:bpy.props.FloatProperty(default = 0)
  526. # soft_max:bpy.props.FloatProperty(default = 1)
  527. # description:bpy.props.StringProperty(default = "")
  528. # node_invoked : bpy.props.PointerProperty(type=bpy.types.Node,
  529. # options ={'HIDDEN'}) # note this seems to affect all
  530. # # subsequent properties
  531. # @classmethod
  532. # def poll(cls, context):
  533. # return True #( hasattr(context, 'node') )
  534. # def invoke(self, context, event):
  535. # print (context.node)
  536. # self.node_invoked = context.node
  537. # print(dir(self))
  538. # wm = context.window_manager
  539. # return wm.invoke_props_dialog(self)
  540. # def execute(self, context):
  541. # n = self.node_invoked
  542. # # For whatever reason, context.node doesn't exist anymore
  543. # # (probably because I use a window to execute)
  544. # # so as a sort of dumb workaround I am saving it to a hidden
  545. # # property of the operator... it works.
  546. # socktype = ''
  547. # if not (self.prop_name):
  548. # self.report({'ERROR_INVALID_INPUT'}, "Must name the property.")
  549. # return {'CANCELLED'}
  550. # if self.prop_type == 'BOOL':
  551. # socktype = 'ParameterBoolSocket'
  552. # if self.prop_type == 'INT':
  553. # socktype = 'ParameterIntSocket'
  554. # if self.prop_type == 'FLOAT':
  555. # socktype = 'ParameterFloatSocket'
  556. # if self.prop_type == 'VECTOR':
  557. # socktype = 'ParameterVectorSocket'
  558. # if self.prop_type == 'STRING':
  559. # socktype = 'ParameterStringSocket'
  560. # #if self.prop_type == 'ENUM':
  561. # # sock_type = 'ParameterStringSocket'
  562. # if (s := n.inputs.get(self.prop_name)):
  563. # try:
  564. # number = int(self.prop_name[-3:])
  565. # # see if it has a number
  566. # number+=1
  567. # self.prop_name = self.prop_name[:-3] + str(number).zfill(3)
  568. # except ValueError:
  569. # self.prop_name+='.001'
  570. # new_prop = n.inputs.new( socktype, self.prop_name)
  571. # if self.prop_type in ['INT','FLOAT']:
  572. # new_prop.min = self.min
  573. # new_prop.max = self.max
  574. # new_prop.soft_min = self.soft_min
  575. # new_prop.soft_max = self.soft_max
  576. # new_prop.description = self.description
  577. # return {'FINISHED'}
  578. class EditFCurveNode(bpy.types.Operator):
  579. """Edit the fCurve owned by fCurve node"""
  580. bl_idname = "mantis.edit_fcurve_node"
  581. bl_label = "Edit fCurve"
  582. bl_options = {'INTERNAL'}
  583. my_window : bpy.props.StringProperty(default = "-1")
  584. node_invoked : bpy.props.PointerProperty(type=bpy.types.Node,
  585. options ={'HIDDEN'}) # note this seems to affect all
  586. # subsequent properties
  587. fake_fcurve_ob: bpy.props.PointerProperty(
  588. type=bpy.types.Object,
  589. options ={'HIDDEN'},)
  590. prev_active: bpy.props.PointerProperty(
  591. type=bpy.types.Object,
  592. options ={'HIDDEN'},)
  593. @classmethod
  594. def poll(cls, context):
  595. return True #(hasattr(context, 'active_node') )
  596. def modal(self, context, event):
  597. for w in context.window_manager.windows:
  598. if str(w.as_pointer()) == self.my_window:
  599. break
  600. else:
  601. context.scene.collection.objects.unlink( self.fake_fcurve_ob )
  602. context.view_layer.objects.active = self.prev_active
  603. self.prev_active.select_set(True)
  604. # at this point I will push the fcurve to nodes
  605. # or some kind of internal data
  606. return {'FINISHED'}
  607. # I can't currently think of anything I need to do with w
  608. return {'PASS_THROUGH'}
  609. def invoke(self, context, event):
  610. self.node_invoked = context.node
  611. self.fake_fcurve_ob = self.node_invoked.fake_fcurve_ob
  612. context.scene.collection.objects.link( self.fake_fcurve_ob )
  613. self.prev_active = context.view_layer.objects.active
  614. context.view_layer.objects.active = self.fake_fcurve_ob
  615. self.fake_fcurve_ob.select_set(True)
  616. context.window_manager.modal_handler_add(self)
  617. # this is added to the active window.
  618. if (self.my_window == "-1"):
  619. prev_windows = set()
  620. for w in context.window_manager.windows:
  621. prev_windows.add(w.as_pointer())
  622. bpy.ops.wm.window_new()
  623. for w in context.window_manager.windows:
  624. w_int = w.as_pointer()
  625. if (w_int not in prev_windows):
  626. self.my_window = str(w_int)
  627. break
  628. else:
  629. print ("cancelled")
  630. return {'CANCELLED'}
  631. # set up properties for w
  632. # w.height = 256 # READ
  633. # w.width = 400 # ONLY
  634. w.screen.areas[0].type = 'GRAPH_EDITOR'
  635. w.screen.areas[0].spaces[0].auto_snap = 'NONE'
  636. return {'RUNNING_MODAL'}
  637. # SIMPLE node operators...
  638. # May rewrite these in a more generic way later
  639. class FcurveAddKeyframeInput(bpy.types.Operator):
  640. """Add a keyframe input to the fCurve node"""
  641. bl_idname = "mantis.fcurve_node_add_kf"
  642. bl_label = "Add Keyframe"
  643. bl_options = {'INTERNAL'}
  644. @classmethod
  645. def poll(cls, context):
  646. return (hasattr(context, 'active_node') )
  647. def execute(self, context):
  648. context.node.inputs.new("KeyframeSocket", "Keyframe")
  649. return {'FINISHED'}
  650. class FcurveRemoveKeyframeInput(bpy.types.Operator):
  651. """Remove a keyframe input from the fCurve node"""
  652. bl_idname = "mantis.fcurve_node_remove_kf"
  653. bl_label = "Remove Keyframe"
  654. bl_options = {'INTERNAL'}
  655. @classmethod
  656. def poll(cls, context):
  657. return (hasattr(context, 'active_node') )
  658. def execute(self, context):
  659. n = context.node
  660. n.inputs.remove(n.inputs[-1])
  661. return {'FINISHED'}
  662. class DriverAddDriverVariableInput(bpy.types.Operator):
  663. """Add a Driver Variable input to the Driver node"""
  664. bl_idname = "mantis.driver_node_add_variable"
  665. bl_label = "Add Driver Variable"
  666. bl_options = {'INTERNAL'}
  667. @classmethod
  668. def poll(cls, context):
  669. return (hasattr(context, 'active_node') )
  670. def execute(self, context): # unicode for 'a'
  671. i = len (context.node.inputs) - 2 + 96
  672. context.node.inputs.new("DriverVariableSocket", chr(i))
  673. return {'FINISHED'}
  674. class DriverRemoveDriverVariableInput(bpy.types.Operator):
  675. """Remove a DriverVariable input from the active Driver node"""
  676. bl_idname = "mantis.driver_node_remove_variable"
  677. bl_label = "Remove Driver Variable"
  678. bl_options = {'INTERNAL'}
  679. @classmethod
  680. def poll(cls, context):
  681. return (hasattr(context, 'active_node') )
  682. def execute(self, context):
  683. n = context.node
  684. n.inputs.remove(n.inputs[-1])
  685. return {'FINISHED'}
  686. class LinkArmatureAddTargetInput(bpy.types.Operator):
  687. """Add a Driver Variable input to the Driver node"""
  688. bl_idname = "mantis.link_armature_node_add_target"
  689. bl_label = "Add Target"
  690. bl_options = {'INTERNAL'}
  691. @classmethod
  692. def poll(cls, context):
  693. return hasattr(context, 'node')
  694. def execute(self, context): # unicode for 'a'
  695. num_targets = len( list(context.node.inputs)[6:])//2
  696. context.node.inputs.new("xFormSocket", "Target."+str(num_targets).zfill(3))
  697. context.node.inputs.new("FloatSocket", "Weight."+str(num_targets).zfill(3))
  698. return {'FINISHED'}
  699. class LinkArmatureRemoveTargetInput(bpy.types.Operator):
  700. """Remove a DriverVariable input from the active Driver node"""
  701. bl_idname = "mantis.link_armature_node_remove_target"
  702. bl_label = "Remove Target"
  703. bl_options = {'INTERNAL'}
  704. @classmethod
  705. def poll(cls, context):
  706. return hasattr(context, 'node')
  707. def execute(self, context):
  708. n = context.node
  709. n.inputs.remove(n.inputs[-1]); n.inputs.remove(n.inputs[-1])
  710. return {'FINISHED'}