misc_containers.py 63 KB

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