ops_nodegroup.py 31 KB

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