link_nodes.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. from .utilities.node_common import *
  2. from bpy.types import Bone, NodeTree
  3. from .base_definitions import MantisNode, GraphError, FLOAT_EPSILON
  4. from .link_socket_templates import *
  5. def TellClasses():
  6. return [
  7. # special
  8. LinkInherit,
  9. # copy
  10. LinkCopyLocation,
  11. LinkCopyRotation,
  12. LinkCopyScale,
  13. LinkCopyTransforms,
  14. LinkTransformation,
  15. # limit
  16. LinkLimitLocation,
  17. LinkLimitRotation,
  18. LinkLimitScale,
  19. LinkLimitDistance,
  20. # tracking
  21. LinkStretchTo,
  22. LinkDampedTrack,
  23. LinkLockedTrack,
  24. LinkTrackTo,
  25. #misc
  26. LinkInheritConstraint,
  27. LinkArmature,
  28. # IK
  29. LinkInverseKinematics,
  30. LinkSplineIK,
  31. # stuff that snaps or limits a bone
  32. LinkFloor,
  33. LinkShrinkWrap,
  34. LinkGeometryAttribute,
  35. # Drivers
  36. LinkDrivenParameter,
  37. ]
  38. # set the name if it is available, otherwise just use the constraint's nice name
  39. set_constraint_name = lambda mantis_node : mantis_node.evaluate_input("Name") if mantis_node.evaluate_input("Name") else mantis_node.__class__.__name__
  40. class MantisLinkNode(MantisNode):
  41. __slots__ = ()
  42. node_type = 'LINK'
  43. def __init__(self, signature : tuple,
  44. base_tree : NodeTree,):
  45. super().__init__(signature, base_tree, self.socket_templates,
  46. additional_parameters={'Name':None})
  47. self.prepared = True; self.bObject=[]
  48. def bTransformPass(self, bContext=None):
  49. parent_xForm_info = get_parent_xForm_info(self, 'Input Relationship')
  50. self.parameters['Output Relationship'] = parent_xForm_info
  51. def evaluate_input(self, input_name, index=0):
  52. # should catch 'Target', 'Pole Target' and ArmatureConstraint targets, too
  53. if ('Target' in input_name) and input_name not in ["Target Space", "Use Target Z"]:
  54. socket = self.inputs.get(input_name)
  55. if socket.is_linked:
  56. node_line, _last_socket = trace_single_line(self, input_name, index)
  57. for other_node in node_line:
  58. if other_node.node_type == 'XFORM':
  59. return other_node
  60. return None
  61. else:
  62. return super().evaluate_input(input_name, index)
  63. def gen_property_socket_map(self) -> dict:
  64. props_sockets = super().gen_property_socket_map()
  65. if (os := self.inputs.get("Owner Space")) and os.is_connected:
  66. node_line, _last_socket = trace_single_line(self, "Owner Space")
  67. for other_node in node_line:
  68. if other_node.node_type == 'XFORM':
  69. del props_sockets['owner_space']; break
  70. if (ts := self.inputs.get("Target Space")) and ts.is_connected:
  71. node_line, _last_socket = trace_single_line(self, "Target Space")
  72. for other_node in node_line:
  73. if other_node.node_type == 'XFORM':
  74. del props_sockets['target_space']; break
  75. return props_sockets
  76. def set_custom_space(self):
  77. for c in self.bObject:
  78. if (os := self.inputs.get("Owner Space")) and os.is_connected:
  79. node_line, _last_socket = trace_single_line(self, "Owner Space")
  80. owner_space_target = None
  81. for other_node in node_line:
  82. if other_node.node_type == 'XFORM':
  83. owner_space_target = other_node; break
  84. if owner_space_target:
  85. c.owner_space='CUSTOM'
  86. xf = owner_space_target.bGetObject(mode="OBJECT")
  87. if isinstance(xf, Bone):
  88. c.space_object=owner_space_target.bGetParentArmature(); c.space_subtarget=xf.name
  89. else:
  90. c.space_object=xf
  91. if (ts := self.inputs.get("Target Space")) and ts.is_connected:
  92. node_line, _last_socket = trace_single_line(self, "Target Space")
  93. target_space_target = None
  94. for other_node in node_line:
  95. if other_node.node_type == 'XFORM':
  96. target_space_target = other_node; break
  97. if target_space_target:
  98. c.target_space='CUSTOM'
  99. xf = target_space_target.bGetObject(mode="OBJECT")
  100. if isinstance(xf, Bone):
  101. c.space_object=target_space_target.bGetParentArmature(); c.space_subtarget=xf.name
  102. else:
  103. c.space_object=xf
  104. def GetxForm(mantis_node, output_name="Output Relationship"):
  105. break_condition= lambda node : node.node_type=='XFORM'
  106. xforms = trace_line_up_branching(mantis_node, output_name, break_condition)
  107. return_me=[]
  108. for xf in xforms:
  109. if xf.node_type != 'XFORM':
  110. continue
  111. if xf in return_me:
  112. continue
  113. return_me.append(xf)
  114. return return_me
  115. def reset_execution(self):
  116. super().reset_execution()
  117. self.prepared = True; self.bObject = []
  118. def bFinalize(self, bContext=None):
  119. finish_drivers(self)
  120. #*#-------------------------------#++#-------------------------------#*#
  121. # L I N K N O D E S
  122. #*#-------------------------------#++#-------------------------------#*#
  123. class LinkInherit(MantisLinkNode):
  124. '''A node representing inheritance'''
  125. __slots__ = ()
  126. socket_templates = LinkInheritSockets
  127. def bTransformPass(self, bContext=None):
  128. parent_xForm_info = get_parent_xForm_info(self, 'Parent')
  129. self.parameters['Inheritance'] = parent_xForm_info
  130. self.executed = True
  131. def GetxForm(self):
  132. # I think this is only run in display update.
  133. trace = trace_single_line_up(self, "Inheritance")
  134. for node in trace[0]:
  135. if (node.node_type == 'XFORM'):
  136. return node
  137. raise GraphError("%s is not connected to a downstream xForm" % self)
  138. class LinkCopyLocation(MantisLinkNode):
  139. '''A node representing Copy Location'''
  140. __slots__ = ()
  141. socket_templates = LinkCopyLocationSockets
  142. def bRelationshipPass(self, context):
  143. prepare_parameters(self)
  144. for xf in self.GetxForm():
  145. c = xf.bGetObject().constraints.new('COPY_LOCATION')
  146. self.get_target_and_subtarget(c)
  147. print(wrapGreen("Creating ")+wrapWhite("Copy Location")+
  148. wrapGreen(" Constraint for bone: ") +
  149. wrapOrange(xf.bGetObject().name))
  150. if constraint_name := self.evaluate_input("Name"):
  151. c.name = constraint_name
  152. self.bObject.append(c)
  153. self.set_custom_space()
  154. props_sockets = self.gen_property_socket_map()
  155. evaluate_sockets(self, c, props_sockets)
  156. self.executed = True
  157. class LinkCopyRotation(MantisLinkNode):
  158. '''A node representing Copy Rotation'''
  159. __slots__ = ()
  160. socket_templates = LinkCopyRotationSockets
  161. def bRelationshipPass(self, context):
  162. prepare_parameters(self)
  163. for xf in self.GetxForm():
  164. c = xf.bGetObject().constraints.new('COPY_ROTATION')
  165. self.get_target_and_subtarget(c)
  166. print(wrapGreen("Creating ")+wrapWhite("Copy Rotation")+
  167. wrapGreen(" Constraint for bone: ") +
  168. wrapOrange(xf.bGetObject().name))
  169. rotation_order = self.evaluate_input("RotationOrder")
  170. if ((rotation_order == 'QUATERNION') or (rotation_order == 'AXIS_ANGLE')):
  171. c.euler_order = 'AUTO'
  172. else:
  173. try:
  174. c.euler_order = rotation_order
  175. except TypeError: # it's a driver or incorrect
  176. c.euler_order = 'AUTO'
  177. if constraint_name := self.evaluate_input("Name"):
  178. c.name = constraint_name
  179. self.bObject.append(c)
  180. self.set_custom_space()
  181. props_sockets = self.gen_property_socket_map()
  182. evaluate_sockets(self, c, props_sockets)
  183. self.executed = True
  184. class LinkCopyScale(MantisLinkNode):
  185. '''A node representing Copy Scale'''
  186. __slots__ = ()
  187. socket_templates = LinkCopyScaleSockets
  188. def bRelationshipPass(self, context):
  189. prepare_parameters(self)
  190. for xf in self.GetxForm():
  191. c = xf.bGetObject().constraints.new('COPY_SCALE')
  192. self.get_target_and_subtarget(c)
  193. print(wrapGreen("Creating ")+wrapWhite("Copy Scale")+
  194. wrapGreen(" Constraint for bone: ") +
  195. wrapOrange(xf.bGetObject().name))
  196. if constraint_name := self.evaluate_input("Name"):
  197. c.name = constraint_name
  198. self.bObject.append(c)
  199. self.set_custom_space()
  200. props_sockets = self.gen_property_socket_map()
  201. evaluate_sockets(self, c, props_sockets)
  202. self.executed = True
  203. class LinkCopyTransforms(MantisLinkNode):
  204. '''A node representing Copy Transfoms'''
  205. __slots__ = ()
  206. socket_templates = LinkCopyTransformsSockets
  207. def bRelationshipPass(self, context):
  208. prepare_parameters(self)
  209. for xf in self.GetxForm():
  210. c = xf.bGetObject().constraints.new('COPY_TRANSFORMS')
  211. self.get_target_and_subtarget(c)
  212. print(wrapGreen("Creating ")+wrapWhite("Copy Transforms")+
  213. wrapGreen(" Constraint for bone: ") +
  214. wrapOrange(xf.bGetObject().name))
  215. if constraint_name := self.evaluate_input("Name"):
  216. c.name = constraint_name
  217. self.bObject.append(c)
  218. self.set_custom_space()
  219. props_sockets = self.gen_property_socket_map()
  220. evaluate_sockets(self, c, props_sockets)
  221. self.executed = True
  222. class LinkTransformation(MantisLinkNode):
  223. '''A node representing Copy Transfoms'''
  224. __slots__ = ()
  225. socket_templates = LinkTransformationSockets
  226. def ui_modify_socket(self, ui_socket, socket_name=None):
  227. from_suffix, to_suffix = '', ''
  228. if self.evaluate_input("Map From") == 'ROTATION': from_suffix='_rot'
  229. elif self.evaluate_input("Map From") == 'SCALE': from_suffix='_scale'
  230. if self.evaluate_input("Map To") == 'ROTATION': to_suffix='_rot'
  231. elif self.evaluate_input("Map To") == 'SCALE': to_suffix='_scale'
  232. if ('To' in ui_socket.name or 'From' in ui_socket.name) and (from_suffix or to_suffix):
  233. for s_temp in self.socket_templates:
  234. if s_temp.name == ui_socket.name: break
  235. if 'from' in s_temp.blender_property:
  236. socket_name=s_temp.blender_property+from_suffix
  237. else:
  238. socket_name=s_temp.blender_property+to_suffix
  239. return self.update_socket_value(socket_name, ui_socket.default_value)
  240. return super().ui_modify_socket(ui_socket, socket_name)
  241. def bRelationshipPass(self, context):
  242. prepare_parameters(self)
  243. for xf in self.GetxForm():
  244. c = xf.bGetObject().constraints.new('TRANSFORM')
  245. self.get_target_and_subtarget(c)
  246. print(wrapGreen("Creating ")+wrapWhite("Transformation")+
  247. wrapGreen(" Constraint for bone: ") +
  248. wrapOrange(xf.bGetObject().name))
  249. if constraint_name := self.evaluate_input("Name"):
  250. c.name = constraint_name
  251. self.bObject.append(c)
  252. self.set_custom_space()
  253. props_sockets = self.gen_property_socket_map()
  254. # we have to fix the blender-property for scale/rotation
  255. # because Blender stores these separately.
  256. # I do not care that this code is ugly.
  257. from_suffix, to_replace = '', ''
  258. if self.evaluate_input("Map From") == 'ROTATION':
  259. from_suffix='_rot'
  260. elif self.evaluate_input("Map From") == 'SCALE':
  261. from_suffix='_scale'
  262. if self.evaluate_input("Map To") == 'ROTATION':
  263. to_replace='_rot'
  264. elif self.evaluate_input("Map To") == 'SCALE':
  265. to_replace='_scale'
  266. if from_suffix:
  267. for axis in ['x', 'y', 'z']:
  268. stub='from_min_'+axis
  269. props_sockets[stub+from_suffix]=props_sockets[stub]
  270. props_sockets[stub+from_suffix].property_name = stub+from_suffix
  271. del props_sockets[stub]
  272. stub='from_max_'+axis
  273. props_sockets[stub+from_suffix]=props_sockets[stub]
  274. props_sockets[stub+from_suffix].property_name = stub+from_suffix
  275. del props_sockets[stub]
  276. if to_replace:
  277. for axis in ['x', 'y', 'z']:
  278. stub='to_min_'+axis
  279. props_sockets[stub+to_replace]=props_sockets[stub]
  280. props_sockets[stub+from_suffix].property_name = stub+to_replace
  281. del props_sockets[stub]
  282. stub='to_max_'+axis
  283. props_sockets[stub+to_replace]=props_sockets[stub]
  284. props_sockets[stub+from_suffix].property_name = stub+to_replace
  285. del props_sockets[stub]
  286. evaluate_sockets(self, c, props_sockets)
  287. self.executed = True
  288. class LinkLimitLocation(MantisLinkNode):
  289. __slots__ = ()
  290. socket_templates = LinkLimitLocationSockets
  291. def bRelationshipPass(self, context):
  292. prepare_parameters(self)
  293. for xf in self.GetxForm():
  294. c = xf.bGetObject().constraints.new('LIMIT_LOCATION')
  295. print(wrapGreen("Creating ")+wrapWhite("Limit Location")+
  296. wrapGreen(" Constraint for bone: ") +
  297. wrapOrange(xf.bGetObject().name))
  298. if constraint_name := self.evaluate_input("Name"):
  299. c.name = constraint_name
  300. self.bObject.append(c)
  301. self.set_custom_space()
  302. props_sockets = self.gen_property_socket_map()
  303. evaluate_sockets(self, c, props_sockets)
  304. self.executed = True
  305. class LinkLimitRotation(MantisLinkNode):
  306. __slots__ = ()
  307. socket_templates = LinkLimitRotationSockets
  308. def bRelationshipPass(self, context):
  309. prepare_parameters(self)
  310. for xf in self.GetxForm():
  311. c = xf.bGetObject().constraints.new('LIMIT_ROTATION')
  312. print(wrapGreen("Creating ")+wrapWhite("Limit Rotation")+
  313. wrapGreen(" Constraint for bone: ") +
  314. wrapOrange(xf.bGetObject().name))
  315. if constraint_name := self.evaluate_input("Name"):
  316. c.name = constraint_name
  317. self.bObject.append(c)
  318. self.set_custom_space()
  319. props_sockets = self.gen_property_socket_map()
  320. evaluate_sockets(self, c, props_sockets)
  321. self.executed = True
  322. class LinkLimitScale(MantisLinkNode):
  323. __slots__ = ()
  324. socket_templates = LinkLimitScaleSockets
  325. def bRelationshipPass(self, context):
  326. prepare_parameters(self)
  327. for xf in self.GetxForm():
  328. c = xf.bGetObject().constraints.new('LIMIT_SCALE')
  329. print(wrapGreen("Creating ")+wrapWhite("Limit Scale")+
  330. wrapGreen(" Constraint for bone: ") +
  331. wrapOrange(xf.bGetObject().name))
  332. if constraint_name := self.evaluate_input("Name"):
  333. c.name = constraint_name
  334. self.bObject.append(c)
  335. self.set_custom_space()
  336. props_sockets = self.gen_property_socket_map()
  337. evaluate_sockets(self, c, props_sockets)
  338. self.executed = True
  339. class LinkLimitDistance(MantisLinkNode):
  340. __slots__ = ()
  341. socket_templates = LinkLimitDistanceSockets
  342. def bRelationshipPass(self, context):
  343. prepare_parameters(self)
  344. for xf in self.GetxForm():
  345. print(wrapGreen("Creating ")+wrapWhite("Limit Distance")+
  346. wrapGreen(" Constraint for bone: ") +
  347. wrapOrange(xf.bGetObject().name))
  348. c = xf.bGetObject().constraints.new('LIMIT_DISTANCE')
  349. self.get_target_and_subtarget(c)
  350. if constraint_name := self.evaluate_input("Name"):
  351. c.name = constraint_name
  352. self.bObject.append(c)
  353. self.set_custom_space()
  354. props_sockets = self.gen_property_socket_map()
  355. evaluate_sockets(self, c, props_sockets)
  356. self.executed = True
  357. # Tracking
  358. class LinkStretchTo(MantisLinkNode):
  359. __slots__ = ()
  360. socket_templates = LinkStretchToSockets
  361. def bRelationshipPass(self, context):
  362. prepare_parameters(self)
  363. for xf in self.GetxForm():
  364. print(wrapGreen("Creating ")+wrapWhite("Stretch-To")+
  365. wrapGreen(" Constraint for bone: ") +
  366. wrapOrange(xf.bGetObject().name))
  367. c = xf.bGetObject().constraints.new('STRETCH_TO')
  368. self.get_target_and_subtarget(c)
  369. if constraint_name := self.evaluate_input("Name"):
  370. c.name = constraint_name
  371. self.bObject.append(c)
  372. props_sockets = self.gen_property_socket_map()
  373. evaluate_sockets(self, c, props_sockets)
  374. if (self.evaluate_input("Original Length") == 0):
  375. # this is meant to be set automatically.
  376. c.rest_length = xf.bGetObject().bone.length
  377. self.executed = True
  378. class LinkDampedTrack(MantisLinkNode):
  379. __slots__ = ()
  380. socket_templates = LinkDampedTrackSockets
  381. def bRelationshipPass(self, context):
  382. prepare_parameters(self)
  383. for xf in self.GetxForm():
  384. print(wrapGreen("Creating ")+wrapWhite("Damped Track")+
  385. wrapGreen(" Constraint for bone: ") +
  386. wrapOrange(xf.bGetObject().name))
  387. c = xf.bGetObject().constraints.new('DAMPED_TRACK')
  388. self.get_target_and_subtarget(c)
  389. if constraint_name := self.evaluate_input("Name"):
  390. c.name = constraint_name
  391. self.bObject.append(c)
  392. props_sockets = self.gen_property_socket_map()
  393. evaluate_sockets(self, c, props_sockets)
  394. self.executed = True
  395. class LinkLockedTrack(MantisLinkNode):
  396. __slots__ = ()
  397. socket_templates = LinkLockedTrackSockets
  398. def bRelationshipPass(self, context):
  399. prepare_parameters(self)
  400. for xf in self.GetxForm():
  401. print(wrapGreen("Creating ")+wrapWhite("Locked Track")+
  402. wrapGreen(" Constraint for bone: ") +
  403. wrapOrange(xf.bGetObject().name))
  404. c = xf.bGetObject().constraints.new('LOCKED_TRACK')
  405. self.get_target_and_subtarget(c)
  406. if constraint_name := self.evaluate_input("Name"):
  407. c.name = constraint_name
  408. self.bObject.append(c)
  409. props_sockets = self.gen_property_socket_map()
  410. evaluate_sockets(self, c, props_sockets)
  411. self.executed = True
  412. class LinkTrackTo(MantisLinkNode):
  413. __slots__ = ()
  414. socket_templates = LinkTrackToSockets
  415. def bRelationshipPass(self, context):
  416. prepare_parameters(self)
  417. for xf in self.GetxForm():
  418. print(wrapGreen("Creating ")+wrapWhite("Track-To")+
  419. wrapGreen(" Constraint for bone: ") +
  420. wrapOrange(xf.bGetObject().name))
  421. c = xf.bGetObject().constraints.new('TRACK_TO')
  422. self.get_target_and_subtarget(c)
  423. if constraint_name := self.evaluate_input("Name"):
  424. c.name = constraint_name
  425. self.bObject.append(c)
  426. props_sockets = self.gen_property_socket_map()
  427. evaluate_sockets(self, c, props_sockets)
  428. self.executed = True
  429. class LinkInheritConstraint(MantisLinkNode):
  430. __slots__ = ()
  431. socket_templates = LinkInheritConstraintSockets
  432. def bRelationshipPass(self, context):
  433. prepare_parameters(self)
  434. for xf in self.GetxForm():
  435. print(wrapGreen("Creating ")+wrapWhite("Child-Of")+
  436. wrapGreen(" Constraint for bone: ") +
  437. wrapOrange(xf.bGetObject().name))
  438. c = xf.bGetObject().constraints.new('CHILD_OF')
  439. self.get_target_and_subtarget(c)
  440. if constraint_name := self.evaluate_input("Name"):
  441. c.name = constraint_name
  442. self.bObject.append(c)
  443. props_sockets = self.gen_property_socket_map()
  444. evaluate_sockets(self, c, props_sockets)
  445. c.set_inverse_pending
  446. self.executed = True
  447. class LinkInverseKinematics(MantisLinkNode):
  448. __slots__ = ()
  449. socket_templates = LinkInverseKinematicsSockets
  450. def get_base_ik_bone(self, ik_bone):
  451. chain_length : int = (self.evaluate_input("Chain Length"))
  452. if not isinstance(chain_length, (int, float)):
  453. raise GraphError(f"Chain Length must be an integer number in {self}::Chain Length")
  454. if chain_length == 0:
  455. chain_length = int("inf")
  456. base_ik_bone = ik_bone; i=1
  457. while (i<chain_length) and (base_ik_bone.parent):
  458. base_ik_bone=base_ik_bone.parent; i+=1
  459. return base_ik_bone
  460. # We need to do the calculation in a "full circle", meaning the pole_angle
  461. # can go over pi or less than -pi - but the actuall constraint value must
  462. # be clamped in that range.
  463. # so we simply wrap the value.
  464. # not very efficient but it's OK
  465. def set_pole_angle(self, constraint, angle: float) -> None:
  466. from math import pi
  467. from .utilities.utilities import wrap
  468. constraint.pole_angle = wrap(-pi, pi, angle)
  469. def calc_pole_angle_pre(self, c, ik_bone):
  470. """
  471. This function gets us most of the way to a correct IK pole angle. Unfortunately,
  472. due to the unpredictable nature of the iterative IK calculation, I can't figure
  473. out an exact solution. So we do a bisect search in calc_pole_angle_post().
  474. """
  475. # TODO: instead of these checks, convert all to armature local space. But this is tedious.
  476. if not c.target:
  477. raise GraphError(f"IK Constraint {self} must have target.")
  478. elif c.target.type != "ARMATURE":
  479. raise NotImplementedError(f"Currently, IK Constraint Target for {self} must be a bone within the same armature.")
  480. if c.pole_target.type != "ARMATURE":
  481. raise NotImplementedError(f"Currently, IK Constraint Pole Target for {self} must be a bone within the same armature.")
  482. ik_handle = c.target.pose.bones[c.subtarget]
  483. if ik_handle.id_data != ik_bone.id_data:
  484. raise NotImplementedError(f"Currently, IK Constraint Target for {self} must be a bone within the same armature.")
  485. ik_pole = c.pole_target.pose.bones[c.pole_subtarget]
  486. if ik_pole.id_data != ik_bone.id_data:
  487. raise NotImplementedError(f"Currently,IK Constraint Pole Target for {self} must be a bone within the same armature.")
  488. base_ik_bone = self.get_base_ik_bone(ik_bone)
  489. start_effector = base_ik_bone.bone.head_local
  490. end_effector = ik_handle.bone.head_local
  491. pole_location = ik_pole.bone.head_local
  492. # this is the X-Axis of the bone's rest-pose, added to its bone
  493. knee_location = base_ik_bone.bone.matrix_local.col[0].xyz+start_effector
  494. ik_axis = (end_effector-start_effector).normalized()
  495. from .utilities.utilities import project_point_to_plane
  496. pole_planar_projection = project_point_to_plane(pole_location, start_effector, ik_axis)
  497. # this planar projection is necessary because the IK axis is different than the base_bone's y axis
  498. planar_projection = project_point_to_plane(knee_location, start_effector, ik_axis)
  499. knee_direction =(planar_projection - start_effector).normalized()
  500. pole_direction =(pole_planar_projection - start_effector).normalized()
  501. return knee_direction.angle(pole_direction)
  502. def calc_pole_angle_post(self, c, ik_bone, context):
  503. """
  504. This function should give us a completely accurate result for IK.
  505. """
  506. from time import time
  507. start_time=time()
  508. def signed_angle(vector_u, vector_v, normal):
  509. # it seems that this fails if the vectors are exactly aligned under certain circumstances.
  510. angle = vector_u.angle(vector_v, 0.0) # So we use a fallback of 0
  511. # Normal specifies orientation
  512. if angle != 0 and vector_u.cross(vector_v).angle(normal) < 1:
  513. angle = -angle
  514. return angle
  515. # we have already checked for valid data.
  516. ik_handle = c.target.pose.bones[c.subtarget]
  517. base_ik_bone = self.get_base_ik_bone(ik_bone)
  518. start_effector = base_ik_bone.bone.head_local
  519. angle = c.pole_angle
  520. dg = context.view_layer.depsgraph
  521. dg.update()
  522. ik_axis = (ik_handle.bone.head_local-start_effector).normalized()
  523. center_point = start_effector +(ik_axis*base_ik_bone.bone.length)
  524. knee_direction = base_ik_bone.bone.tail_local - center_point
  525. current_knee_direction = base_ik_bone.tail-center_point
  526. error=signed_angle(current_knee_direction, knee_direction, ik_axis)
  527. if error == 0:
  528. prGreen("No Fine-tuning needed."); return
  529. # Flip it if needed
  530. dot_before=current_knee_direction.dot(knee_direction)
  531. if dot_before < 0 and angle!=0: # then it is not aligned and we should check the inverse
  532. angle = -angle; c.pole_angle=angle
  533. dg.update()
  534. current_knee_direction = base_ik_bone.tail-center_point
  535. dot_after=current_knee_direction.dot(knee_direction)
  536. if dot_after < dot_before: # they are somehow less aligned
  537. prPurple("Mantis has gone down an unexpected code path. Please report this as a bug.")
  538. angle = -angle; self.set_pole_angle(c, angle)
  539. dg.update()
  540. # now we can do a bisect search to find the best value.
  541. error_threshhold = FLOAT_EPSILON
  542. max_iterations=600
  543. error=signed_angle(current_knee_direction, knee_direction, ik_axis)
  544. if error == 0:
  545. prGreen("No Fine-tuning needed."); return
  546. angle+=error
  547. alt_angle = angle+(error*-2) # should be very near the center when flipped here
  548. # we still need to bisect search because the relationship of pole_angle <==> error is somewhat unpredictable
  549. upper_bounds = alt_angle if alt_angle > angle else angle
  550. lower_bounds = alt_angle if alt_angle < angle else angle
  551. i, error_identical = 0, 0
  552. while ( True ):
  553. if (i>=max_iterations):
  554. prOrange(f"IK Pole Angle Set reached max iterations of {i-error_identical} in {time()-start_time} seconds")
  555. break
  556. if (abs(error)<error_threshhold) or (upper_bounds<=lower_bounds) or (error_identical > 3):
  557. prPurple(f"IK Pole Angle Set converged after {i-error_identical} iterations with error={error} in {time()-start_time} seconds")
  558. break
  559. # get the center-point betweeen the bounds
  560. try_angle = lower_bounds + (upper_bounds-lower_bounds)/2
  561. self.set_pole_angle(c, try_angle); dg.update()
  562. prev_error = error
  563. error = signed_angle((base_ik_bone.tail-center_point), knee_direction, ik_axis)
  564. error_identical+= int(error == prev_error)
  565. if error>0: upper_bounds=try_angle
  566. if error<0: lower_bounds=try_angle
  567. i+=1
  568. def bRelationshipPass(self, context):
  569. prepare_parameters(self)
  570. for xf in self.GetxForm():
  571. print(wrapGreen("Creating ")+wrapOrange("Inverse Kinematics")+
  572. wrapGreen(" Constraint for bone: ") +
  573. wrapOrange(xf.bGetObject().name))
  574. ik_bone = xf.bGetObject()
  575. c = xf.bGetObject().constraints.new('IK')
  576. self.get_target_and_subtarget(c)
  577. self.get_target_and_subtarget(c, input_name = 'Pole Target')
  578. if constraint_name := self.evaluate_input("Name"):
  579. c.name = constraint_name
  580. self.bObject.append(c)
  581. c.chain_count = 1 # so that, if there are errors, this doesn't print
  582. # a whole bunch of circular dependency crap from having infinite chain length
  583. if (c.pole_target):
  584. self.set_pole_angle(c, self.calc_pole_angle_pre(c, ik_bone))
  585. props_sockets = self.gen_property_socket_map()
  586. evaluate_sockets(self, c, props_sockets)
  587. c.use_location = self.evaluate_input("Position") > 0
  588. c.use_rotation = self.evaluate_input("Rotation") > 0
  589. self.executed = True
  590. def bFinalize(self, bContext = None):
  591. # adding a test here
  592. if bContext:
  593. for i, constraint in enumerate(self.bObject):
  594. ik_bone = self.GetxForm()[i].bGetObject(mode='POSE')
  595. if constraint.pole_target:
  596. prWhite(f"Fine-tuning IK Pole Angle for {self}")
  597. # make sure to enable it first
  598. enabled_before = constraint.mute
  599. constraint.mute = False
  600. self.calc_pole_angle_post(constraint, ik_bone, bContext)
  601. constraint.mute = enabled_before
  602. super().bFinalize(bContext)
  603. def ik_report_error(pb, context, do_print=False):
  604. dg = context.view_layer.depsgraph
  605. dg.update()
  606. loc1, rot_quaternion1, scl1 = pb.matrix.decompose()
  607. loc2, rot_quaternion2, scl2 = pb.bone.matrix_local.decompose()
  608. location_error=(loc1-loc2).length
  609. rotation_error = rot_quaternion1.rotation_difference(rot_quaternion2).angle
  610. scale_error = (scl1-scl2).length
  611. if location_error < FLOAT_EPSILON: location_error = 0
  612. if abs(rotation_error) < FLOAT_EPSILON: rotation_error = 0
  613. if scale_error < FLOAT_EPSILON: scale_error = 0
  614. if do_print:
  615. print (f"IK Location Error: {location_error}")
  616. print (f"IK Rotation Error: {rotation_error}")
  617. print (f"IK Scale Error : {scale_error}")
  618. return (location_error, rotation_error, scale_error)
  619. # This is kinda a weird design decision?
  620. class LinkDrivenParameter(MantisLinkNode):
  621. '''A node representing an armature object'''
  622. __slots__ = ()
  623. socket_templates = LinkDrivenParameterSockets
  624. def bRelationshipPass(self, bContext = None,):
  625. prepare_parameters(self)
  626. prGreen("Executing Driven Parameter node")
  627. prop = self.evaluate_input("Parameter")
  628. index = self.evaluate_input("Index")
  629. value = self.evaluate_input("Value")
  630. for xf in self.GetxForm():
  631. ob = xf.bGetObject(mode="POSE")
  632. # IMPORTANT: this node only works on pose bone attributes.
  633. self.bObject.append(ob)
  634. length=1
  635. if hasattr(ob, prop):
  636. try:
  637. length = len(getattr(ob, prop))
  638. except TypeError:
  639. pass
  640. except AttributeError:
  641. pass
  642. else:
  643. raise AttributeError(f"Cannot Set value {prop} on object because it does not exist.")
  644. def_value = 0.0
  645. if length>1:
  646. def_value=[0.0]*length
  647. self.parameters["Value"] = tuple( 0.0 if i != index else value for i in range(length))
  648. from .mantis_dataclasses import MantisSocketFiller
  649. props_sockets = {
  650. prop: MantisSocketFiller( property_name=prop,
  651. socket_name="Value",
  652. default_value=def_value)
  653. }
  654. evaluate_sockets(self, ob, props_sockets)
  655. self.executed = True
  656. def bFinalize(self, bContext = None):
  657. driver = self.evaluate_input("Value")
  658. try:
  659. for i, val in enumerate(self.parameters["Value"]):
  660. from .utilities.drivers import MantisDriver
  661. if isinstance(val, MantisDriver):
  662. driver["ind"] = i
  663. val = driver
  664. except AttributeError:
  665. self.parameters["Value"] = driver
  666. except TypeError:
  667. self.parameters["Value"] = driver
  668. super().bFinalize(bContext)
  669. class LinkArmature(MantisLinkNode):
  670. '''A node representing an armature object'''
  671. __slots__ = ()
  672. socket_templates = LinkArmatureSockets
  673. def bRelationshipPass(self, bContext = None,):
  674. prepare_parameters(self)
  675. for xf in self.GetxForm():
  676. print(wrapGreen("Creating ")+wrapOrange("Armature")+
  677. wrapGreen(" Constraint for bone: ") +
  678. wrapOrange(xf.bGetObject().name))
  679. c = xf.bGetObject().constraints.new('ARMATURE')
  680. if constraint_name := self.evaluate_input("Name"):
  681. c.name = constraint_name
  682. self.bObject.append(c)
  683. # get number of targets
  684. num_targets = len( self.inputs['Targets'].links )
  685. props_sockets = self.gen_property_socket_map()
  686. # TODO: currently this will only work if the constraint bones
  687. # are all in the same armature
  688. # Mantis should intelligently provide a working armature constraint
  689. # even if the user supplies targets that would not work in vanilla Blender
  690. # TODO find out this is reading the arrays in reverse order? is that bad?
  691. targets_weights = {}
  692. for i in range(num_targets):
  693. target = c.targets.new()
  694. self.get_target_and_subtarget(target, 'Targets', i)
  695. weight_value=self.evaluate_input("Weights", i)
  696. if not isinstance(weight_value, float):
  697. weight_value=0
  698. targets_weights[i]=weight_value
  699. from .mantis_dataclasses import MantisSocketFiller
  700. default_value = 1.0
  701. if self.inputs['Weights'].links:
  702. default_value = 1/len(self.inputs['Weights'].links)
  703. filler = MantisSocketFiller(
  704. property_name= (prop_name := "targets[%d].weight" % i),
  705. socket_name="Weights",
  706. default_value=default_value,
  707. array_index=i,
  708. )
  709. props_sockets[prop_name] = filler
  710. evaluate_sockets(self, c, props_sockets)
  711. for target, value in targets_weights.items():
  712. c.targets[target].weight=value
  713. self.executed = True
  714. class LinkSplineIK(MantisLinkNode):
  715. '''A node representing an armature object'''
  716. __slots__ = ()
  717. socket_templates = LinkSplineIKSockets
  718. def bRelationshipPass(self, bContext = None,):
  719. prepare_parameters(self)
  720. if not self.inputs['Target'].is_linked:
  721. raise GraphError(f"ERROR: {self} is not connected to a target curve.")
  722. for xf in self.GetxForm():
  723. print(wrapGreen("Creating ")+wrapOrange("Spline-IK")+
  724. wrapGreen(" Constraint for bone: ") +
  725. wrapOrange(xf.bGetObject().name))
  726. c = xf.bGetObject().constraints.new('SPLINE_IK')
  727. # set the spline - we need to get the right one
  728. spline_index = self.evaluate_input("Spline Index")
  729. proto_curve = None
  730. node_line, _last_socket = trace_single_line(self, "Target")
  731. for other_node in node_line: # trace and get the input
  732. if other_node.node_type == 'XFORM':
  733. proto_curve = other_node.bGetObject(); break
  734. from .utilities.utilities import get_extracted_spline_object
  735. curve = get_extracted_spline_object(proto_curve, spline_index, self.mContext)
  736. # link it to the view layer
  737. if (curve.name not in bContext.view_layer.active_layer_collection.collection.objects):
  738. bContext.view_layer.active_layer_collection.collection.objects.link(curve)
  739. c.target=curve
  740. if constraint_name := self.evaluate_input("Name"):
  741. c.name = constraint_name
  742. self.bObject.append(c)
  743. props_sockets = self.gen_property_socket_map()
  744. evaluate_sockets(self, c, props_sockets)
  745. self.executed = True
  746. class LinkFloor(MantisLinkNode):
  747. '''A node representing an armature object'''
  748. __slots__ = ()
  749. socket_templates = LinkFloorSockets
  750. def bRelationshipPass(self, bContext = None,):
  751. prepare_parameters(self)
  752. for xf in self.GetxForm():
  753. print(wrapGreen("Creating ")+wrapOrange("Floor")+
  754. wrapGreen(" Constraint for bone: ") +
  755. wrapOrange(xf.bGetObject().name))
  756. c = xf.bGetObject().constraints.new('FLOOR')
  757. self.get_target_and_subtarget(c)
  758. if constraint_name := self.evaluate_input("Name"):
  759. c.name = constraint_name
  760. self.bObject.append(c)
  761. self.set_custom_space()
  762. props_sockets = self.gen_property_socket_map()
  763. evaluate_sockets(self, c, props_sockets)
  764. self.executed = True
  765. class LinkShrinkWrap(MantisLinkNode):
  766. '''A node representing a shrinkwrap relationship.'''
  767. __slots__ = ()
  768. socket_templates = LinkShrinkWrapSockets
  769. def bRelationshipPass(self, bContext = None,):
  770. prepare_parameters(self)
  771. for xf in self.GetxForm():
  772. print(wrapGreen("Creating ")+wrapOrange("Shrinkwrap")+
  773. wrapGreen(" Constraint for bone: ") +
  774. wrapOrange(xf.bGetObject().name))
  775. c = xf.bGetObject().constraints.new('SHRINKWRAP')
  776. self.bObject.append(c)
  777. self.get_target_and_subtarget(c)
  778. if constraint_name := self.evaluate_input("Name"):
  779. c.name = constraint_name
  780. props_sockets = self.gen_property_socket_map()
  781. evaluate_sockets(self, c, props_sockets)
  782. # self.set_custom_space() # this needs to be overridden for me TODO
  783. self.executed = True
  784. class LinkGeometryAttribute(MantisLinkNode):
  785. '''A node representing a geometry attribute relationship.'''
  786. __slots__ = ()
  787. socket_templates = LinkGeometryAttributeSockets
  788. def bRelationshipPass(self, bContext = None,):
  789. from bpy.app import version
  790. from .base_definitions import BlenderVersionError
  791. if version < (5,0,0):
  792. raise BlenderVersionError("Geometry Attribute Constraint is supported only in Blender 5.0 or greater.")
  793. prepare_parameters(self)
  794. for xf in self.GetxForm():
  795. print
  796. c = xf.bGetObject().constraints.new('GEOMETRY_ATTRIBUTE')
  797. self.bObject.append(c)
  798. self.get_target_and_subtarget(c)
  799. if constraint_name := self.evaluate_input("Name"):
  800. c.name = constraint_name
  801. props_sockets = self.gen_property_socket_map()
  802. evaluate_sockets(self, c, props_sockets)
  803. self.executed = True