link_containers.py 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505
  1. from .node_container_common import *
  2. from bpy.types import Bone, NodeTree
  3. from .base_definitions import MantisNode, GraphError, FLOAT_EPSILON
  4. from .base_definitions import MantisSocketTemplate as SockTemplate
  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. # Drivers
  32. LinkDrivenParameter,
  33. ]
  34. # Socket Templates we will reuse:
  35. # inputs:
  36. InputRelationshipTemplate : SockTemplate = SockTemplate(
  37. name="Input Relationship", is_input=True, bl_idname='RelationshipSocket', )
  38. TargetTemplate : SockTemplate = SockTemplate(
  39. name="Target", is_input=True, bl_idname='xFormSocket', )
  40. Head_Tail_Template : SockTemplate = SockTemplate(
  41. name="Head/Tail", is_input=True, bl_idname='FloatFactorSocket',
  42. default_value=1.0, blender_property='head_tail' )
  43. UseBBoneTemplate : SockTemplate = SockTemplate(
  44. name="UseBBone", is_input=True, bl_idname='BooleanSocket',
  45. default_value=False, blender_property='use_bbone_shape' )
  46. AxeSockTemplate : SockTemplate = SockTemplate(
  47. name="Axes", is_input=True, bl_idname='BooleanThreeTupleSocket',
  48. default_value=[True, True, True], blender_property=['use_x', 'use_y', 'use_z'])
  49. AxesInvertTemplate : SockTemplate = SockTemplate(
  50. name="Invert", is_input=True, bl_idname='BooleanThreeTupleSocket',
  51. default_value=[False, False, False], blender_property=['invert_x', 'invert_y', 'invert_z'])
  52. TargetSpaceTemplate : SockTemplate = SockTemplate(
  53. name="Target Space", is_input=True, bl_idname='TransformSpaceSocket',
  54. default_value="WORLD", blender_property='target_space' )
  55. OwnerSpaceTemplate : SockTemplate = SockTemplate(
  56. name="Owner Space", is_input=True, bl_idname='TransformSpaceSocket',
  57. default_value="WORLD", blender_property='owner_space' )
  58. InfluenceTemplate : SockTemplate = SockTemplate(
  59. name="Influence", is_input=True, bl_idname='FloatFactorSocket',
  60. default_value=1.0, blender_property='influence')
  61. EnableTemplate : SockTemplate = SockTemplate(
  62. name="Enable", is_input=True, bl_idname='EnableSocket',
  63. default_value=True, blender_property='mute')
  64. OffsetTemplate : SockTemplate = SockTemplate(
  65. name="Offset", bl_idname='BooleanSocket', is_input=True,
  66. default_value=False, blender_property='use_offset')
  67. # Limit Constraints follow a pattern and can use this generator
  68. LimitTemplateGenerator = lambda name_stub, axis : SockTemplate(
  69. name=name_stub+axis.upper(), is_input=True, bl_idname='BoolUpdateParentNode',
  70. default_value=False, blender_property=name_stub.lower().replace(' ', '_')+axis.lower())
  71. LimitAxesSocketTemplates = [] # could generate these with loops, but this is easier to understand
  72. LimitAxesSocketTemplates.append(UseMaxXTemplates := LimitTemplateGenerator("Use Max ", "X"))
  73. LimitAxesSocketTemplates.append(MaxXTemplates := LimitTemplateGenerator("Max ", "X"))
  74. LimitAxesSocketTemplates.append(UseMaxYTemplates := LimitTemplateGenerator("Use Max ", "Y"))
  75. LimitAxesSocketTemplates.append(MaxYTemplates := LimitTemplateGenerator("Max ", "Y"))
  76. LimitAxesSocketTemplates.append(UseMaxZTemplates := LimitTemplateGenerator("Use Max ", "Z"))
  77. LimitAxesSocketTemplates.append(MinZTemplates := LimitTemplateGenerator("Min ", "Z"))
  78. LimitAxesSocketTemplates.append(UseMinXTemplates := LimitTemplateGenerator("Use Min ", "X"))
  79. LimitAxesSocketTemplates.append(MinXTemplates := LimitTemplateGenerator("Min ", "X"))
  80. LimitAxesSocketTemplates.append(UseMinYTemplates := LimitTemplateGenerator("Use Min ", "Y"))
  81. LimitAxesSocketTemplates.append(MinYTemplates := LimitTemplateGenerator("Min ", "Y"))
  82. LimitAxesSocketTemplates.append(UseMinZTemplates := LimitTemplateGenerator("Use Min ", "Z"))
  83. LimitAxesSocketTemplates.append(MinZTemplates := LimitTemplateGenerator("Min ", "Z"))
  84. #
  85. AffectTransformTemplate : SockTemplate = SockTemplate(
  86. name="Affect Transform", bl_idname='BooleanSocket', is_input=True,
  87. default_value=False, blender_property='use_transform_limit')
  88. # outputs:
  89. OutputRelationshipTemplate : SockTemplate = SockTemplate(
  90. name="Output Relationship", is_input=False, bl_idname='RelationshipSocket', )
  91. # set the name if it is available, otherwise just use the constraint's nice name
  92. set_constraint_name = lambda nc : nc.evaluate_input("Name") if nc.evaluate_input("Name") else nc.__class__.__name__
  93. class MantisLinkNode(MantisNode):
  94. def __init__(self, signature : tuple,
  95. base_tree : NodeTree,
  96. socket_templates : list[SockTemplate]=[]):
  97. super().__init__(signature, base_tree, socket_templates)
  98. self.node_type = 'LINK'
  99. self.prepared = True
  100. def evaluate_input(self, input_name, index=0):
  101. # should catch 'Target', 'Pole Target' and ArmatureConstraint targets, too
  102. if ('Target' in input_name) and input_name not in ["Target Space", "Use Target Z"]:
  103. socket = self.inputs.get(input_name)
  104. if socket.is_linked:
  105. return socket.links[0].from_node
  106. return None
  107. else:
  108. return super().evaluate_input(input_name)
  109. def gen_property_socket_map(self) -> dict:
  110. props_sockets = super().gen_property_socket_map()
  111. if (os := self.inputs["Owner Space"]).is_connected and os.links[0].from_node.node_type == 'XFORM':
  112. del props_sockets['owner_space']
  113. if ts := self.inputs.get("Target_Space") and ts.is_connected and ts.links[0].from_node.node_type == 'XFORM':
  114. del props_sockets['target_space']
  115. return props_sockets
  116. def set_custom_space(self):
  117. c = self.bObject
  118. if (os := self.inputs["Owner Space"]).is_connected and os.links[0].from_node.node_type == 'XFORM':
  119. c.owner_space='CUSTOM'
  120. xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
  121. if isinstance(xf, Bone):
  122. c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
  123. else:
  124. c.space_object=xf
  125. if ts := self.inputs.get("Target_Space") and ts.is_connected and ts.links[0].from_node.node_type == 'XFORM':
  126. c.owner_space='CUSTOM'
  127. xf = self.inputs["Target_Space Space"].links[0].from_node.bGetObject(mode="OBJECT")
  128. if isinstance(xf, Bone):
  129. c.space_object=self.inputs["Target_Space Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
  130. else:
  131. c.space_object=xf
  132. #*#-------------------------------#++#-------------------------------#*#
  133. # L I N K N O D E S
  134. #*#-------------------------------#++#-------------------------------#*#
  135. def GetxForm(nc):
  136. trace = trace_single_line_up(nc, "Output Relationship")
  137. for node in trace[0]:
  138. if (node.node_type == 'XFORM'):
  139. return node
  140. raise GraphError("%s is not connected to a downstream xForm" % nc)
  141. LinkInheritSockets = [ # Name is_input bl_idname
  142. SockTemplate(name="Parent", is_input=True, bl_idname='xFormSocket',),
  143. SockTemplate(name="Inherit Rotation", is_input=True, bl_idname='BooleanSocket',),
  144. SockTemplate(name="Inherit Scale", is_input=True, bl_idname='EnumInheritScale',),
  145. SockTemplate(name="Connected", is_input=True, bl_idname='BooleanSocket',),
  146. SockTemplate(name="Inheritance", is_input=False, bl_idname='RelationshipSocket',),
  147. ]
  148. class LinkInherit(MantisLinkNode):
  149. '''A node representing inheritance'''
  150. def __init__(self, signature, base_tree):
  151. super().__init__(signature, base_tree, LinkInheritSockets)
  152. self.init_parameters()
  153. self.set_traverse([('Parent', 'Inheritance')])
  154. self.executed = True
  155. def GetxForm(self): # DUPLICATED, TODO fix this
  156. # I think this is only run in display update.
  157. trace = trace_single_line_up(self, "Inheritance")
  158. for node in trace[0]:
  159. if (node.node_type == 'XFORM'):
  160. return node
  161. raise GraphError("%s is not connected to a downstream xForm" % self)
  162. LinkCopyLocationSockets = [
  163. InputRelationshipTemplate,
  164. Head_Tail_Template,
  165. UseBBoneTemplate,
  166. AxeSockTemplate,
  167. AxesInvertTemplate,
  168. TargetSpaceTemplate,
  169. OwnerSpaceTemplate,
  170. OffsetTemplate,
  171. InfluenceTemplate,
  172. TargetTemplate,
  173. EnableTemplate,
  174. OutputRelationshipTemplate,
  175. ]
  176. class LinkCopyLocation(MantisLinkNode):
  177. '''A node representing Copy Location'''
  178. def __init__(self, signature : tuple,
  179. base_tree : NodeTree,):
  180. super().__init__(signature, base_tree, LinkCopyLocationSockets)
  181. additional_parameters = { "Name":None }
  182. self.init_parameters(additional_parameters=additional_parameters)
  183. self.set_traverse([("Input Relationship", "Output Relationship")])
  184. def GetxForm(self):
  185. return GetxForm(self)
  186. def bExecute(self, context):
  187. prepare_parameters(self)
  188. c = self.GetxForm().bGetObject().constraints.new('COPY_LOCATION')
  189. self.get_target_and_subtarget(c)
  190. print(wrapGreen("Creating ")+wrapWhite("Copy Location")+
  191. wrapGreen(" Constraint for bone: ") +
  192. wrapOrange(self.GetxForm().bGetObject().name))
  193. if constraint_name := self.evaluate_input("Name"):
  194. c.name = constraint_name
  195. self.bObject = c
  196. self.set_custom_space()
  197. props_sockets = self.gen_property_socket_map()
  198. evaluate_sockets(self, c, props_sockets)
  199. self.executed = True
  200. def bFinalize(self, bContext = None):
  201. finish_drivers(self)
  202. LinkCopyRotationSockets = [
  203. InputRelationshipTemplate,
  204. SockTemplate(name='RotationOrder', bl_idname='RotationOrderSocket', is_input=True,
  205. default_value='AUTO', blender_property='euler_order'),
  206. SockTemplate(name='Rotation Mix', bl_idname='EnumRotationMix', is_input=True,
  207. default_value='REPLACE', blender_property='mix_mode'),
  208. AxeSockTemplate,
  209. AxesInvertTemplate,
  210. TargetSpaceTemplate,
  211. OwnerSpaceTemplate,
  212. InfluenceTemplate,
  213. TargetTemplate,
  214. EnableTemplate,
  215. OutputRelationshipTemplate,
  216. ]
  217. class LinkCopyRotation(MantisLinkNode):
  218. '''A node representing Copy Rotation'''
  219. def __init__(self, signature, base_tree):
  220. super().__init__(signature, base_tree, LinkCopyRotationSockets)
  221. additional_parameters = { "Name":None }
  222. self.init_parameters(additional_parameters=additional_parameters)
  223. self.set_traverse([("Input Relationship", "Output Relationship")])
  224. def GetxForm(self):
  225. return GetxForm(self)
  226. def bExecute(self, context):
  227. prepare_parameters(self)
  228. c = self.GetxForm().bGetObject().constraints.new('COPY_ROTATION')
  229. self.get_target_and_subtarget(c)
  230. print(wrapGreen("Creating ")+wrapWhite("Copy Rotation")+
  231. wrapGreen(" Constraint for bone: ") +
  232. wrapOrange(self.GetxForm().bGetObject().name))
  233. rotation_order = self.evaluate_input("RotationOrder")
  234. if ((rotation_order == 'QUATERNION') or (rotation_order == 'AXIS_ANGLE')):
  235. c.euler_order = 'AUTO'
  236. else:
  237. try:
  238. c.euler_order = rotation_order
  239. except TypeError: # it's a driver or incorrect
  240. c.euler_order = 'AUTO'
  241. if constraint_name := self.evaluate_input("Name"):
  242. c.name = constraint_name
  243. self.bObject = c
  244. self.set_custom_space()
  245. props_sockets = self.gen_property_socket_map()
  246. evaluate_sockets(self, c, props_sockets)
  247. self.executed = True
  248. def bFinalize(self, bContext = None):
  249. finish_drivers(self)
  250. LinkCopyScaleSockets = [
  251. InputRelationshipTemplate,
  252. OffsetTemplate,
  253. SockTemplate(name='Average', bl_idname = 'BooleanSocket', is_input=True,
  254. default_value=False, blender_property='use_make_uniform'),
  255. SockTemplate(name='Additive', bl_idname = 'BooleanSocket', is_input=True,
  256. default_value=False, blender_property='use_add'),
  257. AxeSockTemplate,
  258. TargetSpaceTemplate,
  259. OwnerSpaceTemplate,
  260. InfluenceTemplate,
  261. TargetTemplate,
  262. EnableTemplate,
  263. OutputRelationshipTemplate,
  264. ]
  265. class LinkCopyScale(MantisLinkNode):
  266. '''A node representing Copy Scale'''
  267. def __init__(self, signature, base_tree):
  268. super().__init__(signature, base_tree, LinkCopyScaleSockets)
  269. additional_parameters = { "Name":None }
  270. self.init_parameters(additional_parameters=additional_parameters)
  271. self.set_traverse([("Input Relationship", "Output Relationship")])
  272. def GetxForm(self):
  273. return GetxForm(self)
  274. def bExecute(self, context):
  275. prepare_parameters(self)
  276. c = self.GetxForm().bGetObject().constraints.new('COPY_SCALE')
  277. self.get_target_and_subtarget(c)
  278. print(wrapGreen("Creating ")+wrapWhite("Copy Scale")+
  279. wrapGreen(" Constraint for bone: ") +
  280. wrapOrange(self.GetxForm().bGetObject().name))
  281. if constraint_name := self.evaluate_input("Name"):
  282. c.name = constraint_name
  283. self.bObject = c
  284. if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
  285. c.owner_space='CUSTOM'
  286. xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
  287. if isinstance(xf, Bone):
  288. c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
  289. else:
  290. c.space_object=xf
  291. if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
  292. c.target_space='CUSTOM'
  293. xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
  294. if isinstance(xf, Bone):
  295. c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
  296. else:
  297. c.space_object=xf
  298. props_sockets = self.gen_property_socket_map()
  299. evaluate_sockets(self, c, props_sockets)
  300. self.executed = True
  301. def bFinalize(self, bContext = None):
  302. finish_drivers(self)
  303. LinkCopyTransformsSockets = [
  304. InputRelationshipTemplate,
  305. Head_Tail_Template,
  306. UseBBoneTemplate,
  307. SockTemplate(name='Mix', bl_idname = 'EnumRotationMixCopyTransforms', is_input=True,
  308. default_value="REPLACE", blender_property='mix_mode'),
  309. TargetSpaceTemplate,
  310. OwnerSpaceTemplate,
  311. InfluenceTemplate,
  312. TargetTemplate,
  313. EnableTemplate,
  314. OutputRelationshipTemplate,
  315. ]
  316. class LinkCopyTransforms(MantisLinkNode):
  317. '''A node representing Copy Transfoms'''
  318. def __init__(self, signature, base_tree):
  319. super().__init__(signature, base_tree, LinkCopyTransformsSockets)
  320. additional_parameters = { "Name":None }
  321. self.init_parameters(additional_parameters=additional_parameters)
  322. self.set_traverse([("Input Relationship", "Output Relationship")])
  323. def GetxForm(self):
  324. return GetxForm(self)
  325. def bExecute(self, context):
  326. prepare_parameters(self)
  327. c = self.GetxForm().bGetObject().constraints.new('COPY_TRANSFORMS')
  328. self.get_target_and_subtarget(c)
  329. print(wrapGreen("Creating ")+wrapWhite("Copy Transforms")+
  330. wrapGreen(" Constraint for bone: ") +
  331. wrapOrange(self.GetxForm().bGetObject().name))
  332. if constraint_name := self.evaluate_input("Name"):
  333. c.name = constraint_name
  334. self.bObject = 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. def bFinalize(self, bContext = None):
  340. finish_drivers(self)
  341. transformation_props_sockets = {
  342. 'use_motion_extrapolate' : ("Extrapolate", False),
  343. 'map_from' : ("Map From", 'LOCATION'),
  344. 'from_rotation_mode' : ("Rotation Mode", 'AUTO'),
  345. 'from_min_x' : ("X Min From", 0.0),
  346. 'from_max_x' : ("X Max From", 0.0),
  347. 'from_min_y' : ("Y Min From", 0.0),
  348. 'from_max_y' : ("Y Max From", 0.0),
  349. 'from_min_z' : ("Z Min From", 0.0),
  350. 'from_max_z' : ("Z Max From", 0.0),
  351. 'from_min_x_rot' : ("X Min From", 0.0),
  352. 'from_max_x_rot' : ("X Max From", 0.0),
  353. 'from_min_y_rot' : ("Y Min From", 0.0),
  354. 'from_max_y_rot' : ("Y Max From", 0.0),
  355. 'from_min_z_rot' : ("Z Min From", 0.0),
  356. 'from_max_z_rot' : ("Z Max From", 0.0),
  357. 'from_min_x_scale' : ("X Min From", 0.0),
  358. 'from_max_x_scale' : ("X Max From", 0.0),
  359. 'from_min_y_scale' : ("Y Min From", 0.0),
  360. 'from_max_y_scale' : ("Y Max From", 0.0),
  361. 'from_min_z_scale' : ("Z Min From", 0.0),
  362. 'from_max_z_scale' : ("Z Max From", 0.0),
  363. 'map_to' : ("Map To", "LOCATION"),
  364. 'map_to_x_from' : ("X Source Axis", "X"),
  365. 'map_to_y_from' : ("Y Source Axis", "Y"),
  366. 'map_to_z_from' : ("Z Source Axis", "Z"),
  367. 'to_min_x' : ("X Min To", 0.0),
  368. 'to_max_x' : ("X Max To", 0.0),
  369. 'to_min_y' : ("Y Min To", 0.0),
  370. 'to_max_y' : ("Y Max To", 0.0),
  371. 'to_min_z' : ("Z Min To", 0.0),
  372. 'to_max_z' : ("Z Max To", 0.0),
  373. 'to_min_x_rot' : ("X Min To", 0.0),
  374. 'to_max_x_rot' : ("X Max To", 0.0),
  375. 'to_min_y_rot' : ("Y Min To", 0.0),
  376. 'to_max_y_rot' : ("Y Max To", 0.0),
  377. 'to_min_z_rot' : ("Z Min To", 0.0),
  378. 'to_max_z_rot' : ("Z Max To", 0.0),
  379. 'to_min_x_scale' : ("X Min To", 0.0),
  380. 'to_max_x_scale' : ("X Max To", 0.0),
  381. 'to_min_y_scale' : ("Y Min To", 0.0),
  382. 'to_max_y_scale' : ("Y Max To", 0.0),
  383. 'to_min_z_scale' : ("Z Min To", 0.0),
  384. 'to_max_z_scale' : ("Z Max To", 0.0),
  385. 'to_euler_order' : ("Rotation Mode", "AUTO"),
  386. 'mix_mode' : ("Mix Mode (Translation)", "ADD"),
  387. 'mix_mode_rot' : ("Mix Mode (Rotation)", "ADD"),
  388. 'mix_mode_scale' : ("Mix Mode (Scale)", "MULTIPLY"),
  389. 'owner_space' : ("Owner Space", 'WORLD'),
  390. 'target_space' : ("Target Space", 'WORLD'),
  391. 'influence' : ("Influence", 1),
  392. 'mute' : ("Enable", False),
  393. }
  394. class LinkTransformation(MantisLinkNode):
  395. '''A node representing Copy Transfoms'''
  396. def __init__(self, signature, base_tree):
  397. super().__init__(signature, base_tree)
  398. inputs = [
  399. "Input Relationship" ,
  400. "Target Space" ,
  401. "Owner Space" ,
  402. "Influence" ,
  403. "Target" ,
  404. "Enable" ,
  405. "Extrapolate" ,
  406. "Map From" ,
  407. "Rotation Mode" ,
  408. "X Min From" ,
  409. "X Max From" ,
  410. "Y Min From" ,
  411. "Y Max From" ,
  412. "Z Min From" ,
  413. "Z Max From" ,
  414. "Map To" ,
  415. "X Source Axis" ,
  416. "X Min To" ,
  417. "X Max To" ,
  418. "Y Source Axis" ,
  419. "Y Min To" ,
  420. "Y Max To" ,
  421. "Z Source Axis" ,
  422. "Z Min To" ,
  423. "Z Max To" ,
  424. "Mix Mode (Translation)" ,
  425. "Mix Mode (Rotation)" ,
  426. "Mix Mode (Scale)" ,
  427. ]
  428. additional_parameters = { "Name":None }
  429. self.inputs.init_sockets(inputs)
  430. self.outputs.init_sockets(["Output Relationship"])
  431. self.init_parameters(additional_parameters=additional_parameters)
  432. self.set_traverse([("Input Relationship", "Output Relationship")])
  433. def GetxForm(self):
  434. return GetxForm(self)
  435. def bExecute(self, context):
  436. prepare_parameters(self)
  437. c = self.GetxForm().bGetObject().constraints.new('TRANSFORM')
  438. self.get_target_and_subtarget(c)
  439. print(wrapGreen("Creating ")+wrapWhite("Transformation")+
  440. wrapGreen(" Constraint for bone: ") +
  441. wrapOrange(self.GetxForm().bGetObject().name))
  442. if constraint_name := self.evaluate_input("Name"):
  443. c.name = constraint_name
  444. self.bObject = c
  445. custom_space_owner, custom_space_target = False, False
  446. if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
  447. custom_space_owner=True
  448. c.owner_space='CUSTOM'
  449. xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
  450. if isinstance(xf, Bone):
  451. c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
  452. else:
  453. c.space_object=xf
  454. if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
  455. custom_space_target=True
  456. c.target_space='CUSTOM'
  457. xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
  458. if isinstance(xf, Bone):
  459. c.space_object=self.inputs["Target Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
  460. else:
  461. c.space_object=xf
  462. props_sockets = transformation_props_sockets.copy()
  463. if custom_space_owner: del props_sockets['owner_space']
  464. if custom_space_target: del props_sockets['target_space']
  465. #
  466. evaluate_sockets(self, c, props_sockets)
  467. self.executed = True
  468. def bFinalize(self, bContext = None):
  469. finish_drivers(self)
  470. LinkLimitLocationSockets = [
  471. InputRelationshipTemplate,
  472. *LimitAxesSocketTemplates, # we generated these ahead of time in a list
  473. AffectTransformTemplate,
  474. OwnerSpaceTemplate,
  475. InfluenceTemplate,
  476. EnableTemplate,
  477. OutputRelationshipTemplate,
  478. ]
  479. class LinkLimitLocation(MantisLinkNode):
  480. def __init__(self, signature, base_tree):
  481. super().__init__(signature, base_tree, LinkLimitLocationSockets)
  482. self.init_parameters(additional_parameters={ "Name":None })
  483. self.set_traverse([("Input Relationship", "Output Relationship")])
  484. def GetxForm(self):
  485. return GetxForm(self)
  486. def bExecute(self, context):
  487. prepare_parameters(self)
  488. c = self.GetxForm().bGetObject().constraints.new('LIMIT_LOCATION')
  489. #
  490. print(wrapGreen("Creating ")+wrapWhite("Limit Location")+
  491. wrapGreen(" Constraint for bone: ") +
  492. wrapOrange(self.GetxForm().bGetObject().name))
  493. if constraint_name := self.evaluate_input("Name"):
  494. c.name = constraint_name
  495. self.bObject = c
  496. self.set_custom_space()
  497. props_sockets = self.gen_property_socket_map()
  498. #
  499. evaluate_sockets(self, c, props_sockets)
  500. self.executed = True
  501. def bFinalize(self, bContext = None):
  502. finish_drivers(self)
  503. class LinkLimitRotation(MantisLinkNode):
  504. def __init__(self, signature, base_tree):
  505. super().__init__(signature, base_tree)
  506. inputs = [
  507. "Input Relationship" ,
  508. "Use X" ,
  509. "Use Y" ,
  510. "Use Z" ,
  511. "Max X" ,
  512. "Max Y" ,
  513. "Max Z" ,
  514. "Min X" ,
  515. "Min Y" ,
  516. "Min Z" ,
  517. "Affect Transform" ,
  518. "Owner Space" ,
  519. "Influence" ,
  520. "Enable" ,
  521. ]
  522. additional_parameters = { "Name":None }
  523. self.inputs.init_sockets(inputs)
  524. self.outputs.init_sockets(["Output Relationship"])
  525. self.init_parameters(additional_parameters=additional_parameters)
  526. self.set_traverse([("Input Relationship", "Output Relationship")])
  527. def GetxForm(self):
  528. return GetxForm(self)
  529. def bExecute(self, context):
  530. prepare_parameters(self)
  531. c = self.GetxForm().bGetObject().constraints.new('LIMIT_ROTATION')
  532. print(wrapGreen("Creating ")+wrapWhite("Limit Rotation")+
  533. wrapGreen(" Constraint for bone: ") +
  534. wrapOrange(self.GetxForm().bGetObject().name))
  535. if constraint_name := self.evaluate_input("Name"):
  536. c.name = constraint_name
  537. self.bObject = c
  538. custom_space_owner = False
  539. if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
  540. custom_space_owner=True
  541. c.owner_space='CUSTOM'
  542. xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
  543. if isinstance(xf, Bone):
  544. c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
  545. else:
  546. c.space_object=xf
  547. props_sockets = {
  548. 'use_transform_limit' : ("Affect Transform", False),
  549. 'use_limit_x' : ("Use X", False),
  550. 'use_limit_y' : ("Use Y", False),
  551. 'use_limit_z' : ("Use Z", False),
  552. 'max_x' : ("Max X", 0),
  553. 'max_y' : ("Max Y", 0),
  554. 'max_z' : ("Max Z", 0),
  555. 'min_x' : ("Min X", 0),
  556. 'min_y' : ("Min Y", 0),
  557. 'min_z' : ("Min Z", 0),
  558. 'owner_space' : ("Owner Space", 'WORLD'),
  559. 'influence' : ("Influence", 1),
  560. 'mute' : ("Enable", True),
  561. }
  562. if custom_space_owner: del props_sockets['owner_space']
  563. #
  564. evaluate_sockets(self, c, props_sockets)
  565. self.executed = True
  566. def bFinalize(self, bContext = None):
  567. finish_drivers(self)
  568. class LinkLimitScale(MantisLinkNode):
  569. def __init__(self, signature, base_tree):
  570. super().__init__(signature, base_tree)
  571. inputs = [
  572. "Input Relationship" ,
  573. "Use Max X" ,
  574. "Max X" ,
  575. "Use Max Y" ,
  576. "Max Y" ,
  577. "Use Max Z" ,
  578. "Max Z" ,
  579. "Use Min X" ,
  580. "Min X" ,
  581. "Use Min Y" ,
  582. "Min Y" ,
  583. "Use Min Z" ,
  584. "Min Z" ,
  585. "Affect Transform" ,
  586. "Owner Space" ,
  587. "Influence" ,
  588. "Enable" ,
  589. ]
  590. additional_parameters = { "Name":None }
  591. self.inputs.init_sockets(inputs)
  592. self.outputs.init_sockets(["Output Relationship"])
  593. self.init_parameters(additional_parameters=additional_parameters)
  594. self.set_traverse([("Input Relationship", "Output Relationship")])
  595. def GetxForm(self):
  596. return GetxForm(self)
  597. def bExecute(self, context):
  598. prepare_parameters(self)
  599. c = self.GetxForm().bGetObject().constraints.new('LIMIT_SCALE')
  600. print(wrapGreen("Creating ")+wrapWhite("Limit Scale")+
  601. wrapGreen(" Constraint for bone: ") +
  602. wrapOrange(self.GetxForm().bGetObject().name))
  603. if constraint_name := self.evaluate_input("Name"):
  604. c.name = constraint_name
  605. self.bObject = c
  606. custom_space_owner = False
  607. if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
  608. custom_space_owner=True
  609. c.owner_space='CUSTOM'
  610. xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
  611. if isinstance(xf, Bone):
  612. c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
  613. else:
  614. c.space_object=xf
  615. props_sockets = {
  616. 'use_transform_limit' : ("Affect Transform", False),
  617. 'use_max_x' : ("Use Max X", False),
  618. 'use_max_y' : ("Use Max Y", False),
  619. 'use_max_z' : ("Use Max Z", False),
  620. 'use_min_x' : ("Use Min X", False),
  621. 'use_min_y' : ("Use Min Y", False),
  622. 'use_min_z' : ("Use Min Z", False),
  623. 'max_x' : ("Max X", 0),
  624. 'max_y' : ("Max Y", 0),
  625. 'max_z' : ("Max Z", 0),
  626. 'min_x' : ("Min X", 0),
  627. 'min_y' : ("Min Y", 0),
  628. 'min_z' : ("Min Z", 0),
  629. 'owner_space' : ("Owner Space", 'WORLD'),
  630. 'influence' : ("Influence", 1),
  631. 'mute' : ("Enable", True),
  632. }
  633. if custom_space_owner: del props_sockets['owner_space']
  634. #
  635. evaluate_sockets(self, c, props_sockets)
  636. self.executed = True
  637. def bFinalize(self, bContext = None):
  638. finish_drivers(self)
  639. class LinkLimitDistance(MantisLinkNode):
  640. def __init__(self, signature, base_tree):
  641. super().__init__(signature, base_tree)
  642. inputs = [
  643. "Input Relationship" ,
  644. "Head/Tail" ,
  645. "UseBBone" ,
  646. "Distance" ,
  647. "Clamp Region" ,
  648. "Affect Transform" ,
  649. "Owner Space" ,
  650. "Target Space" ,
  651. "Influence" ,
  652. "Target" ,
  653. "Enable" ,
  654. ]
  655. additional_parameters = { "Name":None }
  656. self.inputs.init_sockets(inputs)
  657. self.outputs.init_sockets(["Output Relationship"])
  658. self.init_parameters(additional_parameters=additional_parameters)
  659. self.set_traverse([("Input Relationship", "Output Relationship")])
  660. def GetxForm(self):
  661. return GetxForm(self)
  662. def bExecute(self, context):
  663. prepare_parameters(self)
  664. print(wrapGreen("Creating ")+wrapWhite("Limit Distance")+
  665. wrapGreen(" Constraint for bone: ") +
  666. wrapOrange(self.GetxForm().bGetObject().name))
  667. c = self.GetxForm().bGetObject().constraints.new('LIMIT_DISTANCE')
  668. self.get_target_and_subtarget(c)
  669. if constraint_name := self.evaluate_input("Name"):
  670. c.name = constraint_name
  671. self.bObject = c
  672. #
  673. # TODO: set distance automagically
  674. # IMPORTANT TODO BUG
  675. custom_space_owner, custom_space_target = False, False
  676. if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
  677. custom_space_owner=True
  678. c.owner_space='CUSTOM'
  679. xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
  680. if isinstance(xf, Bone):
  681. c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
  682. else:
  683. c.space_object=xf
  684. if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
  685. custom_space_target=True
  686. c.target_space='CUSTOM'
  687. xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
  688. if isinstance(xf, Bone):
  689. c.space_object=self.inputs["Target Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
  690. else:
  691. c.space_object=xf
  692. props_sockets = {
  693. 'distance' : ("Distance", 0),
  694. 'head_tail' : ("Head/Tail", 0),
  695. 'limit_mode' : ("Clamp Region", "LIMITDIST_INSIDE"),
  696. 'use_bbone_shape' : ("UseBBone", False),
  697. 'use_transform_limit' : ("Affect Transform", 1),
  698. 'owner_space' : ("Owner Space", 1),
  699. 'target_space' : ("Target Space", 1),
  700. 'influence' : ("Influence", 1),
  701. 'mute' : ("Enable", True),
  702. }
  703. if custom_space_owner: del props_sockets['owner_space']
  704. if custom_space_target: del props_sockets['target_space']
  705. #
  706. evaluate_sockets(self, c, props_sockets)
  707. self.executed = True
  708. def bFinalize(self, bContext = None):
  709. finish_drivers(self)
  710. # Tracking
  711. class LinkStretchTo(MantisLinkNode):
  712. def __init__(self, signature, base_tree):
  713. super().__init__(signature, base_tree)
  714. inputs = [
  715. "Input Relationship" ,
  716. "Head/Tail" ,
  717. "UseBBone" ,
  718. "Original Length" ,
  719. "Volume Variation" ,
  720. "Use Volume Min" ,
  721. "Volume Min" ,
  722. "Use Volume Max" ,
  723. "Volume Max" ,
  724. "Smooth" ,
  725. "Maintain Volume" ,
  726. "Rotation" ,
  727. "Influence" ,
  728. "Target" ,
  729. "Enable" ,
  730. ]
  731. additional_parameters = { "Name":None }
  732. self.inputs.init_sockets(inputs)
  733. self.outputs.init_sockets(["Output Relationship"])
  734. self.init_parameters(additional_parameters=additional_parameters)
  735. self.set_traverse([("Input Relationship", "Output Relationship")])
  736. def GetxForm(self):
  737. return GetxForm(self)
  738. def bExecute(self, context):
  739. prepare_parameters(self)
  740. print(wrapGreen("Creating ")+wrapWhite("Stretch-To")+
  741. wrapGreen(" Constraint for bone: ") +
  742. wrapOrange(self.GetxForm().bGetObject().name))
  743. c = self.GetxForm().bGetObject().constraints.new('STRETCH_TO')
  744. self.get_target_and_subtarget(c)
  745. if constraint_name := self.evaluate_input("Name"):
  746. c.name = constraint_name
  747. self.bObject = c
  748. props_sockets = {
  749. 'head_tail' : ("Head/Tail", 0),
  750. 'use_bbone_shape' : ("UseBBone", False),
  751. 'bulge' : ("Volume Variation", 0),
  752. 'use_bulge_min' : ("Use Volume Min", False),
  753. 'bulge_min' : ("Volume Min", 0),
  754. 'use_bulge_max' : ("Use Volume Max", False),
  755. 'bulge_max' : ("Volume Max", 0),
  756. 'bulge_smooth' : ("Smooth", 0),
  757. 'volume' : ("Maintain Volume", 'VOLUME_XZX'),
  758. 'keep_axis' : ("Rotation", 'PLANE_X'),
  759. 'rest_length' : ("Original Length", self.GetxForm().bGetObject().bone.length),
  760. 'influence' : ("Influence", 1),
  761. 'mute' : ("Enable", True),
  762. }
  763. evaluate_sockets(self, c, props_sockets)
  764. if (self.evaluate_input("Original Length") == 0):
  765. # this is meant to be set automatically.
  766. c.rest_length = self.GetxForm().bGetObject().bone.length
  767. self.executed = True
  768. def bFinalize(self, bContext = None):
  769. finish_drivers(self)
  770. class LinkDampedTrack(MantisLinkNode):
  771. def __init__(self, signature, base_tree):
  772. super().__init__(signature, base_tree)
  773. inputs = [
  774. "Input Relationship" ,
  775. "Head/Tail" ,
  776. "UseBBone" ,
  777. "Track Axis" ,
  778. "Influence" ,
  779. "Target" ,
  780. "Enable" ,
  781. ]
  782. additional_parameters = { "Name":None }
  783. self.inputs.init_sockets(inputs)
  784. self.outputs.init_sockets(["Output Relationship"])
  785. self.init_parameters(additional_parameters=additional_parameters)
  786. self.set_traverse([("Input Relationship", "Output Relationship")])
  787. def GetxForm(self):
  788. return GetxForm(self)
  789. def bExecute(self, context):
  790. prepare_parameters(self)
  791. print(wrapGreen("Creating ")+wrapWhite("Damped Track")+
  792. wrapGreen(" Constraint for bone: ") +
  793. wrapOrange(self.GetxForm().bGetObject().name))
  794. c = self.GetxForm().bGetObject().constraints.new('DAMPED_TRACK')
  795. self.get_target_and_subtarget(c)
  796. if constraint_name := self.evaluate_input("Name"):
  797. c.name = constraint_name
  798. self.bObject = c
  799. props_sockets = {
  800. 'head_tail' : ("Head/Tail", 0),
  801. 'use_bbone_shape' : ("UseBBone", False),
  802. 'track_axis' : ("Track Axis", 'TRACK_Y'),
  803. 'influence' : ("Influence", 1),
  804. 'mute' : ("Enable", True),
  805. }
  806. evaluate_sockets(self, c, props_sockets)
  807. self.executed = True
  808. def bFinalize(self, bContext = None):
  809. finish_drivers(self)
  810. class LinkLockedTrack(MantisLinkNode):
  811. def __init__(self, signature, base_tree):
  812. super().__init__(signature, base_tree)
  813. inputs = [
  814. "Input Relationship" ,
  815. "Head/Tail" ,
  816. "UseBBone" ,
  817. "Track Axis" ,
  818. "Lock Axis" ,
  819. "Influence" ,
  820. "Target" ,
  821. "Enable" ,
  822. ]
  823. additional_parameters = { "Name":None }
  824. self.inputs.init_sockets(inputs)
  825. self.outputs.init_sockets(["Output Relationship"])
  826. self.init_parameters(additional_parameters=additional_parameters)
  827. self.set_traverse([("Input Relationship", "Output Relationship")])
  828. def GetxForm(self):
  829. return GetxForm(self)
  830. def bExecute(self, context):
  831. prepare_parameters(self)
  832. print(wrapGreen("Creating ")+wrapWhite("Locked Track")+
  833. wrapGreen(" Constraint for bone: ") +
  834. wrapOrange(self.GetxForm().bGetObject().name))
  835. c = self.GetxForm().bGetObject().constraints.new('LOCKED_TRACK')
  836. self.get_target_and_subtarget(c)
  837. if constraint_name := self.evaluate_input("Name"):
  838. c.name = constraint_name
  839. self.bObject = c
  840. props_sockets = {
  841. 'head_tail' : ("Head/Tail", 0),
  842. 'use_bbone_shape' : ("UseBBone", False),
  843. 'track_axis' : ("Track Axis", 'TRACK_Y'),
  844. 'lock_axis' : ("Lock Axis", 'UP_X'),
  845. 'influence' : ("Influence", 1),
  846. 'mute' : ("Enable", True),
  847. }
  848. evaluate_sockets(self, c, props_sockets)
  849. self.executed = True
  850. def bFinalize(self, bContext = None):
  851. finish_drivers(self)
  852. class LinkTrackTo(MantisLinkNode):
  853. def __init__(self, signature, base_tree):
  854. super().__init__(signature, base_tree)
  855. inputs = [
  856. "Input Relationship" ,
  857. "Head/Tail" ,
  858. "UseBBone" ,
  859. "Track Axis" ,
  860. "Up Axis" ,
  861. "Use Target Z" ,
  862. "Influence" ,
  863. "Target" ,
  864. "Enable" ,
  865. ]
  866. additional_parameters = { "Name":None }
  867. self.inputs.init_sockets(inputs)
  868. self.outputs.init_sockets(["Output Relationship"])
  869. self.init_parameters(additional_parameters=additional_parameters)
  870. self.set_traverse([("Input Relationship", "Output Relationship")])
  871. def GetxForm(self):
  872. return GetxForm(self)
  873. def bExecute(self, context):
  874. prepare_parameters(self)
  875. print(wrapGreen("Creating ")+wrapWhite("Track-To")+
  876. wrapGreen(" Constraint for bone: ") +
  877. wrapOrange(self.GetxForm().bGetObject().name))
  878. c = self.GetxForm().bGetObject().constraints.new('TRACK_TO')
  879. self.get_target_and_subtarget(c)
  880. if constraint_name := self.evaluate_input("Name"):
  881. c.name = constraint_name
  882. self.bObject = c
  883. props_sockets = {
  884. 'head_tail' : ("Head/Tail", 0),
  885. 'use_bbone_shape' : ("UseBBone", False),
  886. 'track_axis' : ("Track Axis", "TRACK_Y"),
  887. 'up_axis' : ("Up Axis", "UP_Z"),
  888. 'use_target_z' : ("Use Target Z", False),
  889. 'influence' : ("Influence", 1),
  890. 'mute' : ("Enable", True),
  891. }
  892. evaluate_sockets(self, c, props_sockets)
  893. self.executed = True
  894. def bFinalize(self, bContext = None):
  895. finish_drivers(self)
  896. # relationships & misc.
  897. class LinkInheritConstraint(MantisLinkNode):
  898. def __init__(self, signature, base_tree):
  899. super().__init__(signature, base_tree)
  900. inputs = [
  901. "Input Relationship" ,
  902. "Location" ,
  903. "Rotation" ,
  904. "Scale" ,
  905. "Influence" ,
  906. "Target" ,
  907. "Enable" ,
  908. ]
  909. additional_parameters = { "Name":None }
  910. self.inputs.init_sockets(inputs)
  911. self.outputs.init_sockets(["Output Relationship"])
  912. self.init_parameters(additional_parameters=additional_parameters)
  913. self.set_traverse([("Input Relationship", "Output Relationship")])
  914. def GetxForm(self):
  915. return GetxForm(self)
  916. def bExecute(self, context):
  917. prepare_parameters(self)
  918. print(wrapGreen("Creating ")+wrapWhite("Child-Of")+
  919. wrapGreen(" Constraint for bone: ") +
  920. wrapOrange(self.GetxForm().bGetObject().name))
  921. c = self.GetxForm().bGetObject().constraints.new('CHILD_OF')
  922. self.get_target_and_subtarget(c)
  923. if constraint_name := self.evaluate_input("Name"):
  924. c.name = constraint_name
  925. self.bObject = c
  926. props_sockets = {
  927. 'use_location_x' : (("Location", 0) , 1),
  928. 'use_location_y' : (("Location", 1) , 1),
  929. 'use_location_z' : (("Location", 2) , 1),
  930. 'use_rotation_x' : (("Rotation", 0) , 1),
  931. 'use_rotation_y' : (("Rotation", 1) , 1),
  932. 'use_rotation_z' : (("Rotation", 2) , 1),
  933. 'use_scale_x' : (("Scale" , 0) , 1),
  934. 'use_scale_y' : (("Scale" , 1) , 1),
  935. 'use_scale_z' : (("Scale" , 2) , 1),
  936. 'influence' : ( "Influence" , 1),
  937. 'mute' : ("Enable", True),
  938. }
  939. evaluate_sockets(self, c, props_sockets)
  940. c.set_inverse_pending
  941. self.executed = True
  942. def bFinalize(self, bContext = None):
  943. finish_drivers(self)
  944. class LinkInverseKinematics(MantisLinkNode):
  945. def __init__(self, signature, base_tree):
  946. super().__init__(signature, base_tree)
  947. inputs = [
  948. "Input Relationship" ,
  949. "Chain Length" ,
  950. "Use Tail" ,
  951. "Stretch" ,
  952. "Position" ,
  953. "Rotation" ,
  954. "Influence" ,
  955. "Target" ,
  956. "Pole Target" ,
  957. "Enable" ,
  958. ]
  959. additional_parameters = { "Name":None }
  960. self.inputs.init_sockets(inputs)
  961. self.outputs.init_sockets(["Output Relationship"])
  962. self.init_parameters(additional_parameters=additional_parameters)
  963. self.set_traverse([("Input Relationship", "Output Relationship")])
  964. def GetxForm(self):
  965. return GetxForm(self)
  966. def get_base_ik_bone(self, ik_bone):
  967. chain_length : int = (self.evaluate_input("Chain Length"))
  968. if not isinstance(chain_length, (int, float)):
  969. raise GraphError(f"Chain Length must be an integer number in {self}::Chain Length")
  970. if chain_length == 0:
  971. chain_length = int("inf")
  972. base_ik_bone = ik_bone; i=1
  973. while (i<chain_length) and (base_ik_bone.parent):
  974. base_ik_bone=base_ik_bone.parent; i+=1
  975. return base_ik_bone
  976. # We need to do the calculation in a "full circle", meaning the pole_angle
  977. # can go over pi or less than -pi - but the actuall constraint value must
  978. # be clamped in that range.
  979. # so we simply wrap the value.
  980. # not very efficient but it's OK
  981. def set_pole_angle(self, angle: float) -> None:
  982. from math import pi
  983. def wrap(min : float, max : float, value: float) -> float:
  984. range = max-min; remainder = value % range
  985. if remainder > max: return min + remainder-max
  986. else: return remainder
  987. self.bObject.pole_angle = wrap(-pi, pi, angle)
  988. def calc_pole_angle_pre(self, c, ik_bone):
  989. """
  990. This function gets us most of the way to a correct IK pole angle. Unfortunately,
  991. due to the unpredictable nature of the iterative IK calculation, I can't figure
  992. out an exact solution. So we do a bisect search in calc_pole_angle_post().
  993. """
  994. # TODO: instead of these checks, convert all to armature local space. But this is tedious.
  995. if not c.target:
  996. raise GraphError(f"IK Constraint {self} must have target.")
  997. elif c.target.type != "ARMATURE":
  998. raise NotImplementedError(f"Currently, IK Constraint Target for {self} must be a bone within the same armature.")
  999. if c.pole_target.type != "ARMATURE":
  1000. raise NotImplementedError(f"Currently, IK Constraint Pole Target for {self} must be a bone within the same armature.")
  1001. ik_handle = c.target.pose.bones[c.subtarget]
  1002. if ik_handle.id_data != ik_bone.id_data:
  1003. raise NotImplementedError(f"Currently, IK Constraint Target for {self} must be a bone within the same armature.")
  1004. ik_pole = c.pole_target.pose.bones[c.pole_subtarget]
  1005. if ik_pole.id_data != ik_bone.id_data:
  1006. raise NotImplementedError(f"Currently,IK Constraint Pole Target for {self} must be a bone within the same armature.")
  1007. base_ik_bone = self.get_base_ik_bone(ik_bone)
  1008. start_effector = base_ik_bone.bone.head_local
  1009. end_effector = ik_handle.bone.head_local
  1010. pole_location = ik_pole.bone.head_local
  1011. # this is the X-Axis of the bone's rest-pose, added to its bone
  1012. knee_location = base_ik_bone.bone.matrix_local.col[0].xyz+start_effector
  1013. ik_axis = (end_effector-start_effector).normalized()
  1014. from .utilities import project_point_to_plane
  1015. pole_planar_projection = project_point_to_plane(pole_location, start_effector, ik_axis)
  1016. # this planar projection is necessary because the IK axis is different than the base_bone's y axis
  1017. planar_projection = project_point_to_plane(knee_location, start_effector, ik_axis)
  1018. knee_direction =(planar_projection - start_effector).normalized()
  1019. pole_direction =(pole_planar_projection - start_effector).normalized()
  1020. return knee_direction.angle(pole_direction)
  1021. def calc_pole_angle_post(self, c, ik_bone, context):
  1022. """
  1023. This function should give us a completely accurate result for IK.
  1024. """
  1025. from time import time
  1026. start_time=time()
  1027. def signed_angle(vector_u, vector_v, normal):
  1028. # it seems that this fails if the vectors are exactly aligned under certain circumstances.
  1029. angle = vector_u.angle(vector_v, 0.0) # So we use a fallback of 0
  1030. # Normal specifies orientation
  1031. if angle != 0 and vector_u.cross(vector_v).angle(normal) < 1:
  1032. angle = -angle
  1033. return angle
  1034. # we have already checked for valid data.
  1035. ik_handle = c.target.pose.bones[c.subtarget]
  1036. base_ik_bone = self.get_base_ik_bone(ik_bone)
  1037. start_effector = base_ik_bone.bone.head_local
  1038. angle = c.pole_angle
  1039. dg = context.view_layer.depsgraph
  1040. dg.update()
  1041. ik_axis = (ik_handle.bone.head_local-start_effector).normalized()
  1042. center_point = start_effector +(ik_axis*base_ik_bone.bone.length)
  1043. knee_direction = base_ik_bone.bone.tail_local - center_point
  1044. current_knee_direction = base_ik_bone.tail-center_point
  1045. error=signed_angle(current_knee_direction, knee_direction, ik_axis)
  1046. if error == 0:
  1047. prGreen("No Fine-tuning needed."); return
  1048. # Flip it if needed
  1049. dot_before=current_knee_direction.dot(knee_direction)
  1050. if dot_before < 0 and angle!=0: # then it is not aligned and we should check the inverse
  1051. angle = -angle; c.pole_angle=angle
  1052. dg.update()
  1053. current_knee_direction = base_ik_bone.tail-center_point
  1054. dot_after=current_knee_direction.dot(knee_direction)
  1055. if dot_after < dot_before: # they are somehow less aligned
  1056. prPurple("Mantis has gone down an unexpected code path. Please report this as a bug.")
  1057. angle = -angle; self.set_pole_angle(angle)
  1058. dg.update()
  1059. # now we can do a bisect search to find the best value.
  1060. error_threshhold = FLOAT_EPSILON
  1061. max_iterations=600
  1062. error=signed_angle(current_knee_direction, knee_direction, ik_axis)
  1063. if error == 0:
  1064. prGreen("No Fine-tuning needed."); return
  1065. angle+=error
  1066. alt_angle = angle+(error*-2) # should be very near the center when flipped here
  1067. # we still need to bisect search because the relationship of pole_angle <==> error is somewhat unpredictable
  1068. upper_bounds = alt_angle if alt_angle > angle else angle
  1069. lower_bounds = alt_angle if alt_angle < angle else angle
  1070. i=0
  1071. while ( True ):
  1072. if (i>=max_iterations):
  1073. prOrange(f"IK Pole Angle Set reached max iterations of {i} in {time()-start_time} seconds")
  1074. break
  1075. if (abs(error)<error_threshhold) or (upper_bounds<=lower_bounds):
  1076. prPurple(f"IK Pole Angle Set converged after {i} iterations with error={error} in {time()-start_time} seconds")
  1077. break
  1078. # get the center-point betweeen the bounds
  1079. try_angle = lower_bounds + (upper_bounds-lower_bounds)/2
  1080. self.set_pole_angle(try_angle); dg.update()
  1081. error=signed_angle((base_ik_bone.tail-center_point), knee_direction, ik_axis)
  1082. if error>0: upper_bounds=try_angle
  1083. if error<0: lower_bounds=try_angle
  1084. i+=1
  1085. def bExecute(self, context):
  1086. prepare_parameters(self)
  1087. print(wrapGreen("Creating ")+wrapOrange("Inverse Kinematics")+
  1088. wrapGreen(" Constraint for bone: ") +
  1089. wrapOrange(self.GetxForm().bGetObject().name))
  1090. ik_bone = self.GetxForm().bGetObject()
  1091. c = self.GetxForm().bGetObject().constraints.new('IK')
  1092. self.get_target_and_subtarget(c)
  1093. self.get_target_and_subtarget(c, input_name = 'Pole Target')
  1094. if constraint_name := self.evaluate_input("Name"):
  1095. c.name = constraint_name
  1096. self.bObject = c
  1097. c.chain_count = 1 # so that, if there are errors, this doesn't print a whole bunch of circular dependency crap from having infinite chain length
  1098. if (c.pole_target): # Calculate the pole angle, the user shouldn't have to.
  1099. # my_xf = self.GetxForm()
  1100. # from .xForm_containers import xFormBone
  1101. # if not isinstance(my_xf, xFormBone):
  1102. # raise GraphError(f"ERROR: Pole Target must be ")
  1103. # if c.target !=
  1104. self.set_pole_angle(self.calc_pole_angle_pre(c, ik_bone))
  1105. props_sockets = {
  1106. 'chain_count' : ("Chain Length", 1),
  1107. 'use_tail' : ("Use Tail", True),
  1108. 'use_stretch' : ("Stretch", True),
  1109. "weight" : ("Position", 1.0),
  1110. "orient_weight" : ("Rotation", 0.0),
  1111. "influence" : ("Influence", 1.0),
  1112. 'mute' : ("Enable", True),
  1113. }
  1114. evaluate_sockets(self, c, props_sockets)
  1115. # TODO: handle drivers
  1116. # (it should be assumed we want it on if it's plugged
  1117. # into a driver).
  1118. c.use_location = self.evaluate_input("Position") > 0
  1119. c.use_rotation = self.evaluate_input("Rotation") > 0
  1120. self.executed = True
  1121. def bFinalize(self, bContext = None):
  1122. # adding a test here
  1123. if bContext:
  1124. ik_bone = self.GetxForm().bGetObject(mode='POSE')
  1125. if self.bObject.pole_target:
  1126. prWhite(f"Fine-tuning IK Pole Angle for {self}")
  1127. self.calc_pole_angle_post(self.bObject, ik_bone, bContext)
  1128. finish_drivers(self)
  1129. def ik_report_error(pb, context, do_print=False):
  1130. dg = context.view_layer.depsgraph
  1131. dg.update()
  1132. loc1, rot_quaternion1, scl1 = pb.matrix.decompose()
  1133. loc2, rot_quaternion2, scl2 = pb.bone.matrix_local.decompose()
  1134. location_error=(loc1-loc2).length
  1135. rotation_error = rot_quaternion1.rotation_difference(rot_quaternion2).angle
  1136. scale_error = (scl1-scl2).length
  1137. if location_error < FLOAT_EPSILON: location_error = 0
  1138. if abs(rotation_error) < FLOAT_EPSILON: rotation_error = 0
  1139. if scale_error < FLOAT_EPSILON: scale_error = 0
  1140. if do_print:
  1141. print (f"IK Location Error: {location_error}")
  1142. print (f"IK Rotation Error: {rotation_error}")
  1143. print (f"IK Scale Error : {scale_error}")
  1144. return (location_error, rotation_error, scale_error)
  1145. # This is kinda a weird design decision?
  1146. class LinkDrivenParameter(MantisLinkNode):
  1147. '''A node representing an armature object'''
  1148. def __init__(self, signature, base_tree):
  1149. self.base_tree=base_tree
  1150. inputs = [
  1151. "Input Relationship" ,
  1152. "Value" ,
  1153. "Parameter" ,
  1154. "Index" ,
  1155. ]
  1156. self.signature = signature
  1157. additional_parameters = { "Name":None }
  1158. self.inputs.init_sockets(inputs)
  1159. self.outputs.init_sockets(["Output Relationship"])
  1160. self.init_parameters(additional_parameters=additional_parameters)
  1161. self.set_traverse([("Input Relationship", "Output Relationship")])
  1162. def GetxForm(self):
  1163. return GetxForm(self)
  1164. def bExecute(self, bContext = None,):
  1165. prepare_parameters(self)
  1166. prGreen("Executing Driven Parameter node")
  1167. prop = self.evaluate_input("Parameter")
  1168. index = self.evaluate_input("Index")
  1169. value = self.evaluate_input("Value")
  1170. xf = self.GetxForm()
  1171. ob = xf.bGetObject(mode="POSE")
  1172. # IMPORTANT: this node only works on pose bone attributes.
  1173. self.bObject = ob
  1174. length=1
  1175. if hasattr(ob, prop):
  1176. try:
  1177. length = len(getattr(ob, prop))
  1178. except TypeError:
  1179. pass
  1180. except AttributeError:
  1181. pass
  1182. else:
  1183. raise AttributeError(f"Cannot Set value {prop} on object because it does not exist.")
  1184. def_value = 0.0
  1185. if length>1:
  1186. def_value=[0.0]*length
  1187. self.parameters["Value"] = tuple( 0.0 if i != index else value for i in range(length))
  1188. props_sockets = {
  1189. prop: ("Value", def_value)
  1190. }
  1191. evaluate_sockets(self, ob, props_sockets)
  1192. self.executed = True
  1193. def bFinalize(self, bContext = None):
  1194. driver = self.evaluate_input("Value")
  1195. try:
  1196. for i, val in enumerate(self.parameters["Value"]):
  1197. from .drivers import MantisDriver
  1198. if isinstance(val, MantisDriver):
  1199. driver["ind"] = i
  1200. val = driver
  1201. except AttributeError:
  1202. self.parameters["Value"] = driver
  1203. except TypeError:
  1204. self.parameters["Value"] = driver
  1205. finish_drivers(self)
  1206. class LinkArmature(MantisLinkNode):
  1207. '''A node representing an armature object'''
  1208. def __init__(self, signature, base_tree,):
  1209. super().__init__(signature, base_tree)
  1210. inputs = [
  1211. "Input Relationship" ,
  1212. "Preserve Volume" ,
  1213. "Use Envelopes" ,
  1214. "Use Current Location" ,
  1215. "Influence" ,
  1216. "Enable" ,
  1217. ]
  1218. additional_parameters = { "Name":None }
  1219. self.inputs.init_sockets(inputs)
  1220. self.outputs.init_sockets(["Output Relationship"])
  1221. self.init_parameters(additional_parameters=additional_parameters)
  1222. self.set_traverse([("Input Relationship", "Output Relationship")])
  1223. setup_custom_props(self)
  1224. def GetxForm(self):
  1225. return GetxForm(self)
  1226. def bExecute(self, bContext = None,):
  1227. prGreen("Creating Armature Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
  1228. prepare_parameters(self)
  1229. c = self.GetxForm().bGetObject().constraints.new('ARMATURE')
  1230. if constraint_name := self.evaluate_input("Name"):
  1231. c.name = constraint_name
  1232. self.bObject = c
  1233. # get number of targets
  1234. num_targets = len( list(self.inputs.values())[6:] )//2
  1235. props_sockets = {
  1236. 'use_deform_preserve_volume' : ("Preserve Volume", 0),
  1237. 'use_bone_envelopes' : ("Use Envelopes", 0),
  1238. 'use_current_location' : ("Use Current Location", 0),
  1239. 'influence' : ( "Influence" , 1),
  1240. 'mute' : ("Enable", True),
  1241. }
  1242. targets_weights = {}
  1243. for i in range(num_targets):
  1244. target = c.targets.new()
  1245. target_input_name = list(self.inputs.keys())[i*2+6 ]
  1246. weight_input_name = list(self.inputs.keys())[i*2+6+1]
  1247. self.get_target_and_subtarget(target, target_input_name)
  1248. weight_value=self.evaluate_input(weight_input_name)
  1249. if not isinstance(weight_value, float):
  1250. weight_value=0
  1251. targets_weights[i]=weight_value
  1252. props_sockets["targets[%d].weight" % i] = (weight_input_name, 0)
  1253. # targets_weights.append({"weight":(weight_input_name, 0)})
  1254. evaluate_sockets(self, c, props_sockets)
  1255. for target, value in targets_weights.items():
  1256. c.targets[target].weight=value
  1257. # for i, (target, weight) in enumerate(zip(c.targets, targets_weights)):
  1258. # evaluate_sockets(self, target, weight)
  1259. self.executed = True
  1260. def bFinalize(self, bContext = None):
  1261. finish_drivers(self)
  1262. class LinkSplineIK(MantisLinkNode):
  1263. '''A node representing an armature object'''
  1264. def __init__(self, signature, base_tree):
  1265. super().__init__(signature, base_tree)
  1266. inputs = [
  1267. "Input Relationship" ,
  1268. "Target" ,
  1269. "Chain Length" ,
  1270. "Even Divisions" ,
  1271. "Chain Offset" ,
  1272. "Use Curve Radius" ,
  1273. "Y Scale Mode" ,
  1274. "XZ Scale Mode" ,
  1275. "Use Original Scale" ,
  1276. "Influence" ,
  1277. ]
  1278. additional_parameters = { "Name":None }
  1279. self.inputs.init_sockets(inputs)
  1280. self.outputs.init_sockets(["Output Relationship"])
  1281. self.init_parameters(additional_parameters=additional_parameters)
  1282. self.set_traverse([("Input Relationship", "Output Relationship")])
  1283. def GetxForm(self):
  1284. return GetxForm(self)
  1285. def bExecute(self, bContext = None,):
  1286. prepare_parameters(self)
  1287. prGreen("Creating Spline-IK Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
  1288. c = self.GetxForm().bGetObject().constraints.new('SPLINE_IK')
  1289. self.get_target_and_subtarget(c)
  1290. if constraint_name := self.evaluate_input("Name"):
  1291. c.name = constraint_name
  1292. self.bObject = c
  1293. props_sockets = {
  1294. 'chain_count' : ("Chain Length", 0),
  1295. 'use_even_divisions' : ("Even Divisions", False),
  1296. 'use_chain_offset' : ("Chain Offset", False),
  1297. 'use_curve_radius' : ("Use Curve Radius", False),
  1298. 'y_scale_mode' : ("Y Scale Mode", "FIT_CURVE"),
  1299. 'xz_scale_mode' : ("XZ Scale Mode", "NONE"),
  1300. 'use_original_scale' : ("Use Original Scale", False),
  1301. 'influence' : ("Influence", 1),
  1302. }
  1303. evaluate_sockets(self, c, props_sockets)
  1304. self.executed = True