ops_nodegroup.py 32 KB

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