link_containers.py 61 KB

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