misc_containers.py 66 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777
  1. from .node_container_common import *
  2. # The fact that I need this means that some of these classes should
  3. # probably be moved to link_containers.py
  4. from .xForm_containers import xFormArmature, xFormBone
  5. from math import pi, tau
  6. def TellClasses():
  7. return [
  8. # utility
  9. InputFloat,
  10. InputIntNode,
  11. InputVector,
  12. InputBoolean,
  13. InputBooleanThreeTuple,
  14. InputRotationOrder,
  15. InputTransformSpace,
  16. InputString,
  17. InputMatrix,
  18. InputExistingGeometryObject,
  19. InputExistingGeometryData,
  20. UtilityPointFromCurve,
  21. UtilityMatrixFromCurve,
  22. UtilityMatricesFromCurve,
  23. UtilityMetaRig,
  24. UtilityBoneProperties,
  25. UtilityDriverVariable,
  26. UtilityDriver,
  27. UtilityFCurve,
  28. UtilityKeyframe,
  29. UtilitySwitch,
  30. UtilityCombineThreeBool,
  31. UtilityCombineVector,
  32. UtilityCatStrings,
  33. UtilityGetBoneLength,
  34. UtilityPointFromBoneMatrix,
  35. UtilitySetBoneLength,
  36. UtilityMatrixSetLocation,
  37. UtilityMatrixGetLocation,
  38. UtilityMatrixFromXForm,
  39. UtilityAxesFromMatrix,
  40. UtilityBoneMatrixHeadTailFlip,
  41. UtilityMatrixTransform,
  42. UtilityTransformationMatrix,
  43. UtilityIntToString,
  44. UtilityArrayGet,
  45. UtilitySetBoneMatrixTail,
  46. # Control flow switches
  47. UtilityCompare,
  48. UtilityChoose,
  49. # useful NoOp:
  50. UtilityPrint,
  51. ]
  52. def matrix_from_head_tail(head, tail):
  53. from mathutils import Vector, Quaternion, Matrix
  54. rotation = Vector((0,1,0)).rotation_difference((tail-head).normalized()).to_matrix()
  55. m= Matrix.LocRotScale(head, rotation, None)
  56. m[3][3] = (tail-head).length
  57. return m
  58. #*#-------------------------------#++#-------------------------------#*#
  59. # U T I L I T Y N O D E S
  60. #*#-------------------------------#++#-------------------------------#*#
  61. class InputFloat:
  62. '''A node representing float input'''
  63. def __init__(self, signature, base_tree):
  64. self.base_tree=base_tree
  65. self.signature = signature
  66. self.inputs = {}
  67. self.outputs = {"Float Input" : NodeSocket(name = "Float Input", node=self) }
  68. self.parameters = {"Float Input":None, "Mute":None}
  69. self.node_type = 'UTILITY'
  70. self.hierarchy_connections = []
  71. self.connections = []
  72. self.hierarchy_dependencies = []
  73. self.dependencies = []
  74. self.prepared = True
  75. self.executed = True
  76. def evaluate_input(self, input_name):
  77. return self.parameters["Float Input"]
  78. class InputIntNode:
  79. '''A node representing integer input'''
  80. def __init__(self, signature, base_tree):
  81. self.base_tree=base_tree
  82. self.signature = signature
  83. self.inputs = {}
  84. self.outputs = {"Integer" : NodeSocket(name = "Integer", node=self) }
  85. self.parameters = {"Integer":None, "Mute":None}
  86. self.node_type = 'UTILITY'
  87. self.hierarchy_connections = []
  88. self.connections = []
  89. self.hierarchy_dependencies = []
  90. self.dependencies = []
  91. self.prepared = True
  92. self.executed = True
  93. def evaluate_input(self, input_name):
  94. return self.parameters["Integer"]
  95. class InputVector:
  96. '''A node representing vector input'''
  97. def __init__(self, signature, base_tree):
  98. self.base_tree=base_tree
  99. self.signature = signature
  100. self.inputs = {}
  101. self.outputs = {"" : NodeSocket(name = '', node=self) }
  102. self.parameters = {'':None, "Mute":None}
  103. self.node_type = 'UTILITY'
  104. self.hierarchy_connections = []
  105. self.connections = []
  106. self.hierarchy_dependencies = []
  107. self.dependencies = []
  108. self.prepared = True
  109. self.executed = True
  110. def evaluate_input(self, input_name):
  111. return self.parameters[""]
  112. class InputBoolean:
  113. '''A node representing boolean input'''
  114. def __init__(self, signature, base_tree):
  115. self.base_tree=base_tree
  116. self.signature = signature
  117. self.inputs = {}
  118. self.outputs = {"" : NodeSocket(name = '', node=self) }
  119. self.parameters = {'':None, "Mute":None}
  120. self.node_type = 'UTILITY'
  121. self.hierarchy_connections = []
  122. self.connections = []
  123. self.hierarchy_dependencies = []
  124. self.dependencies = []
  125. self.prepared = True
  126. self.executed = True
  127. def evaluate_input(self, input_name):
  128. return self.parameters[""]
  129. class InputBooleanThreeTuple:
  130. '''A node representing a tuple of three booleans'''
  131. def __init__(self, signature, base_tree):
  132. self.base_tree=base_tree
  133. self.signature = signature
  134. self.inputs = {}
  135. self.outputs = {"" : NodeSocket(name = '', node=self) }
  136. self.parameters = {'':None, "Mute":None}
  137. self.node_type = 'UTILITY'
  138. self.hierarchy_connections = []
  139. self.connections = []
  140. self.hierarchy_dependencies = []
  141. self.dependencies = []
  142. self.prepared = True
  143. self.executed = True
  144. def evaluate_input(self, input_name):
  145. return self.parameters[""]
  146. class InputRotationOrder:
  147. '''A node representing string input for rotation order'''
  148. def __init__(self, signature, base_tree):
  149. self.base_tree=base_tree
  150. self.signature = signature
  151. self.inputs = {}
  152. self.outputs = {"" : NodeSocket(name = '', node=self) }
  153. self.parameters = {'':None, "Mute":None}
  154. self.node_type = 'UTILITY'
  155. self.hierarchy_connections = []
  156. self.connections = []
  157. self.hierarchy_dependencies = []
  158. self.dependencies = []
  159. self.prepared = True
  160. self.executed = True
  161. def evaluate_input(self, input_name):
  162. return self.parameters[""]
  163. class InputTransformSpace:
  164. '''A node representing string input for transform space'''
  165. def __init__(self, signature, base_tree):
  166. self.base_tree=base_tree
  167. self.signature = signature
  168. self.inputs = {}
  169. self.outputs = {"" : NodeSocket(name = '', node=self) }
  170. self.parameters = {'':None, "Mute":None}
  171. self.node_type = 'UTILITY'
  172. self.hierarchy_connections = []
  173. self.connections = []
  174. self.hierarchy_dependencies = []
  175. self.dependencies = []
  176. self.prepared = True
  177. self.executed = True
  178. def evaluate_input(self, input_name):
  179. return self.parameters[""]
  180. class InputString:
  181. '''A node representing string input'''
  182. def __init__(self, signature, base_tree):
  183. self.base_tree=base_tree
  184. self.signature = signature
  185. self.inputs = {}
  186. self.outputs = {"" : NodeSocket(name = '', node=self) }
  187. self.parameters = {'':None, "Mute":None}
  188. self.node_type = 'UTILITY'
  189. self.hierarchy_connections = []
  190. self.connections = []
  191. self.hierarchy_dependencies = []
  192. self.dependencies = []
  193. self.prepared = True
  194. self.executed = True
  195. def evaluate_input(self, input_name):
  196. return self.parameters[""]
  197. class InputMatrix:
  198. '''A node representing axis-angle quaternion input'''
  199. def __init__(self, signature, base_tree):
  200. self.base_tree=base_tree
  201. self.signature = signature
  202. self.inputs = {}
  203. self.outputs = {"Matrix" : NodeSocket(name = 'Matrix', node=self) }
  204. self.parameters = {'Matrix':None, "Mute":None}
  205. self.node_type = 'UTILITY'
  206. self.hierarchy_connections = []
  207. self.connections = []
  208. self.hierarchy_dependencies = []
  209. self.dependencies = []
  210. self.prepared = True
  211. self.executed = True
  212. #
  213. from mathutils import Matrix
  214. from .utilities import get_node_prototype
  215. node_prototype = get_node_prototype(self.signature, self.base_tree)
  216. matrix = ( node_prototype.first_row[ 0], node_prototype.first_row[ 1], node_prototype.first_row[ 2], node_prototype.first_row[ 3],
  217. node_prototype.second_row[0], node_prototype.second_row[1], node_prototype.second_row[2], node_prototype.second_row[3],
  218. node_prototype.third_row[ 0], node_prototype.third_row[ 1], node_prototype.third_row[ 2], node_prototype.third_row[ 3],
  219. node_prototype.fourth_row[0], node_prototype.fourth_row[1], node_prototype.fourth_row[2], node_prototype.fourth_row[3], )
  220. self.parameters["Matrix"] = Matrix([matrix[0:4], matrix[4:8], matrix[8:12], matrix[12:16]])
  221. def evaluate_input(self, input_name):
  222. return self.parameters["Matrix"]
  223. def fill_parameters(self):
  224. return
  225. class UtilityMatrixFromCurve:
  226. '''Get a matrix from a curve'''
  227. def __init__(self, signature, base_tree):
  228. self.base_tree=base_tree
  229. self.executed = False
  230. self.signature = signature
  231. self.inputs = {
  232. "Curve" : NodeSocket(is_input = True, name = "Curve", node=self),
  233. "Total Divisions" : NodeSocket(is_input = True, name = "Total Divisions", node=self),
  234. "Matrix Index" : NodeSocket(is_input = True, name = "Matrix Index", node=self),
  235. }
  236. self.outputs = {
  237. "Matrix" : NodeSocket(name = "Matrix", node=self),
  238. }
  239. self.parameters = {
  240. "Curve" : None,
  241. "Total Divisions" : None,
  242. "Matrix Index" : None,
  243. "Matrix" : None,
  244. }
  245. self.node_type = "UTILITY"
  246. self.hierarchy_connections, self.connections = [], []
  247. self.hierarchy_dependencies, self.dependencies = [], []
  248. self.prepared, self.executed = False, False
  249. def bPrepare(self, bContext = None,):
  250. from mathutils import Matrix
  251. import bpy
  252. m = Matrix.Identity(4)
  253. curve = bpy.data.objects.get(self.evaluate_input("Curve"))
  254. if not curve:
  255. prRed(f"No curve found for {self}. Using an Identity matrix instead.")
  256. m[3][3] = 1.0
  257. else:
  258. from .utilities import mesh_from_curve, data_from_ribbon_mesh
  259. if not bContext:
  260. # TODO find out if this is bad or a HACK or if it is OK
  261. bContext = bpy.context
  262. # IMPORTANT TODO: I need to be able to reuse this m
  263. # First, try to get the one we made before
  264. m_name = curve.name+'.'+self.base_tree.execution_id
  265. if not (m := bpy.data.meshes.get(m_name)):
  266. m = mesh_from_curve(curve, bContext)
  267. m.name = m_name
  268. #
  269. num_divisions = self.evaluate_input("Total Divisions")
  270. m_index = self.evaluate_input("Matrix Index")
  271. factors = [1/num_divisions*m_index, 1/num_divisions*(m_index+1)]
  272. data = data_from_ribbon_mesh(m, [factors], curve.matrix_world)
  273. # print(data)
  274. # this is in world space... let's just convert it back
  275. m = matrix_from_head_tail(data[0][0][0], data[0][0][1])
  276. m.translation -= curve.location
  277. # TODO HACK TODO
  278. # all the nodes should work in world-space, and it should be the responsibility
  279. # of the xForm node to convert!
  280. self.parameters["Matrix"] = m
  281. self.prepared = True
  282. self.executed = True
  283. def bFinalize(self, bContext=None):
  284. import bpy
  285. curve_name = self.evaluate_input("Curve")
  286. curve = bpy.data.objects.get(curve_name)
  287. m_name = curve.name+'.'+self.base_tree.execution_id
  288. if (mesh := bpy.data.meshes.get(m_name)):
  289. bpy.data.meshes.remove(mesh)
  290. def fill_parameters(self):
  291. fill_parameters(self)
  292. class UtilityPointFromCurve:
  293. '''Get a point from a curve'''
  294. def __init__(self, signature, base_tree):
  295. self.base_tree=base_tree
  296. self.executed = False
  297. self.signature = signature
  298. self.inputs = {
  299. "Curve" : NodeSocket(is_input = True, name = "Curve", node=self),
  300. "Factor" : NodeSocket(is_input = True, name = "Factor", node=self),
  301. }
  302. self.outputs = {
  303. "Point" : NodeSocket(name = "Point", node=self),
  304. }
  305. self.parameters = {
  306. "Curve" : None,
  307. "Factor" : None,
  308. "Point" : None,
  309. }
  310. self.node_type = "UTILITY"
  311. self.hierarchy_connections, self.connections = [], []
  312. self.hierarchy_dependencies, self.dependencies = [], []
  313. self.prepared, self.executed = False, False
  314. def bPrepare(self, bContext = None,):
  315. from mathutils import Matrix
  316. import bpy
  317. curve = bpy.data.objects.get(self.evaluate_input("Curve"))
  318. if not curve:
  319. raise RuntimeError(f"No curve found for {self}.")
  320. else:
  321. from .utilities import mesh_from_curve, data_from_ribbon_mesh
  322. if not bContext:
  323. # TODO find out if this is bad or a HACK or if it is OK
  324. bContext = bpy.context
  325. # IMPORTANT TODO: I need to be able to reuse this m
  326. # First, try to get the one we made before
  327. m_name = curve.name+'.'+self.base_tree.execution_id
  328. if not (m := bpy.data.meshes.get(m_name)):
  329. m = mesh_from_curve(curve, bContext)
  330. m.name = m_name
  331. #
  332. num_divisions = 1
  333. factors = [self.evaluate_input("Factor")]
  334. data = data_from_ribbon_mesh(m, [factors], curve.matrix_world)
  335. p = data[0][0][0] - curve.location
  336. self.parameters["Point"] = p
  337. self.prepared, self.executed = True, True
  338. def bFinalize(self, bContext=None):
  339. import bpy
  340. curve_name = self.evaluate_input("Curve")
  341. curve = bpy.data.objects.get(curve_name)
  342. m_name = curve.name+'.'+self.base_tree.execution_id
  343. if (mesh := bpy.data.meshes.get(m_name)):
  344. bpy.data.meshes.remove(mesh)
  345. def fill_parameters(self):
  346. fill_parameters(self)
  347. class UtilityMatricesFromCurve:
  348. '''Get matrices from a curve'''
  349. def __init__(self, signature, base_tree):
  350. self.base_tree=base_tree
  351. self.executed = False
  352. self.signature = signature
  353. self.inputs = {
  354. "Curve" : NodeSocket(is_input = True, name = "Curve", node=self),
  355. "Total Divisions" : NodeSocket(is_input = True, name = "Total Divisions", node=self),
  356. # "Matrix Index" : NodeSocket(is_input = True, name = "Matrix Index", node=self),
  357. }
  358. self.outputs = {
  359. "Matrices" : NodeSocket(name = "Matrices", node=self),
  360. }
  361. self.parameters = {
  362. "Curve" : None,
  363. "Total Divisions" : None,
  364. # "Matrix Index" : None,
  365. "Matrices" : None,
  366. }
  367. self.node_type = "UTILITY"
  368. self.hierarchy_connections = []
  369. self.connections = []
  370. self.hierarchy_dependencies = []
  371. self.dependencies = []
  372. self.prepared = False
  373. self.executed = False
  374. def bPrepare(self, bContext = None,):
  375. import time
  376. # start_time = time.time()
  377. #
  378. from mathutils import Matrix
  379. import bpy
  380. m = Matrix.Identity(4)
  381. curve_name = self.evaluate_input("Curve")
  382. curve = bpy.data.objects.get(curve_name)
  383. if not curve:
  384. prRed(f"No curve found for {self}. Using an Identity matrix instead.")
  385. m[3][3] = 1.0
  386. else:
  387. from .utilities import mesh_from_curve, data_from_ribbon_mesh
  388. if not bContext:
  389. bContext = bpy.context
  390. m_name = curve.name+'.'+self.base_tree.execution_id
  391. if not (mesh := bpy.data.meshes.get(m_name)):
  392. mesh = mesh_from_curve(curve, bContext)
  393. mesh.name = m_name
  394. num_divisions = self.evaluate_input("Total Divisions")
  395. factors = [0.0] + [(1/num_divisions*(i+1)) for i in range(num_divisions)]
  396. data = data_from_ribbon_mesh(mesh, [factors], curve.matrix_world)
  397. # 0 is the spline index. 0 selects points as opposed to normals or whatever.
  398. matrices = [matrix_from_head_tail(data[0][0][i], data[0][0][i+1]) for i in range(num_divisions)]
  399. for link in self.outputs["Matrices"].links:
  400. for i, m in enumerate(matrices):
  401. name = "Matrix"+str(i).zfill(4)
  402. if not (out := self.outputs.get(name)): # reuse them if there are multiple links.
  403. out = self.outputs[name] = NodeSocket(name = name, node=self)
  404. c = out.connect(link.to_node, link.to_socket)
  405. # prOrange(c)
  406. self.parameters[name] = m
  407. # print (mesh)
  408. link.die()
  409. self.prepared = True
  410. self.executed = True
  411. # prGreen(f"Matrices from curves took {time.time() - start_time} seconds.")
  412. def bFinalize(self, bContext=None):
  413. import bpy
  414. curve_name = self.evaluate_input("Curve")
  415. curve = bpy.data.objects.get(curve_name)
  416. m_name = curve.name+'.'+self.base_tree.execution_id
  417. if (mesh := bpy.data.meshes.get(m_name)):
  418. prGreen(f"Freeing mesh data {m_name}...")
  419. bpy.data.meshes.remove(mesh)
  420. def fill_parameters(self):
  421. fill_parameters(self)
  422. class UtilityMetaRig:
  423. '''A node representing an armature object'''
  424. def __init__(self, signature, base_tree):
  425. self.base_tree=base_tree
  426. self.executed = False
  427. self.signature = signature
  428. self.inputs = {
  429. "Meta-Armature" : NodeSocket(is_input = True, name = "Meta-Armature", node=self),
  430. "Meta-Bone" : NodeSocket(is_input = True, name = "Meta-Bone", node=self),
  431. }
  432. self.outputs = {
  433. "Matrix" : NodeSocket(name = "Matrix", node=self),
  434. }
  435. self.parameters = {
  436. "Meta-Armature" : None,
  437. "Meta-Bone" : None,
  438. }
  439. self.node_type = "UTILITY"
  440. self.hierarchy_connections = []
  441. self.connections = []
  442. self.hierarchy_dependencies = []
  443. self.dependencies = []
  444. self.prepared = False
  445. self.executed = False
  446. def bPrepare(self, bContext = None,):
  447. #kinda clumsy, whatever
  448. import bpy
  449. from mathutils import Matrix
  450. m = Matrix.Identity(4)
  451. meta_rig = self.evaluate_input("Meta-Armature")
  452. meta_bone = self.evaluate_input("Meta-Bone")
  453. if meta_rig:
  454. if ( armOb := bpy.data.objects.get(meta_rig) ):
  455. m = armOb.matrix_world
  456. if ( b := armOb.data.bones.get(meta_bone)):
  457. # calculate the correct object-space matrix
  458. m = Matrix.Identity(3)
  459. bones = [] # from the last ancestor, mult the matrices until we get to b
  460. while (b): bones.append(b); b = b.parent
  461. while (bones): b = bones.pop(); m = m @ b.matrix
  462. m = Matrix.Translation(b.head_local) @ m.to_4x4()
  463. #
  464. m[3][3] = b.length # this is where I arbitrarily decided to store length
  465. # else:
  466. # prRed("no bone for MetaRig node ", self)
  467. else:
  468. raise RuntimeError(wrapRed(f"No meta-rig input for MetaRig node {self}"))
  469. self.parameters["Matrix"] = m
  470. self.prepared = True
  471. self.executed = True
  472. def fill_parameters(self):
  473. fill_parameters(self)
  474. # self.parameters["Matrix"] = None # why in the
  475. class UtilityBoneProperties:
  476. '''A node representing an armature object'''
  477. def __init__(self, signature, base_tree):
  478. self.base_tree=base_tree
  479. self.executed = False
  480. self.signature = signature
  481. self.inputs = {}
  482. self.outputs = {
  483. "matrix" : NodeSocket(name = "matrix", node=self),
  484. "matrix_local" : NodeSocket(name = "matrix_local", node=self),
  485. "matrix_basis" : NodeSocket(name = "matrix_basis", node=self),
  486. "head" : NodeSocket(name = "head", node=self),
  487. "tail" : NodeSocket(name = "tail", node=self),
  488. "length" : NodeSocket(name = "length", node=self),
  489. "rotation" : NodeSocket(name = "rotation", node=self),
  490. "location" : NodeSocket(name = "location", node=self),
  491. "scale" : NodeSocket(name = "scale", node=self),
  492. }
  493. self.parameters = {
  494. "matrix":None,
  495. "matrix_local":None,
  496. "matrix_basis":None,
  497. "head":None,
  498. "tail":None,
  499. "length":None,
  500. "rotation":None,
  501. "location":None,
  502. "scale":None,
  503. }
  504. self.node_type = "UTILITY"
  505. #
  506. self.hierarchy_connections = []
  507. self.connections = []
  508. self.hierarchy_dependencies = []
  509. self.dependencies = []
  510. self.prepared = True
  511. self.executed = True
  512. def fill_parameters(self):
  513. pass#fill_parameters(self)
  514. # TODO this should probably be moved to Links
  515. class UtilityDriverVariable:
  516. '''A node representing an armature object'''
  517. def __init__(self, signature, base_tree):
  518. self.base_tree=base_tree
  519. self.executed = False
  520. self.signature = signature
  521. self.inputs = {
  522. "Variable Type" : NodeSocket(is_input = True, name = "Variable Type", node = self),
  523. "Property" : NodeSocket(is_input = True, name = "Property", node = self),
  524. "Property Index" : NodeSocket(is_input = True, name = "Property Index", node = self),
  525. "Evaluation Space" : NodeSocket(is_input = True, name = "Evaluation Space", node = self),
  526. "Rotation Mode" : NodeSocket(is_input = True, name = "Rotation Mode", node = self),
  527. "xForm 1" : NodeSocket(is_input = True, name = "xForm 1", node = self),
  528. "xForm 2" : NodeSocket(is_input = True, name = "xForm 2", node = self),
  529. }
  530. self.outputs = {
  531. "Driver Variable" : NodeSocket(name = "Driver Variable", node=self),
  532. }
  533. self.parameters = {
  534. "Variable Type":None,
  535. "Property":None,
  536. "Property Index":None,
  537. "Evaluation Space":None,
  538. "Rotation Mode":None,
  539. "xForm 1":None,
  540. "xForm 2":None,
  541. }
  542. self.node_type = "DRIVER" # MUST be run in Pose mode
  543. #
  544. self.hierarchy_connections = []
  545. self.connections = []
  546. self.hierarchy_dependencies = []
  547. self.dependencies = []
  548. self.prepared = True
  549. self.executed = False
  550. def evaluate_input(self, input_name):
  551. if input_name == 'Property':
  552. if self.inputs['Property'].is_linked:
  553. # get the name instead...
  554. trace = trace_single_line(self, input_name)
  555. return trace[1].name # the name of the socket
  556. return self.parameters["Property"]
  557. return evaluate_input(self, input_name)
  558. def GetxForm(self, index=1):
  559. trace = trace_single_line(self, "xForm 1" if index == 1 else "xForm 2")
  560. for node in trace[0]:
  561. if (node.__class__ in [xFormArmature, xFormBone]):
  562. return node #this will fetch the first one, that's good!
  563. return None
  564. def bExecute(self, bContext = None,):
  565. prepare_parameters(self)
  566. #prPurple ("Executing Driver Variable Node")
  567. xForm1 = self.GetxForm()
  568. xForm2 = self.GetxForm(index=2)
  569. # kinda clumsy
  570. if xForm1 : xForm1 = xForm1.bGetObject()
  571. if xForm2 : xForm2 = xForm2.bGetObject()
  572. v_type = self.evaluate_input("Variable Type")
  573. i = self.evaluate_input("Property Index"); dVarChannel = ""
  574. if (i >= 0): #negative values will use the vector property.
  575. if self.evaluate_input("Property") == 'location':
  576. if i == 0: dVarChannel = "LOC_X"
  577. elif i == 1: dVarChannel = "LOC_Y"
  578. elif i == 2: dVarChannel = "LOC_Z"
  579. else: raise RuntimeError("Invalid property index for %s" % self)
  580. if self.evaluate_input("Property") == 'rotation':
  581. if i == 0: dVarChannel = "ROT_X"
  582. elif i == 1: dVarChannel = "ROT_Y"
  583. elif i == 2: dVarChannel = "ROT_Z"
  584. elif i == 3: dVarChannel = "ROT_W"
  585. else: raise RuntimeError("Invalid property index for %s" % self)
  586. if self.evaluate_input("Property") == 'scale':
  587. if i == 0: dVarChannel = "SCALE_X"
  588. elif i == 1: dVarChannel = "SCALE_Y"
  589. elif i == 2: dVarChannel = "SCALE_Z"
  590. elif i == 3: dVarChannel = "SCALE_AVG"
  591. else: raise RuntimeError("Invalid property index for %s" % self)
  592. if self.evaluate_input("Property") == 'scale_average':
  593. dVarChannel = "SCALE_AVG"
  594. if dVarChannel: v_type = "TRANSFORMS"
  595. my_var = {
  596. "owner" : xForm1, # will be filled in by Driver
  597. "prop" : self.evaluate_input("Property"), # will be filled in by Driver
  598. "type" : v_type,
  599. "space" : self.evaluate_input("Evaluation Space"),
  600. "rotation_mode" : self.evaluate_input("Rotation Mode"),
  601. "xForm 1" : xForm1,#self.GetxForm(index = 1),
  602. "xForm 2" : xForm2,#self.GetxForm(index = 2),
  603. "channel" : dVarChannel,}
  604. # Push parameter to downstream connected node.connected:
  605. if (out := self.outputs["Driver Variable"]).is_linked:
  606. self.parameters[out.name] = my_var
  607. for link in out.links:
  608. link.to_node.parameters[link.to_socket] = my_var
  609. self.executed = True
  610. class UtilityKeyframe:
  611. '''A node representing a keyframe for a F-Curve'''
  612. def __init__(self, signature, base_tree):
  613. self.base_tree=base_tree
  614. self.executed = False
  615. self.signature = signature
  616. self.inputs = {
  617. "Frame" : NodeSocket(is_input = True, name = "Frame", node = self),
  618. "Value" : NodeSocket(is_input = True, name = "Value", node = self),
  619. }
  620. self.outputs = {
  621. "Keyframe" : NodeSocket(name = "Keyframe", node=self),
  622. }
  623. self.parameters = {
  624. "Frame":None,
  625. "Value":None,
  626. "Keyframe":{}, # for some reason I have to initialize this and then use the dict... why? TODO find out
  627. }
  628. self.node_type = "DRIVER" # MUST be run in Pose mode
  629. setup_custom_props(self)
  630. self.hierarchy_connections = []
  631. self.connections = []
  632. self.hierarchy_dependencies = []
  633. self.dependencies = []
  634. self.prepared = False
  635. self.executed = False
  636. def bPrepare(self, bContext = None,):
  637. key = self.parameters["Keyframe"]
  638. from mathutils import Vector
  639. key["co"]= Vector( (self.evaluate_input("Frame"), self.evaluate_input("Value"),))
  640. key["type"]="GENERATED"
  641. key["interpolation"] = "LINEAR"
  642. # eventually this will have the right data, TODO
  643. # self.parameters["Keyframe"] = key
  644. self.prepared = True
  645. self.executed = True
  646. class UtilityFCurve:
  647. '''A node representing an armature object'''
  648. def __init__(self, signature, base_tree):
  649. self.base_tree=base_tree
  650. self.executed = False
  651. self.signature = signature
  652. self.inputs = {}
  653. self.outputs = {
  654. "fCurve" : NodeSocket(name = "fCurve", node=self),
  655. }
  656. self.parameters = {
  657. "Use_KeyFrame_Nodes": True, # HACK
  658. "fCurve":None,
  659. }
  660. self.node_type = "UTILITY"
  661. setup_custom_props(self)
  662. self.hierarchy_connections = []
  663. self.connections = []
  664. self.hierarchy_dependencies = []
  665. self.dependencies = []
  666. self.prepared = False
  667. self.executed = False
  668. def evaluate_input(self, input_name):
  669. return evaluate_input(self, input_name)
  670. def bExecute(self, bContext = None,):
  671. prepare_parameters(self)
  672. from .utilities import get_node_prototype
  673. np = get_node_prototype(self.signature, self.base_tree)
  674. keys = []
  675. #['amplitude', 'back', 'bl_rna', 'co', 'co_ui', 'easing', 'handle_left', 'handle_left_type', 'handle_right', 'handle_right_type',
  676. # 'interpolation', 'period', 'rna_type', 'select_control_point', 'select_left_handle', 'select_right_handle', 'type']
  677. if True: #np.use_kf_nodes:
  678. for k in self.inputs.keys():
  679. # print (self.inputs[k])
  680. if (key := self.evaluate_input(k)) is None:
  681. prOrange(f"WARN: No enough keyframes connected to {self}:{k}. Skipping Link.")
  682. else:
  683. keys.append(key)
  684. else:
  685. raise NotImplementedError("Getting the keys from an fCurve isn't really working anymore lol need to fix")
  686. fc_ob = np.fake_fcurve_ob
  687. fc = fc_ob.animation_data.action.fcurves[0]
  688. for k in fc.keyframe_points:
  689. key = {}
  690. for prop in dir(k):
  691. if ("__" in prop) or ("bl_" in prop): continue
  692. #it's __name__ or bl_rna or something
  693. key[prop] = getattr(k, prop)
  694. keys.append(key)
  695. # Push parameter to downstream connected node.connected:
  696. # TODO: find out if this is necesary, even
  697. if (out := self.outputs["fCurve"]).is_linked:
  698. self.parameters[out.name] = keys
  699. for link in out.links:
  700. link.to_node.parameters[link.to_socket] = keys
  701. # the above was a HACK. I don't want nodes modiying their descenedents.
  702. # If the above was necessary, I want to get an error from it so I can fix it in the descendent's class
  703. if len(keys) <1:
  704. prOrange(f"WARN: no keys in fCurve {self}.")
  705. self.prepared = True
  706. self.executed = True
  707. class UtilityDriver:
  708. '''A node representing an armature object'''
  709. def __init__(self, signature, base_tree):
  710. self.base_tree=base_tree
  711. self.executed = False
  712. self.signature = signature
  713. self.inputs = {
  714. "Driver Type" : NodeSocket(is_input = True, name = "Driver Type", node = self),
  715. "Expression" : NodeSocket(is_input = True, name = "Expression", node = self),
  716. "fCurve" : NodeSocket(is_input = True, name = "fCurve", node = self),
  717. }
  718. self.outputs = {
  719. "Driver" : NodeSocket(name = "Driver", node=self),
  720. }
  721. from .drivers import MantisDriver
  722. self.parameters = {
  723. "Driver Type":None,
  724. "Expression":None,
  725. "fCurve":None,
  726. "Driver":MantisDriver(),
  727. }
  728. self.node_type = "DRIVER" # MUST be run in Pose mode
  729. setup_custom_props(self)
  730. self.hierarchy_connections = []
  731. self.connections = []
  732. self.hierarchy_dependencies = []
  733. self.dependencies = []
  734. self.prepared = True
  735. self.executed = True
  736. def bExecute(self, bContext = None,):
  737. prepare_parameters(self)
  738. from .drivers import MantisDriver
  739. #prPurple("Executing Driver Node")
  740. my_vars = []
  741. if (keys := self.evaluate_input("fCurve")) is None:
  742. keys={}
  743. prWhite(f"INFO: no fCurve connected to {self}; using default fCurve.")
  744. from mathutils import Vector
  745. keys = [
  746. {"co":Vector( (0, 0,)), "type":"GENERATED", "interpolation":"LINEAR" },
  747. {"co":Vector( (1, 1,)), "type":"GENERATED", "interpolation":"LINEAR" },
  748. ]
  749. for inp in list(self.inputs.keys() )[3:]:
  750. if (new_var := self.evaluate_input(inp)):
  751. new_var["name"] = inp
  752. my_vars.append(new_var)
  753. else:
  754. raise RuntimeError(f"Failed to initialize Driver variable for {self}")
  755. my_driver ={ "owner" : None,
  756. "prop" : None, # will be filled out in the node that uses the driver
  757. "expression" : self.evaluate_input("Expression"),
  758. "ind" : -1, # same here
  759. "type" : self.evaluate_input("Driver Type"),
  760. "vars" : my_vars,
  761. "keys" : keys, }
  762. my_driver = MantisDriver(my_driver)
  763. self.parameters["Driver"].update(my_driver)
  764. print("Initializing driver %s " % (wrapPurple(self.__repr__())) )
  765. self.executed = True
  766. class UtilitySwitch:
  767. '''A node representing an armature object'''
  768. def __init__(self, signature, base_tree):
  769. self.base_tree=base_tree
  770. self.executed = False
  771. self.signature = signature
  772. self.inputs = {
  773. # "xForm" : NodeSocket(is_input = True, name = "xForm", node = self),
  774. "Parameter" : NodeSocket(is_input = True, name = "Parameter", node = self),
  775. "Parameter Index" : NodeSocket(is_input = True, name = "Parameter Index", node = self),
  776. "Invert Switch" : NodeSocket(is_input = True, name = "Invert Switch", node = self),
  777. }
  778. self.outputs = {
  779. "Driver" : NodeSocket(name = "Driver", node=self),
  780. }
  781. from .drivers import MantisDriver
  782. self.parameters = {
  783. # "xForm":None,
  784. "Parameter":None,
  785. "Parameter Index":None,
  786. "Invert Switch":None,
  787. "Driver":MantisDriver(), # empty for now
  788. }
  789. self.node_type = "DRIVER" # MUST be run in Pose mode
  790. self.hierarchy_connections = []
  791. self.connections = []
  792. self.hierarchy_dependencies = []
  793. self.dependencies = []
  794. self.prepared = True
  795. self.executed = False
  796. def evaluate_input(self, input_name):
  797. if input_name == 'Parameter':
  798. if self.inputs['Parameter'].is_connected:
  799. trace = trace_single_line(self, input_name)
  800. return trace[1].name # the name of the socket
  801. return self.parameters["Parameter"]
  802. return evaluate_input(self, input_name)
  803. def GetxForm(self,):
  804. trace = trace_single_line(self, "Parameter" )
  805. for node in trace[0]:
  806. if (node.__class__ in [xFormArmature, xFormBone]):
  807. return node #this will fetch the first one, that's good!
  808. return None
  809. def bExecute(self, bContext = None,):
  810. #prepare_parameters(self)
  811. #prPurple ("Executing Switch Node")
  812. xForm = self.GetxForm()
  813. if xForm : xForm = xForm.bGetObject()
  814. if not xForm:
  815. raise RuntimeError("Could not evaluate xForm for %s" % self)
  816. from .drivers import MantisDriver
  817. my_driver ={ "owner" : None,
  818. "prop" : None, # will be filled out in the node that uses the driver
  819. "ind" : -1, # same here
  820. "type" : "SCRIPTED",
  821. "vars" : [ { "owner" : xForm,
  822. "prop" : self.evaluate_input("Parameter"),
  823. "name" : "a",
  824. "type" : "SINGLE_PROP", } ],
  825. "keys" : [ { "co":(0,0),
  826. "interpolation": "LINEAR",
  827. "type":"KEYFRAME",}, #display type
  828. { "co":(1,1),
  829. "interpolation": "LINEAR",
  830. "type":"KEYFRAME",},], }
  831. my_driver ["expression"] = "a"
  832. my_driver = MantisDriver(my_driver)
  833. # this makes it so I can check for type later!
  834. if self.evaluate_input("Invert Switch") == True:
  835. my_driver ["expression"] = "1 - a"
  836. # this way, regardless of what order things are handled, the
  837. # driver is sent to the next node.
  838. # In the case of some drivers, the parameter may be sent out
  839. # before it's filled in (because there is a circular dependency)
  840. # I want to support this behaviour because Blender supports it,
  841. # but I also do not want to support it because it makes things
  842. # more complex and IMO it's bad practice.
  843. # We do not make a copy. We update the driver, so that
  844. # the same instance is filled out.
  845. self.parameters["Driver"].update(my_driver)
  846. print("Initializing driver %s " % (wrapPurple(self.__repr__())) )
  847. self.executed = True
  848. class UtilityCombineThreeBool:
  849. '''A node for combining three booleans into a boolean three-tuple'''
  850. def __init__(self, signature, base_tree):
  851. self.base_tree=base_tree
  852. self.executed = False
  853. self.signature = signature
  854. self.inputs = {
  855. "X" : NodeSocket(is_input = True, name = "X", node = self),
  856. "Y" : NodeSocket(is_input = True, name = "Y", node = self),
  857. "Z" : NodeSocket(is_input = True, name = "Z", node = self),
  858. }
  859. self.outputs = {
  860. "Three-Bool" : NodeSocket(name = "Three-Bool", node=self),
  861. }
  862. self.parameters = {
  863. "X":None,
  864. "Y":None,
  865. "Z":None, }
  866. self.node_type = "UTILITY"
  867. self.hierarchy_connections = []
  868. self.connections = []
  869. self.hierarchy_dependencies = []
  870. self.dependencies = []
  871. self.prepared = False
  872. self.executed = False
  873. def bPrepare(self, bContext = None,):
  874. #prPurple("Executing CombineThreeBool Node")
  875. #prepare_parameters(self)
  876. self.parameters["Three-Bool"] = (
  877. self.evaluate_input("X"),
  878. self.evaluate_input("Y"),
  879. self.evaluate_input("Z"), )
  880. # DO:
  881. # figure out how to get the driver at execute-time
  882. # because Blender allows circular dependencies in drivers
  883. # (sort of), I need to adopt a more convoluted way of doing
  884. # things here or elsewhere.
  885. self.prepared = True
  886. self.executed = True
  887. # Note this is a copy of the above. This needs to be de-duplicated into
  888. # a simpler CombineVector node_container.
  889. # TODO
  890. class UtilityCombineVector:
  891. '''A node for combining three floats into a vector'''
  892. def __init__(self, signature, base_tree):
  893. self.base_tree=base_tree
  894. self.executed = False
  895. self.signature = signature
  896. self.inputs = {
  897. "X" : NodeSocket(is_input = True, name = "X", node = self),
  898. "Y" : NodeSocket(is_input = True, name = "Y", node = self),
  899. "Z" : NodeSocket(is_input = True, name = "Z", node = self),
  900. }
  901. self.outputs = {
  902. "Vector" : NodeSocket(name = "Vector", node=self),
  903. }
  904. self.parameters = {
  905. "X":None,
  906. "Y":None,
  907. "Z":None, }
  908. self.node_type = "UTILITY"
  909. self.hierarchy_connections = []
  910. self.connections = []
  911. self.hierarchy_dependencies = []
  912. self.dependencies = []
  913. self.prepared = False
  914. self.executed = False
  915. def bPrepare(self, bContext = None,):
  916. #prPurple("Executing CombineVector Node")
  917. prepare_parameters(self)
  918. self.parameters["Vector"] = (
  919. self.evaluate_input("X"),
  920. self.evaluate_input("Y"),
  921. self.evaluate_input("Z"), )
  922. self.prepared = True
  923. self.executed = True
  924. class UtilityCatStrings:
  925. '''A node representing an armature object'''
  926. def __init__(self, signature, base_tree):
  927. self.base_tree=base_tree
  928. self.executed = False
  929. self.signature = signature
  930. self.inputs = {
  931. "String_1" : NodeSocket(is_input = True, name = "String_1", node = self),
  932. "String_2" : NodeSocket(is_input = True, name = "String_2", node = self),
  933. }
  934. self.outputs = {
  935. "OutputString" : NodeSocket(name = "OutputString", node=self),
  936. }
  937. self.parameters = {
  938. "String_1":None,
  939. "String_2":None,
  940. }
  941. self.node_type = "UTILITY"
  942. self.hierarchy_connections = []
  943. self.connections = []
  944. self.hierarchy_dependencies = []
  945. self.dependencies = []
  946. self.prepared = False
  947. self.executed = False
  948. def bPrepare(self, bContext = None,):
  949. self.parameters["OutputString"] = self.evaluate_input("String_1")+self.evaluate_input("String_2")
  950. self.prepared = True
  951. self.executed = True
  952. class InputLayerMask:
  953. '''A node representing an armature object'''
  954. def __init__(self, signature, base_tree):
  955. self.base_tree=base_tree
  956. self.executed = False
  957. self.signature = signature
  958. self.inputs = {
  959. }
  960. self.outputs = {
  961. "Layer Mask" : NodeSocket(is_input = True, name = "Layer Mask", node = self),
  962. }
  963. self.parameters = {
  964. "Layer Mask":None,
  965. }
  966. self.node_type = "UTILITY"
  967. self.hierarchy_connections = []
  968. self.connections = []
  969. self.hierarchy_dependencies = []
  970. self.dependencies = []
  971. self.prepared = True
  972. self.executed = True
  973. class InputExistingGeometryObject:
  974. '''A node representing an existing object'''
  975. def __init__(self, signature, base_tree):
  976. self.base_tree=base_tree
  977. self.executed = False
  978. self.signature = signature
  979. self.inputs = {
  980. "Name" : NodeSocket(is_input = True, name = "Name", node = self),
  981. }
  982. self.outputs = {
  983. "Object" : NodeSocket(is_input = False, name = "Object", node=self),
  984. }
  985. self.parameters = {
  986. "Name":None,
  987. "Object":None,
  988. }
  989. self.node_type = "XFORM"
  990. self.hierarchy_connections = []
  991. self.connections = []
  992. self.hierarchy_dependencies = []
  993. self.dependencies = []
  994. self.prepared = False
  995. self.executed = False
  996. def bPrepare(self, bContext=None):
  997. from bpy import data
  998. name = self.evaluate_input("Name")
  999. if name:
  1000. self.bObject = data.objects.get( name )
  1001. else:
  1002. self.bObject = None
  1003. if self is None and (name := self.evaluate_input("Name")):
  1004. prRed(f"No object found with name {name} in {self}")
  1005. self.prepared = True; self.executed = True
  1006. def bGetObject(self, mode=''):
  1007. return self.bObject
  1008. class InputExistingGeometryData:
  1009. '''A node representing existing object data'''
  1010. def __init__(self, signature, base_tree):
  1011. self.base_tree=base_tree
  1012. self.executed = False
  1013. self.signature = signature
  1014. self.inputs = {
  1015. "Name" : NodeSocket(is_input = True, name = "Name", node = self),
  1016. }
  1017. self.outputs = {
  1018. "Geometry" : NodeSocket(is_input = False, name = "Geometry", node=self),
  1019. }
  1020. self.parameters = {
  1021. "Name":None,
  1022. "Geometry":None,
  1023. }
  1024. self.node_type = "UTILITY"
  1025. self.hierarchy_connections = []
  1026. self.connections = []
  1027. self.hierarchy_dependencies = []
  1028. self.dependencies = []
  1029. self.prepared = True
  1030. self.executed = True
  1031. # mode for interface consistency
  1032. def bGetObject(self, mode=''):
  1033. from bpy import data
  1034. # first try Curve, then try Mesh
  1035. bObject = data.curves.get(self.evaluate_input("Name"))
  1036. if not bObject:
  1037. bObject = data.meshes.get(self.evaluate_input("Name"))
  1038. if bObject is None:
  1039. raise RuntimeError(f"Could not find a mesh or curve datablock named \"{self.evaluate_input('Name')}\" for node {self}")
  1040. return bObject
  1041. class UtilityGetBoneLength:
  1042. '''A node to get the length of a bone matrix'''
  1043. def __init__(self, signature, base_tree):
  1044. self.base_tree=base_tree
  1045. self.executed = False
  1046. self.signature = signature
  1047. self.inputs = {
  1048. "Bone Matrix" : NodeSocket(is_input = True, name = "Bone Matrix", node = self),
  1049. }
  1050. self.outputs = {
  1051. "Bone Length" : NodeSocket(name = "Bone Length", node = self),
  1052. }
  1053. self.parameters = {
  1054. "Bone Length":None,
  1055. }
  1056. self.node_type = "UTILITY"
  1057. self.hierarchy_connections = []
  1058. self.connections = []
  1059. self.hierarchy_dependencies = []
  1060. self.dependencies = []
  1061. self.prepared = False
  1062. self.executed = False
  1063. def bPrepare(self, bContext = None,):
  1064. # print (self.inputs['Bone Matrix'].links)
  1065. if l := self.evaluate_input("Bone Matrix"):
  1066. self.parameters["Bone Length"] = l[3][3]
  1067. self.prepared = True
  1068. self.executed = True
  1069. class UtilityPointFromBoneMatrix:
  1070. '''A node representing an armature object'''
  1071. def __init__(self, signature, base_tree):
  1072. self.base_tree=base_tree
  1073. self.executed = False
  1074. self.signature = signature
  1075. self.inputs = {
  1076. "Bone Matrix" : NodeSocket(is_input = True, name = "Bone Matrix", node = self),
  1077. "Head/Tail" : NodeSocket(is_input = True, name = "Head/Tail", node = self),
  1078. }
  1079. self.outputs = {
  1080. "Point" : NodeSocket(name = "Point", node = self),
  1081. }
  1082. self.parameters = {
  1083. "Bone Matrix":None,
  1084. "Head/Tail":None,
  1085. "Point":None,
  1086. }
  1087. self.node_type = "UTILITY"
  1088. self.hierarchy_connections = []
  1089. self.connections = []
  1090. self.hierarchy_dependencies = []
  1091. self.dependencies = []
  1092. self.prepared = False
  1093. self.executed = False
  1094. # TODO: find out why this is sometimes not ready at bPrepare phase
  1095. def bPrepare(self, bContext = None,):
  1096. from mathutils import Vector
  1097. matrix = self.evaluate_input("Bone Matrix")
  1098. head, rotation, _scale = matrix.copy().decompose()
  1099. tail = head.copy() + (rotation @ Vector((0,1,0)))*matrix[3][3]
  1100. self.parameters["Point"] = head.lerp(tail, self.evaluate_input("Head/Tail"))
  1101. self.prepared = True
  1102. self.executed = True
  1103. class UtilitySetBoneLength:
  1104. '''Sets the length of a Bone's matrix'''
  1105. def __init__(self, signature, base_tree):
  1106. self.base_tree=base_tree
  1107. self.executed = False
  1108. self.signature = signature
  1109. self.inputs = {
  1110. "Bone Matrix" : NodeSocket(is_input = True, name = "Bone Matrix", node = self),
  1111. "Length" : NodeSocket(is_input = True, name = "Length", node = self),
  1112. }
  1113. self.outputs = {
  1114. "Bone Matrix" : NodeSocket(name = "Bone Matrix", node = self),
  1115. }
  1116. self.parameters = {
  1117. "Bone Matrix":None,
  1118. "Length":None,
  1119. }
  1120. self.node_type = "UTILITY"
  1121. self.hierarchy_connections = []
  1122. self.connections = []
  1123. self.hierarchy_dependencies = []
  1124. self.dependencies = []
  1125. self.prepared = False
  1126. self.executed = False
  1127. def bPrepare(self, bContext = None,):
  1128. from mathutils import Vector
  1129. if matrix := self.evaluate_input("Bone Matrix"):
  1130. matrix = matrix.copy()
  1131. # print (self.inputs["Length"].links)
  1132. matrix[3][3] = self.evaluate_input("Length")
  1133. self.parameters["Length"] = self.evaluate_input("Length")
  1134. self.parameters["Bone Matrix"] = matrix
  1135. self.prepared = True
  1136. self.executed = True
  1137. class UtilityMatrixSetLocation:
  1138. '''Sets the location of a matrix'''
  1139. def __init__(self, signature, base_tree):
  1140. self.base_tree=base_tree
  1141. self.executed = False
  1142. self.signature = signature
  1143. self.inputs = {
  1144. "Matrix" : NodeSocket(is_input = True, name = "Matrix", node = self),
  1145. "Location" : NodeSocket(is_input = True, name = "Location", node = self),
  1146. }
  1147. self.outputs = {
  1148. "Matrix" : NodeSocket(name = "Matrix", node = self),
  1149. }
  1150. self.parameters = {
  1151. "Matrix" : None,
  1152. "Location" : None,
  1153. }
  1154. self.node_type = "UTILITY"
  1155. self.connections, self.hierarchy_connections = [], []
  1156. self.dependencies, self.hierarchy_dependencies = [], []
  1157. self.prepared, self.executed = False,False
  1158. def bPrepare(self, bContext = None,):
  1159. from mathutils import Vector
  1160. if matrix := self.evaluate_input("Matrix"):
  1161. matrix = matrix.copy()
  1162. # print (self.inputs["Length"].links)
  1163. loc = self.evaluate_input("Location")
  1164. matrix[0][3] = loc[0]; matrix[1][3] = loc[1]; matrix[2][3] = loc[2]
  1165. self.parameters["Matrix"] = matrix
  1166. self.prepared = True
  1167. self.executed = True
  1168. class UtilityMatrixGetLocation:
  1169. '''Gets the location of a matrix'''
  1170. def __init__(self, signature, base_tree):
  1171. self.base_tree=base_tree
  1172. self.signature = signature
  1173. self.inputs = {
  1174. "Matrix" : NodeSocket(is_input = True, name = "Matrix", node = self),
  1175. }
  1176. self.outputs = {
  1177. "Location" : NodeSocket(name = "Location", node = self),
  1178. }
  1179. self.parameters = {
  1180. "Matrix" : None,
  1181. "Location" : None,
  1182. }
  1183. self.node_type = "UTILITY"
  1184. self.connections, self.hierarchy_connections = [], []
  1185. self.dependencies, self.hierarchy_dependencies = [], []
  1186. self.prepared, self.executed = False,False
  1187. def bPrepare(self, bContext = None,):
  1188. from mathutils import Vector
  1189. if matrix := self.evaluate_input("Matrix"):
  1190. self.parameters["Location"] = matrix.to_translation()
  1191. self.prepared = True; self.executed = True
  1192. class UtilityMatrixFromXForm:
  1193. """Returns the matrix of the given xForm node."""
  1194. def __init__(self, signature, base_tree):
  1195. self.base_tree=base_tree
  1196. self.signature = signature
  1197. self.inputs = {
  1198. "xForm" : NodeSocket(is_input = True, name = "xForm", node = self),
  1199. }
  1200. self.outputs = {
  1201. "Matrix" : NodeSocket(name = "Matrix", node = self),
  1202. }
  1203. self.parameters = {
  1204. "Matrix" : None,
  1205. }
  1206. self.node_type = "UTILITY"
  1207. self.connections, self.hierarchy_connections = [], []
  1208. self.dependencies, self.hierarchy_dependencies = [], []
  1209. self.prepared, self.executed = False,False
  1210. def GetxForm(self):
  1211. trace = trace_single_line(self, "xForm")
  1212. for node in trace[0]:
  1213. if (node.node_type == 'XFORM'):
  1214. return node
  1215. raise GraphError("%s is not connected to an xForm" % self)
  1216. def bPrepare(self, bContext = None,):
  1217. from mathutils import Vector, Matrix
  1218. self.parameters["Matrix"] = Matrix.Identity(4)
  1219. if matrix := self.GetxForm().parameters.get("Matrix"):
  1220. self.parameters["Matrix"] = matrix.copy()
  1221. elif hasattr(self.GetxForm().bObject, "matrix"):
  1222. self.parameters["Matrix"] = self.GetxForm().bObject.matrix.copy()
  1223. elif hasattr(self.GetxForm().bObject, "matrix_world"):
  1224. self.parameters["Matrix"] = self.GetxForm().bObject.matrix_world.copy()
  1225. else:
  1226. prRed(f"Could not find matrix for {self} - check if the referenced object exists.")
  1227. self.prepared = True; self.executed = True
  1228. class UtilityAxesFromMatrix:
  1229. """Returns the axes of the given matrix."""
  1230. def __init__(self, signature, base_tree):
  1231. self.base_tree=base_tree
  1232. self.signature = signature
  1233. self.inputs = {
  1234. "Matrix" : NodeSocket(is_input = True, name = "Matrix", node = self),
  1235. }
  1236. self.outputs = {
  1237. "X Axis" : NodeSocket(name = "X Axis", node = self),
  1238. "Y Axis" : NodeSocket(name = "Y Axis", node = self),
  1239. "Z Axis" : NodeSocket(name = "Z Axis", node = self),
  1240. }
  1241. self.parameters = {
  1242. "Matrix" : None,
  1243. "X Axis" : None,
  1244. "Y Axis" : None,
  1245. "Z Axis" : None,
  1246. }
  1247. self.node_type = "UTILITY"
  1248. self.connections, self.hierarchy_connections = [], []
  1249. self.dependencies, self.hierarchy_dependencies = [], []
  1250. self.prepared, self.executed = False,False
  1251. def bPrepare(self, bContext = None,):
  1252. from mathutils import Vector
  1253. if matrix := self.evaluate_input("Matrix"):
  1254. matrix= matrix.copy().to_3x3()
  1255. self.parameters['X Axis'] = matrix @ Vector((1,0,0))
  1256. self.parameters['Y Axis'] = matrix @ Vector((0,1,0))
  1257. self.parameters['Z Axis'] = matrix @ Vector((0,0,1))
  1258. self.prepared = True; self.executed = True
  1259. class UtilityBoneMatrixHeadTailFlip:
  1260. def __init__(self, signature, base_tree):
  1261. self.base_tree=base_tree
  1262. self.executed = False
  1263. self.signature = signature
  1264. self.inputs = {
  1265. "Bone Matrix" : NodeSocket(is_input = True, name = "Bone Matrix", node = self),
  1266. }
  1267. self.outputs = {
  1268. "Bone Matrix" : NodeSocket(name = "Bone Matrix", node = self),
  1269. }
  1270. self.parameters = {
  1271. "Bone Matrix":None,
  1272. }
  1273. self.node_type = "UTILITY"
  1274. self.hierarchy_connections = []
  1275. self.connections = []
  1276. self.hierarchy_dependencies = []
  1277. self.dependencies = []
  1278. self.prepared = False
  1279. self.executed = False
  1280. def bPrepare(self, bContext = None,):
  1281. from mathutils import Vector, Matrix, Quaternion
  1282. from bpy.types import Bone
  1283. if matrix := self.evaluate_input("Bone Matrix"):
  1284. axis, roll = Bone.AxisRollFromMatrix(matrix.to_3x3())
  1285. new_mat = Bone.MatrixFromAxisRoll(-1*axis, roll)
  1286. length = matrix[3][3]
  1287. new_mat.resize_4x4() # last column contains
  1288. new_mat[0][3] = matrix[0][3] + axis[0]*length # x location
  1289. new_mat[1][3] = matrix[1][3] + axis[1]*length # y location
  1290. new_mat[2][3] = matrix[2][3] + axis[2]*length # z location
  1291. new_mat[3][3] = length # length
  1292. self.parameters["Bone Matrix"] = new_mat
  1293. self.prepared, self.executed = True, True
  1294. class UtilityMatrixTransform:
  1295. def __init__(self, signature, base_tree):
  1296. self.base_tree=base_tree
  1297. self.executed = False
  1298. self.signature = signature
  1299. self.inputs = {
  1300. "Matrix 1" : NodeSocket(is_input = True, name = "Matrix 1", node = self),
  1301. "Matrix 2" : NodeSocket(is_input = True, name = "Matrix 2", node = self),
  1302. }
  1303. self.outputs = {
  1304. "Out Matrix" : NodeSocket(name = "Out Matrix", node = self),
  1305. }
  1306. self.parameters = {
  1307. "Matrix 1" : None,
  1308. "Matrix 2" : None,
  1309. "Out Matrix" : None,
  1310. }
  1311. self.node_type = "UTILITY"
  1312. self.hierarchy_connections = []
  1313. self.connections = []
  1314. self.hierarchy_dependencies = []
  1315. self.dependencies = []
  1316. self.prepared = False
  1317. self.executed = False
  1318. def bPrepare(self, bContext = None,):
  1319. from mathutils import Vector
  1320. mat1 = self.evaluate_input("Matrix 1"); mat2 = self.evaluate_input("Matrix 2")
  1321. if mat1 and mat2:
  1322. mat1copy = mat1.copy()
  1323. self.parameters["Out Matrix"] = mat2 @ mat1copy
  1324. self.parameters["Out Matrix"].translation = mat1copy.to_translation()+ mat2.to_translation()
  1325. else:
  1326. raise RuntimeError(wrapRed(f"Node {self} did not receive all matrix inputs... found input 1? {mat1 is not None}, 2? {mat2 is not None}"))
  1327. self.prepared = True
  1328. self.executed = True
  1329. class UtilityTransformationMatrix:
  1330. def __init__(self, signature, base_tree):
  1331. self.base_tree=base_tree
  1332. self.executed = False
  1333. self.signature = signature
  1334. self.inputs = {
  1335. "Operation" : NodeSocket(is_input = True, name = "Operation", node = self),
  1336. "Vector" : NodeSocket(is_input = True, name = "Vector", node = self),
  1337. "W" : NodeSocket(is_input = True, name = "W", node = self),
  1338. }
  1339. self.outputs = {
  1340. "Matrix" : NodeSocket(name = "Matrix", node = self),
  1341. }
  1342. self.parameters = {
  1343. "Operation" : None,
  1344. "Origin" : None,
  1345. "Vector" : None,
  1346. "W" : None,
  1347. "Matrix" : None,
  1348. }
  1349. self.node_type = "UTILITY"
  1350. self.hierarchy_connections = []
  1351. self.connections = []
  1352. self.hierarchy_dependencies = []
  1353. self.dependencies = []
  1354. self.prepared = False
  1355. self.executed = False
  1356. def bPrepare(self, bContext = None,):
  1357. from mathutils import Matrix, Vector
  1358. if (operation := self.evaluate_input("Operation")) == 'ROTATE_AXIS_ANGLE':
  1359. # this can, will, and should fail if the axis is 0,0,0
  1360. self.parameters["Matrix"] = rotMat = Matrix.Rotation(self.evaluate_input("W"), 4, Vector(self.evaluate_input("Vector")).normalized())
  1361. elif (operation := self.evaluate_input("Operation")) == 'TRANSLATE':
  1362. m = Matrix.Identity(4)
  1363. if axis := self.evaluate_input("Vector"):
  1364. m[0][3]=axis[0];m[1][3]=axis[1];m[2][3]=axis[2]
  1365. self.parameters['Matrix'] = m
  1366. elif (operation := self.evaluate_input("Operation")) == 'SCALE':
  1367. self.parameters["Matrix"] = Matrix.Scale(self.evaluate_input("W"), 4, Vector(self.evaluate_input("Vector")).normalized())
  1368. else:
  1369. raise NotImplementedError(self.evaluate_input("Operation").__repr__()+ " Operation not yet implemented.")
  1370. self.prepared = True
  1371. self.executed = True
  1372. class UtilityIntToString:
  1373. def __init__(self, signature, base_tree):
  1374. self.base_tree=base_tree
  1375. self.executed = False
  1376. self.signature = signature
  1377. self.inputs = {
  1378. "Number" : NodeSocket(is_input = True, name = "Number", node = self),
  1379. "Zero Padding" : NodeSocket(is_input = True, name = "Zero Padding", node = self),
  1380. }
  1381. self.outputs = {
  1382. "String" : NodeSocket(name = "String", node = self),
  1383. }
  1384. self.parameters = {
  1385. "Number" : None,
  1386. "Zero Padding" : None,
  1387. "String" : None,
  1388. }
  1389. self.node_type = "UTILITY"
  1390. self.hierarchy_connections = []
  1391. self.connections = []
  1392. self.hierarchy_dependencies = []
  1393. self.dependencies = []
  1394. self.prepared = False
  1395. self.executed = False
  1396. def bPrepare(self, bContext = None,):
  1397. number = self.evaluate_input("Number")
  1398. zeroes = self.evaluate_input("Zero Padding")
  1399. # I'm casting to int because I want to support any number, even though the node asks for int.
  1400. self.parameters["String"] = str(int(number)).zfill(int(zeroes))
  1401. self.prepared = True
  1402. self.executed = True
  1403. class UtilityArrayGet:
  1404. def __init__(self, signature, base_tree):
  1405. self.base_tree=base_tree
  1406. self.executed = False
  1407. self.signature = signature
  1408. self.inputs = {
  1409. "Index" : NodeSocket(is_input = True, name = "Index", node = self),
  1410. "OoB Behaviour" : NodeSocket(is_input = True, name = "OoB Behaviour", node = self),
  1411. "Array" : NodeSocket(is_input = True, name = "Array", node = self),
  1412. }
  1413. self.outputs = {
  1414. "Output" : NodeSocket(name = "Output", node = self),
  1415. }
  1416. self.parameters = {
  1417. "Index" : None,
  1418. "OoB Behaviour" : None,
  1419. "Array" : None,
  1420. "Output" : None,
  1421. }
  1422. self.node_type = "UTILITY"
  1423. self.hierarchy_connections = []
  1424. self.connections = []
  1425. self.hierarchy_dependencies = []
  1426. self.dependencies = []
  1427. self.prepared = False
  1428. self.executed = False
  1429. def bPrepare(self, bContext = None,):
  1430. if self.prepared == False:
  1431. # sort the array entries
  1432. for inp in self.inputs.values():
  1433. inp.links.sort(key=lambda a : -a.multi_input_sort_id)
  1434. oob = self.evaluate_input("OoB Behaviour")
  1435. index = self.evaluate_input("Index")
  1436. from .utilities import cap, wrap
  1437. # we must assume that the array has sent the correct number of links
  1438. if oob == 'WRAP':
  1439. index = index % len(self.inputs['Array'].links)
  1440. if oob == 'HOLD':
  1441. index = cap(index, len(self.inputs['Array'].links)-1)
  1442. # relink the connections and then kill all the links to and from the array
  1443. from .utilities import init_connections, init_dependencies
  1444. l = self.inputs["Array"].links[index]
  1445. for link in self.outputs["Output"].links:
  1446. to_node = link.to_node
  1447. l.from_node.outputs[l.from_socket].connect(to_node, link.to_socket)
  1448. link.die()
  1449. init_dependencies(to_node)
  1450. from_node=l.from_node
  1451. for inp in self.inputs.values():
  1452. for l in inp.links:
  1453. l.die()
  1454. init_connections(from_node)
  1455. if self in from_node.hierarchy_connections:
  1456. raise RuntimeError()
  1457. # this is intentional because the Array Get is kind of a weird hybrid between a Utility and a Schema
  1458. # so it should be removed from the tree when it is done. it has already dealt with the actual links.
  1459. # however I think this is redundant. Check.
  1460. self.hierarchy_connections, self.connections = [], []
  1461. self.hierarchy_dependencies, self.dependencies = [], []
  1462. self.prepared = True
  1463. self.executed = True
  1464. class UtilitySetBoneMatrixTail:
  1465. def __init__(self, signature, base_tree):
  1466. self.base_tree=base_tree
  1467. self.executed = False
  1468. self.signature = signature
  1469. self.inputs = {
  1470. "Matrix" : NodeSocket(is_input = True, name = "Matrix", node = self),
  1471. "Tail Location" : NodeSocket(is_input = True, name = "Tail Location", node = self),
  1472. }
  1473. self.outputs = {
  1474. "Result" : NodeSocket(name = "Result", node = self),
  1475. }
  1476. self.parameters = {
  1477. "Matrix" : None,
  1478. "Tail Location" : None,
  1479. "Result" : None,
  1480. }
  1481. self.node_type = "UTILITY"
  1482. self.hierarchy_connections, self.connections = [], []
  1483. self.hierarchy_dependencies, self.dependencies = [], []
  1484. self.prepared, self.executed = False, False
  1485. def bPrepare(self, bContext = None,):
  1486. from mathutils import Matrix
  1487. matrix = self.evaluate_input("Matrix")
  1488. if matrix is None: matrix = Matrix.Identity(4)
  1489. #just do this for now lol
  1490. self.parameters["Result"] = matrix_from_head_tail(matrix.translation, self.evaluate_input("Tail Location"))
  1491. class UtilityPrint:
  1492. def __init__(self, signature, base_tree):
  1493. self.base_tree=base_tree
  1494. self.executed = False
  1495. self.signature = signature
  1496. self.inputs = {
  1497. "Input" : NodeSocket(is_input = True, name = "Input", node = self),
  1498. }
  1499. self.outputs = {}
  1500. self.parameters = {
  1501. "Input" : None,
  1502. }
  1503. self.node_type = "UTILITY"
  1504. self.hierarchy_connections = []
  1505. self.connections = []
  1506. self.hierarchy_dependencies = []
  1507. self.dependencies = []
  1508. self.prepared = False
  1509. self.executed = False
  1510. def bPrepare(self, bContext = None,):
  1511. if my_input := self.evaluate_input("Input"):
  1512. print("Preparation phase: ", wrapWhite(self), wrapGreen(my_input))
  1513. # else:
  1514. # prRed("No input to print.")
  1515. self.prepared = True
  1516. def bExecute(self, bContext = None,):
  1517. if my_input := self.evaluate_input("Input"):
  1518. print("Execution phase: ", wrapWhite(self), wrapGreen(my_input))
  1519. # else:
  1520. # prRed("No input to print.")
  1521. self.executed = True
  1522. class UtilityCompare:
  1523. def __init__(self, signature, base_tree):
  1524. self.base_tree=base_tree
  1525. self.executed = False
  1526. self.signature = signature
  1527. self.inputs = {
  1528. "A" : NodeSocket(is_input = True, name = "A", node = self),
  1529. "B" : NodeSocket(is_input = True, name = "B", node = self),
  1530. }
  1531. self.outputs = {
  1532. "Result" : NodeSocket(name = "Result", node = self),
  1533. }
  1534. self.parameters = {
  1535. "A" : None,
  1536. "B" : None,
  1537. "Result" : None,
  1538. }
  1539. self.node_type = "UTILITY"
  1540. self.hierarchy_connections, self.connections = [], []
  1541. self.hierarchy_dependencies, self.dependencies = [], []
  1542. self.prepared, self.executed = False, False
  1543. def bPrepare(self, bContext = None,):
  1544. self.parameters["Result"] = self.evaluate_input("A") == self.evaluate_input("B")
  1545. self.prepared = True; self.executed = True
  1546. class UtilityChoose:
  1547. def __init__(self, signature, base_tree):
  1548. self.base_tree=base_tree
  1549. self.executed = False
  1550. self.signature = signature
  1551. self.inputs = {
  1552. "Condition" : NodeSocket(is_input = True, name = "Condition", node = self),
  1553. "A" : NodeSocket(is_input = True, name = "A", node = self),
  1554. "B" : NodeSocket(is_input = True, name = "B", node = self),
  1555. }
  1556. self.outputs = {
  1557. "Result" : NodeSocket(name = "Result", node = self),
  1558. }
  1559. self.parameters = {
  1560. "Condition" : None,
  1561. "A" : None,
  1562. "B" : None,
  1563. "Result" : None,
  1564. }
  1565. self.node_type = "UTILITY"
  1566. self.hierarchy_connections, self.connections = [], []
  1567. self.hierarchy_dependencies, self.dependencies = [], []
  1568. self.prepared, self.executed = False, False
  1569. def bPrepare(self, bContext = None,):
  1570. condition = self.evaluate_input("Condition")
  1571. if condition:
  1572. self.parameters["Result"] = self.evaluate_input("B")
  1573. else:
  1574. self.parameters["Result"] = self.evaluate_input("A")
  1575. self.prepared = True
  1576. self.executed = True
  1577. for c in TellClasses():
  1578. setup_container(c)