ops_nodegroup.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  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. MantisVisualizeOutput,
  19. TestOperator,
  20. # xForm
  21. AddCustomProperty,
  22. EditCustomProperty,
  23. RemoveCustomProperty,
  24. # Fcurve
  25. # EditFCurveNode,
  26. FcurveAddKeyframeInput,
  27. FcurveRemoveKeyframeInput,
  28. # Driver
  29. DriverAddDriverVariableInput,
  30. DriverRemoveDriverVariableInput,
  31. # Armature Link Node
  32. LinkArmatureAddTargetInput,
  33. LinkArmatureRemoveTargetInput,]
  34. # ExportNodeTreeToJSON,]
  35. def mantis_tree_poll_op(context):
  36. # return True
  37. space = context.space_data
  38. if hasattr(space, "node_tree"):
  39. if (space.node_tree):
  40. return (space.tree_type == "MantisTree")
  41. return False
  42. def get_override(active=None, edit=False, selected=[], type='VIEW_3D'):
  43. #no clue what this does... taken from sorcar
  44. override = bpy.context.copy()
  45. if (type == 'VIEW_3D'):
  46. override["active_object"] = active
  47. if (edit):
  48. override["edit_object"] = active
  49. if (active not in selected):
  50. selected.append(active)
  51. override["selected_object"] = selected
  52. flag = False
  53. for window in bpy.data.window_managers[0].windows:
  54. for area in window.screen.areas:
  55. if area.type == type:
  56. override["area"] = area
  57. override["region"] = [i for i in area.regions if i.type == 'WINDOW'][0]
  58. flag = True
  59. break
  60. if (flag):
  61. break
  62. return override
  63. def ChooseNodeGroupNode(treetype):
  64. #I don't need this anymore... but I'm leaving it here
  65. # because this is a useful thing to separate
  66. # in case I add multiple tree types in the future
  67. if (treetype == "MantisTree"):
  68. return "MantisNodeGroup"
  69. # if (treetype == "LinkTree"):
  70. # return "linkGroupNode"
  71. #########################################################################3
  72. class MantisGroupNodes(Operator):
  73. """Create node-group from selected nodes"""
  74. bl_idname = "mantis.group_nodes"
  75. bl_label = "Group Nodes"
  76. @classmethod
  77. def poll(cls, context):
  78. return mantis_tree_poll_op(context)
  79. def execute(self, context):
  80. base_tree=context.space_data.path[-1].node_tree
  81. base_tree.is_exporting = True
  82. from .i_o import export_to_json, do_import
  83. from random import random
  84. grp_name = "".join([chr(int(random()*30)+35) for i in range(20)])
  85. trees=[base_tree]
  86. selected_nodes=export_to_json(trees, write_file=False, only_selected=True)
  87. selected_nodes[base_tree.name][0]["name"]=grp_name
  88. # this is for debugging the result of the export
  89. # for k,v in selected_nodes[base_tree.name][2].items():
  90. # prPurple(k)
  91. # for k1, v1 in v["sockets"].items():
  92. # prRed(" ", k1, v1["name"])
  93. do_import(selected_nodes, context)
  94. affected_links_in = []
  95. affected_links_out = []
  96. for l in base_tree.links:
  97. if l.from_node.select and not l.to_node.select: affected_links_out.append(l)
  98. if not l.from_node.select and l.to_node.select: affected_links_in.append(l)
  99. delete_me = []
  100. all_nodes_bounding_box=[Vector((float("inf"),float("inf"))), Vector((-float("inf"),-float("inf")))]
  101. for n in base_tree.nodes:
  102. if n.select:
  103. if n.location.x < all_nodes_bounding_box[0].x:
  104. all_nodes_bounding_box[0].x = n.location.x
  105. if n.location.y < all_nodes_bounding_box[0].y:
  106. all_nodes_bounding_box[0].y = n.location.y
  107. #
  108. if n.location.x > all_nodes_bounding_box[1].x:
  109. all_nodes_bounding_box[1].x = n.location.x
  110. if n.location.y > all_nodes_bounding_box[1].y:
  111. all_nodes_bounding_box[1].y = n.location.y
  112. delete_me.append(n)
  113. grp_node = base_tree.nodes.new('MantisNodeGroup')
  114. grp_node.node_tree = bpy.data.node_groups[grp_name]
  115. bb_center = all_nodes_bounding_box[0].lerp(all_nodes_bounding_box[1],0.5)
  116. for n in grp_node.node_tree.nodes:
  117. n.location -= bb_center
  118. 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))
  119. # for l in selected_nodes[base_tree.name][3]:
  120. # if source := l.get("source"):
  121. # n_from = base_tree.nodes.get(source[0])
  122. # # s_from = n_from.
  123. for n in selected_nodes[base_tree.name][2].values():
  124. for s in n["sockets"].values():
  125. if source := s.get("source"):
  126. prGreen (s["name"], source[0], source[1])
  127. base_tree_node=base_tree.nodes.get(source[0])
  128. if s["is_output"]:
  129. for output in base_tree_node.outputs:
  130. if output.identifier == source[1]:
  131. break
  132. else:
  133. raise RuntimeError(wrapRed("Socket not found when grouping"))
  134. base_tree.links.new(input=output, output=grp_node.inputs[s["name"]])
  135. else:
  136. for s_input in base_tree_node.inputs:
  137. if s_input.identifier == source[1]:
  138. break
  139. else:
  140. raise RuntimeError(wrapRed("Socket not found when grouping"))
  141. base_tree.links.new(input=grp_node.outputs[s["name"]], output=s_input)
  142. for n in delete_me: base_tree.nodes.remove(n)
  143. base_tree.nodes.active = grp_node
  144. base_tree.is_exporting = False
  145. grp_node.node_tree.name = "Group_Node.000"
  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. # get the active node in the current path
  169. path[len(path)-1].node_tree.nodes.active.update() # call update to force the node group to check if its tree has changed
  170. return {"CANCELLED"}
  171. class ExecuteNodeTree(Operator):
  172. """Execute this node tree"""
  173. bl_idname = "mantis.execute_node_tree"
  174. bl_label = "Execute Node Tree"
  175. @classmethod
  176. def poll(cls, context):
  177. return (mantis_tree_poll_op(context))
  178. def execute(self, context):
  179. from time import time
  180. from .utilities import wrapGreen
  181. tree=context.space_data.path[0].node_tree
  182. import cProfile
  183. from os import environ
  184. start_time = time()
  185. do_profile=False
  186. print (environ.get("DOPROFILE"))
  187. if environ.get("DOPROFILE"):
  188. do_profile=True
  189. if do_profile:
  190. # cProfile.runctx("tree.update_tree(context)", None, locals())
  191. # cProfile.runctx("tree.execute_tree(context)", None, locals())
  192. # import hunter
  193. # hunter.trace(stdlib=False, action=hunter.CallPrinter(force_colors=False))
  194. # tree.update_tree(context)
  195. # tree.execute_tree(context)
  196. # return {"FINISHED"}
  197. import pstats, io
  198. from pstats import SortKey
  199. with cProfile.Profile() as pr:
  200. tree.update_tree(context)
  201. tree.execute_tree(context)
  202. # from the Python docs at https://docs.python.org/3/library/profile.html#module-cProfile
  203. s = io.StringIO()
  204. sortby = SortKey.TIME
  205. # sortby = SortKey.CUMULATIVE
  206. ps = pstats.Stats(pr, stream=s).strip_dirs().sort_stats(sortby)
  207. ps.print_stats(20) # print the top 20
  208. print(s.getvalue())
  209. else:
  210. tree.update_tree(context)
  211. tree.execute_tree(context)
  212. prGreen("Finished executing tree in %f seconds" % (time() - start_time))
  213. return {"FINISHED"}
  214. # class CreateMetaGroup(Operator):
  215. # """Create Meta Rig group node"""
  216. # bl_idname = "mantis.create_meta_group"
  217. # bl_label = "Create Meta Rig group node"
  218. # @classmethod
  219. # def poll(cls, context):
  220. # return (mantis_tree_poll_op(context))
  221. # def execute(self, context):
  222. # space = context.space_data
  223. # path = space.path
  224. # node_tree = space.path[len(path)-1].node_tree
  225. # # selected_nodes = [i for i in node_tree.nodes if i.select]
  226. # ob = bpy.context.active_object
  227. # matrices_build = []
  228. # if (ob):
  229. # if (ob.type == 'ARMATURE'):
  230. # for pb in ob.pose.bones:
  231. # matrices_build.append((pb.name, pb.matrix, pb.length))
  232. # xloc = -400
  233. # yloc = 400
  234. # loops = 0
  235. # node_group = bpy.data.node_groups.new(ob.name, space.tree_type)
  236. # group_node = node_tree.nodes.new("MantisNodeGroup")
  237. # group_output = node_group.nodes.new("NodeGroupOutput")
  238. # path.append(node_group, node=group_node)
  239. # group_node.node_tree = node_group
  240. # gTree = group_node.node_tree
  241. # for name, m, length in matrices_build:
  242. # n = gTree.nodes.new("MetaRigMatrixNode")
  243. # n.first_row = m[0]
  244. # n.second_row = m[1]
  245. # n.third_row = m[2]
  246. # n.fourth_row = [m[3][0], m[3][1], m[3][2], length]
  247. # print (n.fourth_row[3])
  248. # n.name = name
  249. # n.label = name
  250. # n.location = (xloc + loops*250, yloc)
  251. # if (yloc > -800):
  252. # yloc-=55
  253. # else:
  254. # loops+=1
  255. # yloc = 400
  256. # node_group.links.new(n.outputs["Matrix"], group_output.inputs[''])
  257. # node_group.outputs["Matrix"].name = name
  258. # return {"FINISHED"}
  259. class QueryNodeSockets(Operator):
  260. """Utility Operator for querying the data in a socket"""
  261. bl_idname = "mantis.query_sockets"
  262. bl_label = "Query Node Sockets"
  263. @classmethod
  264. def poll(cls, context):
  265. return (mantis_tree_poll_op(context))
  266. def execute(self, context):
  267. node = context.active_node
  268. print ("Node type: ", node.bl_idname)
  269. # This is useful. Todo: reimplement this eventually.
  270. return {"FINISHED"}
  271. class ForceDisplayUpdate(Operator):
  272. """Utility Operator for querying the data in a socket"""
  273. bl_idname = "mantis.force_display_update"
  274. bl_label = "Force Mantis Display Update"
  275. @classmethod
  276. def poll(cls, context):
  277. return (mantis_tree_poll_op(context))
  278. def execute(self, context):
  279. base_tree = bpy.context.space_data.path[0].node_tree
  280. base_tree.display_update(context)
  281. return {"FINISHED"}
  282. class CleanUpNodeGraph(bpy.types.Operator):
  283. """Clean Up Node Graph"""
  284. bl_idname = "mantis.nodes_cleanup"
  285. bl_label = "Clean Up Node Graph"
  286. bl_options = {'REGISTER', 'UNDO'}
  287. # num_iterations=bpy.props.IntProperty(default=8)
  288. @classmethod
  289. def poll(cls, context):
  290. return hasattr(context, 'active_node')
  291. def execute(self, context):
  292. base_tree=context.space_data.path[-1].node_tree
  293. from .utilities import SugiyamaGraph
  294. SugiyamaGraph(base_tree, 12)
  295. return {'FINISHED'}
  296. class MantisMuteNode(Operator):
  297. """Mantis Test Operator"""
  298. bl_idname = "mantis.mute_node"
  299. bl_label = "Mute Node"
  300. @classmethod
  301. def poll(cls, context):
  302. return (mantis_tree_poll_op(context))
  303. def execute(self, context):
  304. path = context.space_data.path
  305. node = path[len(path)-1].node_tree.nodes.active
  306. node.mute = not node.mute
  307. # There should only be one of these
  308. if (enable := node.inputs.get("Enable")):
  309. # annoyingly, 'mute' and 'enable' are opposites
  310. enable.default_value = not node.mute
  311. if (hide := node.inputs.get("Hide")):
  312. hide.default_value = node.mute
  313. return {"FINISHED"}
  314. class MantisVisualizeOutput(Operator):
  315. """Mantis Visualize Output Operator"""
  316. bl_idname = "mantis.visualize_output"
  317. bl_label = "Visualize Output"
  318. @classmethod
  319. def poll(cls, context):
  320. return (mantis_tree_poll_op(context))
  321. def execute(self, context):
  322. from time import time
  323. from .utilities import wrapGreen, prGreen
  324. tree=context.space_data.path[0].node_tree
  325. tree.update_tree(context)
  326. # tree.execute_tree(context)
  327. prGreen(f"Visualize Tree: {tree.name}")
  328. nodes = tree.parsed_tree
  329. from .readtree import visualize_tree
  330. visualize_tree(nodes, tree, context)
  331. return {"FINISHED"}
  332. class TestOperator(Operator):
  333. """Mantis Test Operator"""
  334. bl_idname = "mantis.test_operator"
  335. bl_label = "Mantis Test Operator"
  336. @classmethod
  337. def poll(cls, context):
  338. return (mantis_tree_poll_op(context))
  339. def execute(self, context):
  340. path = context.space_data.path
  341. base_tree = path[0].node_tree
  342. tree = path[len(path)-1].node_tree
  343. node = tree.nodes.active
  344. node.display_update(base_tree.parsed_tree, context)
  345. # from .base_definitions import get_signature_from_edited_tree
  346. # if nc := base_tree.parsed_tree.get(get_signature_from_edited_tree(node, context)):
  347. # from .utilities import get_all_dependencies
  348. # deps = get_all_dependencies(nc)
  349. # self.report({'INFO'}, f"Number of Node Dependencies: {len(deps)}")
  350. # # for n in deps:
  351. # # prGreen(n)
  352. # else:
  353. # # prRed("No NC found in parsed tree.")
  354. # self.report({'ERROR_INVALID_CONTEXT'}, "No data for node.")
  355. return {"FINISHED"}
  356. ePropertyType =(
  357. ('BOOL' , "Boolean", "Boolean", 0),
  358. ('INT' , "Integer", "Integer", 1),
  359. ('FLOAT' , "Float" , "Float" , 2),
  360. ('VECTOR', "Vector" , "Vector" , 3),
  361. ('STRING', "String" , "String" , 4),
  362. #('ENUM' , "Enum" , "Enum" , 5),
  363. )
  364. from .base_definitions import xFormNode
  365. class AddCustomProperty(bpy.types.Operator):
  366. """Add Custom Property to xForm Node"""
  367. bl_idname = "mantis.add_custom_property"
  368. bl_label = "Add Custom Property"
  369. prop_type : bpy.props.EnumProperty(
  370. items=ePropertyType,
  371. name="New Property Type",
  372. description="Type of data for new Property",
  373. default = 'BOOL',)
  374. prop_name : bpy.props.StringProperty(default='Prop')
  375. min:bpy.props.FloatProperty(default = 0)
  376. max:bpy.props.FloatProperty(default = 1)
  377. soft_min:bpy.props.FloatProperty(default = 0)
  378. soft_max:bpy.props.FloatProperty(default = 1)
  379. description:bpy.props.StringProperty(default = "")
  380. node_invoked : bpy.props.PointerProperty(type=bpy.types.Node,
  381. options ={'HIDDEN'}) # note this seems to affect all
  382. # subsequent properties
  383. @classmethod
  384. def poll(cls, context):
  385. return True #( hasattr(context, 'node') )
  386. def invoke(self, context, event):
  387. self.node_invoked = context.node
  388. wm = context.window_manager
  389. return wm.invoke_props_dialog(self)
  390. def execute(self, context):
  391. n = self.node_invoked
  392. # For whatever reason, context.node doesn't exist anymore
  393. # (probably because I use a window to execute)
  394. # so as a sort of dumb workaround I am saving it to a hidden
  395. # property of the operator... it works.
  396. socktype = ''
  397. if not (self.prop_name):
  398. self.report({'ERROR_INVALID_INPUT'}, "Must name the property.")
  399. return {'CANCELLED'}
  400. if self.prop_type == 'BOOL':
  401. socktype = 'ParameterBoolSocket'
  402. if self.prop_type == 'INT':
  403. socktype = 'ParameterIntSocket'
  404. if self.prop_type == 'FLOAT':
  405. socktype = 'ParameterFloatSocket'
  406. if self.prop_type == 'VECTOR':
  407. socktype = 'ParameterVectorSocket'
  408. if self.prop_type == 'STRING':
  409. socktype = 'ParameterStringSocket'
  410. #if self.prop_type == 'ENUM':
  411. # sock_type = 'ParameterStringSocket'
  412. if (s := n.inputs.get(self.prop_name)):
  413. try:
  414. number = int(self.prop_name[-3:])
  415. # see if it has a number
  416. number+=1
  417. self.prop_name = self.prop_name[:-3] + str(number).zfill(3)
  418. except ValueError:
  419. self.prop_name+='.001'
  420. # WRONG # HACK # TODO # BUG #
  421. new_prop = n.inputs.new( socktype, self.prop_name)
  422. if self.prop_type in ['INT','FLOAT']:
  423. new_prop.min = self.min
  424. new_prop.max = self.max
  425. new_prop.soft_min = self.soft_min
  426. new_prop.soft_max = self.soft_max
  427. new_prop.description = self.description
  428. # now do the output
  429. n.outputs.new( socktype, self.prop_name)
  430. return {'FINISHED'}
  431. #DOESN'T WORK YET
  432. class EditCustomProperty(bpy.types.Operator):
  433. """Edit Custom Property"""
  434. bl_idname = "mantis.edit_custom_property"
  435. bl_label = "Edit Custom Property"
  436. prop_type : bpy.props.EnumProperty(
  437. items=ePropertyType,
  438. name="New Property Type",
  439. description="Type of data for new Property",
  440. default = 'BOOL',)
  441. prop_name : bpy.props.StringProperty(default='Prop')
  442. min:bpy.props.FloatProperty(default = 0)
  443. max:bpy.props.FloatProperty(default = 1)
  444. soft_min:bpy.props.FloatProperty(default = 0)
  445. soft_max:bpy.props.FloatProperty(default = 1)
  446. description:bpy.props.StringProperty(default = "") # TODO: use getters to fill these automatically
  447. node_invoked : bpy.props.PointerProperty(type=bpy.types.Node,
  448. options ={'HIDDEN'}) # note this seems to affect all
  449. # subsequent properties
  450. @classmethod
  451. def poll(cls, context):
  452. return True #( hasattr(context, 'node') )
  453. def invoke(self, context, event):
  454. self.node_invoked = context.node
  455. wm = context.window_manager
  456. return wm.invoke_props_dialog(self)
  457. def execute(self, context):
  458. n = self.node_invoked
  459. prop = n.inputs.get( self.prop_name )
  460. if (s := n.inputs.get(self.prop_name)):
  461. if self.prop_type in ['INT','FLOAT']:
  462. new_prop.min = self.min
  463. new_prop.max = self.max
  464. new_prop.soft_min = self.soft_min
  465. new_prop.soft_max = self.soft_max
  466. new_prop.description = self.description
  467. return {'FINISHED'}
  468. class RemoveCustomProperty(bpy.types.Operator):
  469. """Remove a Custom Property from an xForm Node"""
  470. bl_idname = "mantis.remove_custom_property"
  471. bl_label = "Remove Custom Property"
  472. def get_existing_custom_properties(self, context):
  473. ret = []; i = -1
  474. n = context.active_node
  475. for inp in n.inputs:
  476. if 'Parameter' in inp.bl_idname:
  477. ret.append( (inp.identifier, inp.name, "Parameter to remove", i := i + 1), )
  478. if ret:
  479. return ret
  480. return None
  481. prop_remove : bpy.props.EnumProperty(
  482. items=get_existing_custom_properties,
  483. name="Property to remove?",
  484. description="Select which property to remove",)
  485. node_invoked : bpy.props.PointerProperty(type=bpy.types.Node,
  486. options ={'HIDDEN'}) # note this seems to affect all
  487. # subsequent properties
  488. @classmethod
  489. def poll(cls, context):
  490. return True #(hasattr(context, 'active_node') )
  491. def invoke(self, context, event):
  492. print (context.node)
  493. self.node_invoked = context.node
  494. t = context.node.id_data
  495. # HACK the props dialog makes this necesary
  496. # because context.node only exists during the event that
  497. # was created by clicking on the node.
  498. t.nodes.active = context.node # HACK
  499. context.node.select = True # HACK
  500. # I need this bc of the callback for the enum property.
  501. # for whatever reason I can't use node_invoked there
  502. wm = context.window_manager
  503. return wm.invoke_props_dialog(self)
  504. def execute(self, context):
  505. n = self.node_invoked
  506. # For whatever reason, context.node doesn't exist anymore
  507. # (probably because I use a window to execute)
  508. # so as a sort of dumb workaround I am saving it to a hidden
  509. # property of the operator... it works.
  510. for i, inp in enumerate(n.inputs):
  511. if inp.identifier == self.prop_remove:
  512. break
  513. else:
  514. self.report({'ERROR'}, "Input not found")
  515. raise RuntimeError("This should not happen!")
  516. # it's possible that the output property's identifier isn't the
  517. # exact same... but I don' care. Shouldn't ever happen. TODO
  518. for j, out in enumerate(n.outputs):
  519. if out.identifier == self.prop_remove:
  520. break
  521. else:
  522. self.report({'ERROR'}, "Output not found")
  523. raise RuntimeError("This should not happen!")
  524. n.inputs.remove ( n.inputs [i] )
  525. n.outputs.remove( n.outputs[j] )
  526. return {'FINISHED'}
  527. # TODO: not a priority
  528. # This one will remove the old socket and add a new one
  529. # and it'll put it back in place and reconnect the links
  530. # It's OK to just ask the user to do this manually for now
  531. #
  532. # class EditCustomProperty(bpy.types.Operator):
  533. # """Edit Custom Property in xForm Node"""
  534. # bl_idname = "mantis.edit_custom_property"
  535. # bl_label = "Edit Custom Property"
  536. # prop_type : bpy.props.EnumProperty(
  537. # items=ePropertyType,
  538. # name="New Property Type",
  539. # description="Type of data for new Property",
  540. # default = 'BOOL',)
  541. # prop_name : bpy.props.StringProperty(default='Prop')
  542. # min:bpy.props.FloatProperty(default = 0)
  543. # max:bpy.props.FloatProperty(default = 1)
  544. # soft_min:bpy.props.FloatProperty(default = 0)
  545. # soft_max:bpy.props.FloatProperty(default = 1)
  546. # description:bpy.props.StringProperty(default = "")
  547. # node_invoked : bpy.props.PointerProperty(type=bpy.types.Node,
  548. # options ={'HIDDEN'}) # note this seems to affect all
  549. # # subsequent properties
  550. # @classmethod
  551. # def poll(cls, context):
  552. # return True #( hasattr(context, 'node') )
  553. # def invoke(self, context, event):
  554. # print (context.node)
  555. # self.node_invoked = context.node
  556. # print(dir(self))
  557. # wm = context.window_manager
  558. # return wm.invoke_props_dialog(self)
  559. # def execute(self, context):
  560. # n = self.node_invoked
  561. # # For whatever reason, context.node doesn't exist anymore
  562. # # (probably because I use a window to execute)
  563. # # so as a sort of dumb workaround I am saving it to a hidden
  564. # # property of the operator... it works.
  565. # socktype = ''
  566. # if not (self.prop_name):
  567. # self.report({'ERROR_INVALID_INPUT'}, "Must name the property.")
  568. # return {'CANCELLED'}
  569. # if self.prop_type == 'BOOL':
  570. # socktype = 'ParameterBoolSocket'
  571. # if self.prop_type == 'INT':
  572. # socktype = 'ParameterIntSocket'
  573. # if self.prop_type == 'FLOAT':
  574. # socktype = 'ParameterFloatSocket'
  575. # if self.prop_type == 'VECTOR':
  576. # socktype = 'ParameterVectorSocket'
  577. # if self.prop_type == 'STRING':
  578. # socktype = 'ParameterStringSocket'
  579. # #if self.prop_type == 'ENUM':
  580. # # sock_type = 'ParameterStringSocket'
  581. # if (s := n.inputs.get(self.prop_name)):
  582. # try:
  583. # number = int(self.prop_name[-3:])
  584. # # see if it has a number
  585. # number+=1
  586. # self.prop_name = self.prop_name[:-3] + str(number).zfill(3)
  587. # except ValueError:
  588. # self.prop_name+='.001'
  589. # new_prop = n.inputs.new( socktype, self.prop_name)
  590. # if self.prop_type in ['INT','FLOAT']:
  591. # new_prop.min = self.min
  592. # new_prop.max = self.max
  593. # new_prop.soft_min = self.soft_min
  594. # new_prop.soft_max = self.soft_max
  595. # new_prop.description = self.description
  596. # return {'FINISHED'}
  597. class EditFCurveNode(bpy.types.Operator):
  598. """Edit the fCurve owned by fCurve node"""
  599. bl_idname = "mantis.edit_fcurve_node"
  600. bl_label = "Edit fCurve"
  601. bl_options = {'INTERNAL'}
  602. my_window : bpy.props.StringProperty(default = "-1")
  603. node_invoked : bpy.props.PointerProperty(type=bpy.types.Node,
  604. options ={'HIDDEN'}) # note this seems to affect all
  605. # subsequent properties
  606. fake_fcurve_ob: bpy.props.PointerProperty(
  607. type=bpy.types.Object,
  608. options ={'HIDDEN'},)
  609. prev_active: bpy.props.PointerProperty(
  610. type=bpy.types.Object,
  611. options ={'HIDDEN'},)
  612. @classmethod
  613. def poll(cls, context):
  614. return True #(hasattr(context, 'active_node') )
  615. def modal(self, context, event):
  616. for w in context.window_manager.windows:
  617. if str(w.as_pointer()) == self.my_window:
  618. break
  619. else:
  620. context.scene.collection.objects.unlink( self.fake_fcurve_ob )
  621. context.view_layer.objects.active = self.prev_active
  622. self.prev_active.select_set(True)
  623. # at this point I will push the fcurve to nodes
  624. # or some kind of internal data
  625. return {'FINISHED'}
  626. # I can't currently think of anything I need to do with w
  627. return {'PASS_THROUGH'}
  628. def invoke(self, context, event):
  629. self.node_invoked = context.node
  630. self.fake_fcurve_ob = self.node_invoked.fake_fcurve_ob
  631. context.scene.collection.objects.link( self.fake_fcurve_ob )
  632. self.prev_active = context.view_layer.objects.active
  633. context.view_layer.objects.active = self.fake_fcurve_ob
  634. self.fake_fcurve_ob.select_set(True)
  635. context.window_manager.modal_handler_add(self)
  636. # this is added to the active window.
  637. if (self.my_window == "-1"):
  638. prev_windows = set()
  639. for w in context.window_manager.windows:
  640. prev_windows.add(w.as_pointer())
  641. bpy.ops.wm.window_new()
  642. for w in context.window_manager.windows:
  643. w_int = w.as_pointer()
  644. if (w_int not in prev_windows):
  645. self.my_window = str(w_int)
  646. break
  647. else:
  648. print ("cancelled")
  649. return {'CANCELLED'}
  650. # set up properties for w
  651. # w.height = 256 # READ
  652. # w.width = 400 # ONLY
  653. w.screen.areas[0].type = 'GRAPH_EDITOR'
  654. w.screen.areas[0].spaces[0].auto_snap = 'NONE'
  655. return {'RUNNING_MODAL'}
  656. # SIMPLE node operators...
  657. # May rewrite these in a more generic way later
  658. class FcurveAddKeyframeInput(bpy.types.Operator):
  659. """Add a keyframe input to the fCurve node"""
  660. bl_idname = "mantis.fcurve_node_add_kf"
  661. bl_label = "Add Keyframe"
  662. bl_options = {'INTERNAL'}
  663. @classmethod
  664. def poll(cls, context):
  665. return (hasattr(context, 'active_node') )
  666. def execute(self, context):
  667. num_keys = len( context.node.inputs)
  668. context.node.inputs.new("KeyframeSocket", "Keyframe."+str(num_keys).zfill(3))
  669. return {'FINISHED'}
  670. class FcurveRemoveKeyframeInput(bpy.types.Operator):
  671. """Remove a keyframe input from the fCurve node"""
  672. bl_idname = "mantis.fcurve_node_remove_kf"
  673. bl_label = "Remove Keyframe"
  674. bl_options = {'INTERNAL'}
  675. @classmethod
  676. def poll(cls, context):
  677. return (hasattr(context, 'active_node') )
  678. def execute(self, context):
  679. n = context.node
  680. n.inputs.remove(n.inputs[-1])
  681. return {'FINISHED'}
  682. class DriverAddDriverVariableInput(bpy.types.Operator):
  683. """Add a Driver Variable input to the Driver node"""
  684. bl_idname = "mantis.driver_node_add_variable"
  685. bl_label = "Add Driver Variable"
  686. bl_options = {'INTERNAL'}
  687. @classmethod
  688. def poll(cls, context):
  689. return (hasattr(context, 'active_node') )
  690. def execute(self, context): # unicode for 'a'
  691. i = len (context.node.inputs) - 2 + 96
  692. context.node.inputs.new("DriverVariableSocket", chr(i))
  693. return {'FINISHED'}
  694. class DriverRemoveDriverVariableInput(bpy.types.Operator):
  695. """Remove a DriverVariable input from the active Driver node"""
  696. bl_idname = "mantis.driver_node_remove_variable"
  697. bl_label = "Remove Driver Variable"
  698. bl_options = {'INTERNAL'}
  699. @classmethod
  700. def poll(cls, context):
  701. return (hasattr(context, 'active_node') )
  702. def execute(self, context):
  703. n = context.node
  704. n.inputs.remove(n.inputs[-1])
  705. return {'FINISHED'}
  706. class LinkArmatureAddTargetInput(bpy.types.Operator):
  707. """Add a Driver Variable input to the Driver node"""
  708. bl_idname = "mantis.link_armature_node_add_target"
  709. bl_label = "Add Target"
  710. bl_options = {'INTERNAL'}
  711. @classmethod
  712. def poll(cls, context):
  713. return hasattr(context, 'node')
  714. def execute(self, context): # unicode for 'a'
  715. num_targets = len( list(context.node.inputs)[6:])//2
  716. context.node.inputs.new("xFormSocket", "Target."+str(num_targets).zfill(3))
  717. context.node.inputs.new("FloatFactorSocket", "Weight."+str(num_targets).zfill(3))
  718. return {'FINISHED'}
  719. class LinkArmatureRemoveTargetInput(bpy.types.Operator):
  720. """Remove a DriverVariable input from the active Driver node"""
  721. bl_idname = "mantis.link_armature_node_remove_target"
  722. bl_label = "Remove Target"
  723. bl_options = {'INTERNAL'}
  724. @classmethod
  725. def poll(cls, context):
  726. return hasattr(context, 'node')
  727. def execute(self, context):
  728. n = context.node
  729. n.inputs.remove(n.inputs[-1]); n.inputs.remove(n.inputs[-1])
  730. return {'FINISHED'}
  731. # class ExportNodeTreeToJSON(Operator):
  732. # """Export this node tree as a JSON file"""
  733. # bl_idname = "mantis.export_node_tree_json"
  734. # bl_label = "Export Mantis Tree to JSON"
  735. # @classmethod
  736. # def poll(cls, context):
  737. # return (mantis_tree_poll_op(context))
  738. # def execute(self, context):
  739. # from .i_o import export_to_json
  740. # import bpy
  741. # tree = context.space_data.path[0].node_tree
  742. # # tree.update_tree(context)
  743. # trees = {tree}
  744. # check_trees=[tree]
  745. # while check_trees:
  746. # check = check_trees.pop()
  747. # for n in check.nodes:
  748. # if hasattr(n, "node_tree"):
  749. # if n.node_tree not in trees:
  750. # check_trees.append(n.node_tree)
  751. # trees.add(n.node_tree)
  752. # def remove_special_characters(stritree):
  753. # # https://stackoverflow.com/questions/295135/turn-a-stritree-into-a-valid-filename
  754. # # thank you user "Sophie Gage"
  755. # import re # regular expressions
  756. # return re.sub('[^\w_.)( -]', '', stritree)
  757. # path = bpy.path.abspath('//')+remove_special_characters(tree.name)+".json"
  758. # export_to_json(trees, path)
  759. # return {"FINISHED"}