misc_nodes.py 61 KB

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