link_containers.py 63 KB

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