deformer_containers.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. from .node_container_common import *
  2. from .xForm_containers import xFormGeometryObject
  3. from .misc_containers import InputExistingGeometryObject
  4. from .base_definitions import MantisNode
  5. from .utilities import (prRed, prGreen, prPurple, prWhite, prOrange,
  6. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  7. wrapOrange,)
  8. def TellClasses():
  9. return [
  10. DeformerArmature,
  11. DeformerHook,
  12. DeformerMorphTarget,
  13. DeformerMorphTargetDeform,
  14. ]
  15. def trace_xForm_back(nc, socket):
  16. from .xForm_containers import xFormGeometryObject
  17. from .misc_containers import InputExistingGeometryObject
  18. from bpy.types import Object
  19. if (trace := trace_single_line(nc, socket)[0] ) :
  20. for i in range(len(trace)): # have to look in reverse, actually
  21. if ( isinstance(trace[ i ], xFormGeometryObject ) ) or ( isinstance(trace[ i ], InputExistingGeometryObject ) ):
  22. return trace[ i ].bGetObject()
  23. raise GraphError(wrapRed(f"No other object found for {nc}."))
  24. def default_evaluate_input(nc, input_name):
  25. # duped from link_containers... should be common?
  26. # should catch 'Target', 'Pole Target' and ArmatureConstraint targets, too
  27. if ('Target' in input_name) and input_name != "Target Space":
  28. socket = nc.inputs.get(input_name)
  29. if socket.is_linked:
  30. return socket.links[0].from_node
  31. return None
  32. else:
  33. return evaluate_input(nc, input_name)
  34. # semi-duplicated from link_containers
  35. def GetxForm(nc):
  36. trace = trace_single_line_up(nc, "Deformer")
  37. for node in trace[0]:
  38. if (node.__class__ in [xFormGeometryObject, InputExistingGeometryObject]):
  39. return node
  40. raise GraphError("%s is not connected to a downstream xForm" % nc)
  41. class DeformerArmature(MantisNode):
  42. '''A node representing an armature deformer'''
  43. def __init__(self, signature, base_tree):
  44. self.base_tree=base_tree
  45. self.signature = signature
  46. self.inputs = {
  47. "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
  48. "Armature Object" : NodeSocket(is_input = True, name = "Armature Object", node = self,),
  49. "Blend Vertex Group" : NodeSocket(is_input = True, name = "Blend Vertex Group", node = self),
  50. "Invert Vertex Group" : NodeSocket(is_input = True, name = "Invert Vertex Group", node = self),
  51. "Preserve Volume" : NodeSocket(is_input = True, name = "Preserve Volume", node = self),
  52. "Use Multi Modifier" : NodeSocket(is_input = True, name = "Use Multi Modifier", node = self),
  53. "Use Envelopes" : NodeSocket(is_input = True, name = "Use Envelopes", node = self),
  54. "Use Vertex Groups" : NodeSocket(is_input = True, name = "Use Vertex Groups", node = self),
  55. "Skinning Method" : NodeSocket(is_input = True, name = "Skinning Method", node = self),
  56. "Deformer" : NodeSocket(is_input = True, name = "Deformer", node = self),
  57. "Copy Skin Weights From" : NodeSocket(is_input = True, name = "Copy Skin Weights From", node = self),
  58. }
  59. self.outputs = {
  60. "Deformer" : NodeSocket(is_input = False, name = "Deformer", node=self), }
  61. self.parameters = {
  62. "Name" : None,
  63. "Armature Object" : None,
  64. "Blend Vertex Group" : None,
  65. "Invert Vertex Group" : None,
  66. "Preserve Volume" : None,
  67. "Use Multi Modifier" : None,
  68. "Use Envelopes" : None,
  69. "Use Vertex Groups" : None,
  70. "Skinning Method" : None,
  71. "Deformer" : None,
  72. "Copy Skin Weights From" : None,
  73. }
  74. # now set up the traverse target...
  75. self.inputs["Deformer"].set_traverse_target(self.outputs["Deformer"])
  76. self.outputs["Deformer"].set_traverse_target(self.inputs["Deformer"])
  77. self.node_type = "LINK"
  78. self.hierarchy_connections, self.connections = [], []
  79. self.hierarchy_dependencies, self.dependencies = [], []
  80. self.prepared = True
  81. self.executed = False
  82. def GetxForm(self, socket="Deformer"):
  83. if socket == "Deformer":
  84. return GetxForm(self)
  85. else:
  86. trace_xForm_back(self, socket)
  87. # DUPLICATED FROM xForm_containers::xFormBone
  88. # DEDUP HACK HACK HACK HACK HACK
  89. def bGetParentArmature(self):
  90. from .xForm_containers import xFormArmature
  91. from .misc_containers import InputExistingGeometryObject
  92. from bpy.types import Object
  93. if (trace := trace_single_line(self, "Armature Object")[0] ) :
  94. for i in range(len(trace)):
  95. # have to look in reverse, actually
  96. if ( isinstance(trace[ i ], xFormArmature ) ):
  97. return trace[ i ].bGetObject()
  98. elif ( isinstance(trace[i], InputExistingGeometryObject)):
  99. if (ob := trace[i].bGetObject()).type == "ARMATURE":
  100. return ob
  101. raise RuntimeError(f"Cannot find armature for node {self}")
  102. return None
  103. #should do the trick...
  104. def bExecute(self, bContext = None,):
  105. self.executed = True
  106. def initialize_vgroups(self,):
  107. ob = self.GetxForm().bGetObject()
  108. armOb = self.bGetParentArmature()
  109. for b in armOb.data.bones:
  110. if b.use_deform == False:
  111. continue
  112. vg = ob.vertex_groups.get(b.name)
  113. if not vg:
  114. vg = ob.vertex_groups.new(name=b.name)
  115. num_verts = len(ob.data.vertices)
  116. vg.add(range(num_verts), 0, 'REPLACE')
  117. def copy_weights(self):
  118. # we'll use modifiers for this, maybe use GN for it in the future tho
  119. import bpy
  120. ob = self.GetxForm().bGetObject()
  121. try:
  122. copy_from = self.GetxForm(socket="Copy Skin Weights From")
  123. except GraphError:
  124. copy_from = None
  125. prRed(f"No object found for copying weights in {self}, continuing anyway.")
  126. m = ob.modifiers.new(type="DATA_TRANSFER", name="Mantis_temp_data_transfer")
  127. m.object = None; m.use_vert_data = True
  128. m.data_types_verts = {'VGROUP_WEIGHTS'}
  129. m.vert_mapping = 'POLYINTERP_NEAREST'
  130. m.layers_vgroup_select_src = 'ALL'
  131. m.layers_vgroup_select_dst = 'NAME'
  132. m.object = copy_from
  133. # m.use_object_transform = False # testing reveals that this is undesirable - since the objects may not have their transforms applied.
  134. ob.modifiers.move(len(ob.modifiers)-1, 0)
  135. # ob.data = ob.data.copy()
  136. if False: #MAYBE the mouse needs to be in the 3D viewport, no idea how to set this in an override
  137. # TODO: figure out how to apply this, context is incorrect because armature is still in pose mode
  138. original_active = bpy.context.active_object
  139. original_mode = original_active.mode
  140. bpy.ops.object.mode_set(mode='OBJECT')
  141. with bpy.context.temp_override(**{'active_object':ob, 'selected_objects':[ob, copy_from]}):
  142. # bpy.ops.object.datalayout_transfer(modifier=m.name) # note: this operator is used by the modifier or stand-alone in the UI
  143. # the poll for this operator is defined in blender/source/blender/editors/object/object_data_transfer.cc
  144. # and blender/source/blender/editors/object/object_modifier.cc
  145. # bpy.ops.object.modifier_apply(modifier=m.name, single_user=True)
  146. bpy.ops.object.datalayout_transfer(data_type='VGROUP_WEIGHTS')
  147. bpy.ops.object.data_transfer(data_type='VGROUP_WEIGHTS')
  148. bpy.ops.object.mode_set(mode=original_mode)
  149. def bFinalize(self, bContext=None):
  150. prGreen("Executing Armature Deform Node")
  151. mod_name = self.evaluate_input("Name")
  152. d = self.GetxForm().bGetObject().modifiers.new(mod_name, type='ARMATURE')
  153. if d is None:
  154. raise RuntimeError(f"Modifier was not created in node {self} -- the object is invalid.")
  155. self.bObject = d
  156. d.object = self.bGetParentArmature()
  157. props_sockets = {
  158. 'vertex_group' : ("Blend Vertex Group", ""),
  159. 'invert_vertex_group' : ("Invert Vertex Group", ""),
  160. 'use_deform_preserve_volume' : ("Preserve Volume", False),
  161. 'use_multi_modifier' : ("Use Multi Modifier", False),
  162. 'use_bone_envelopes' : ("Use Envelopes", False),
  163. 'use_vertex_groups' : ("Use Vertex Groups", False),
  164. }
  165. evaluate_sockets(self, d, props_sockets)
  166. #
  167. if (skin_method := self.evaluate_input("Skinning Method")) == "AUTOMATIC_HEAT":
  168. # This is bad and leads to somewhat unpredictable
  169. # behaviour, e.g. what object will be selected? What mode?
  170. # also bpy.ops is ugly and prone to error when used in
  171. # scripts. I don't intend to use bpy.ops when I can avoid it.
  172. import bpy
  173. self.initialize_vgroups()
  174. bContext.view_layer.depsgraph.update()
  175. ob = self.GetxForm().bGetObject()
  176. armOb = self.bGetParentArmature()
  177. deform_bones = []
  178. for pb in armOb.pose.bones:
  179. if pb.bone.use_deform == True:
  180. deform_bones.append(pb)
  181. context_override = {
  182. 'active_object':ob,
  183. 'selected_objects':[ob, armOb],
  184. 'active_pose_bone':deform_bones[0],
  185. 'selected_pose_bones':deform_bones,}
  186. #
  187. # with bContext.temp_override(**{'active_object':armOb}):
  188. # bpy.ops.object.mode_set(mode='POSE')
  189. # bpy.ops.pose.select_all(action='SELECT')
  190. for b in armOb.data.bones:
  191. b.select = True
  192. with bContext.temp_override(**context_override):
  193. bpy.ops.paint.weight_paint_toggle()
  194. bpy.ops.paint.weight_from_bones(type='AUTOMATIC')
  195. bpy.ops.paint.weight_paint_toggle()
  196. for b in armOb.data.bones:
  197. b.select = False
  198. #
  199. # with bContext.temp_override(**{'active_object':armOb}):
  200. # bpy.ops.object.mode_set(mode='POSE')
  201. # bpy.ops.pose.select_all(action='DESELECT')
  202. # bpy.ops.object.mode_set(mode='OBJECT')
  203. # TODO: modify Blender to make this available as a Python API function.
  204. elif skin_method == "EXISTING_GROUPS":
  205. pass
  206. elif skin_method == "COPY_FROM_OBJECT":
  207. self.initialize_vgroups()
  208. self.copy_weights()
  209. class DeformerHook(MantisNode):
  210. '''A node representing a hook deformer'''
  211. def __init__(self, signature, base_tree):
  212. self.base_tree=base_tree
  213. self.signature = signature
  214. self.inputs = {
  215. "Hook Target" : NodeSocket(is_input = True, name = "Hook Target", node = self,),
  216. "Index" : NodeSocket(is_input = True, name = "Index", node = self),
  217. "Deformer" : NodeSocket(is_input = True, name = "Deformer", node = self),
  218. }
  219. self.outputs = {
  220. "Deformer" : NodeSocket(is_input = False, name = "Deformer", node=self), }
  221. self.parameters = {
  222. "Hook Target" : None,
  223. "Index" : None,
  224. "Deformer" : None,
  225. "Name" : None,
  226. }
  227. # now set up the traverse target...
  228. self.inputs["Deformer"].set_traverse_target(self.outputs["Deformer"])
  229. self.outputs["Deformer"].set_traverse_target(self.inputs["Deformer"])
  230. self.node_type = "LINK"
  231. self.hierarchy_connections, self.connections = [], []
  232. self.hierarchy_dependencies, self.dependencies = [], []
  233. self.prepared = True
  234. self.executed = False
  235. def GetxForm(self, socket="Deformer"):
  236. if socket == "Deformer":
  237. return GetxForm(self)
  238. else:
  239. trace_xForm_back(self, socket)
  240. def bExecute(self, bContext = None,):
  241. self.executed = True
  242. def bFinalize(self, bContext=None):
  243. from bpy.types import Bone, PoseBone, Object
  244. prGreen(f"Executing Hook Deform Node: {self}")
  245. mod_name = self.evaluate_input("Name")
  246. target_node = self.evaluate_input('Hook Target')
  247. target = target_node.bGetObject(); subtarget = ""
  248. if isinstance(target, Bone) or isinstance(target, PoseBone):
  249. subtarget = target.name; target = target.id_data
  250. ob=self.GetxForm().bGetObject()
  251. reuse = False
  252. for m in ob.modifiers:
  253. if m.type == 'HOOK' and m.object == target and m.subtarget == subtarget:
  254. d = m; reuse = True; break
  255. else:
  256. d = ob.modifiers.new(mod_name, type='HOOK')
  257. if d is None:
  258. raise RuntimeError(f"Modifier was not created in node {self} -- the object is invalid.")
  259. get_target_and_subtarget(self, d, input_name="Hook Target")
  260. vertices_used=[]
  261. if reuse: # Get the verts in the list... filter out all the unneeded 0's
  262. vertices_used = list(d.vertex_indices)
  263. include_0 = 0 in vertices_used
  264. vertices_used = list(filter(lambda a : a != 0, vertices_used))
  265. if include_0: vertices_used.append(0)
  266. # now we add the selected vertex to the list, too
  267. vertices_used.append(self.evaluate_input("Index"))
  268. d.vertex_indices_set(vertices_used)
  269. # todo: this should be able to take many indices in the future.
  270. # since this only takes a single index, I can always hack together a shape-key solution to tilt...
  271. # GN solution would work too, provided a separate object is made for it
  272. # I like this, it is perhaps a little innefficient but can be improved later on
  273. class DeformerMorphTarget(MantisNode):
  274. '''A node representing an armature deformer'''
  275. def __init__(self, signature, base_tree):
  276. self.base_tree=base_tree
  277. self.signature = signature
  278. self.inputs = {
  279. "Relative to" : NodeSocket(is_input = True, name = "Relative To", node = self,),
  280. "Object" : NodeSocket(is_input = True, name = "Object", node = self,),
  281. "Deformer" : NodeSocket(is_input = True, name = "Deformer", node = self),
  282. "Vertex Group" : NodeSocket(is_input = True, name = "Vertex Group", node = self),
  283. }
  284. self.outputs = {
  285. "Deformer" : NodeSocket(is_input = False, name = "Deformer", node=self),
  286. "Morph Target" : NodeSocket(is_input = False, name = "Morph Target", node=self), }
  287. self.parameters = {
  288. "Name" : None,
  289. "Relative to" : None,
  290. "Object" : None,
  291. "Morph Target" : None,
  292. "Deformer" : None,
  293. "Vertex Group" : None,
  294. }
  295. # now set up the traverse target...
  296. self.inputs["Deformer"].set_traverse_target(self.outputs["Deformer"])
  297. self.outputs["Deformer"].set_traverse_target(self.inputs["Deformer"])
  298. self.node_type = "LINK"
  299. self.hierarchy_connections, self.connections = [], []
  300. self.hierarchy_dependencies, self.dependencies = [], []
  301. self.prepared = True
  302. self.executed = False
  303. def GetxForm(self, trace_input="Object"):
  304. trace = trace_single_line(self, trace_input)
  305. for node in trace[0]:
  306. if (node.__class__ in [xFormGeometryObject, InputExistingGeometryObject]):
  307. return node
  308. raise GraphError("%s is not connected to an upstream xForm" % self)
  309. def bExecute(self, bContext = None,):
  310. prGreen("Executing Morph Target Node")
  311. ob = None; relative = None
  312. # do NOT check if the object exists here. Just let the next node deal with that.
  313. try:
  314. ob = self.GetxForm().bGetObject().name
  315. except Exception as e: # this will and should throw an error if it fails
  316. ob = self.GetxForm().evaluate_input("Name")
  317. if self.inputs["Relative to"].is_linked:
  318. try:
  319. relative = self.GetxForm("Relative to").bGetObject().name
  320. except Exception as e: # same here
  321. prRed(f"Execution failed at {self}: no relative object found for morph target, despite link existing.")
  322. raise e
  323. vg = self.evaluate_input("Vertex Group") if self.evaluate_input("Vertex Group") else "" # just make sure it is a string
  324. mt={"object":ob, "vertex_group":vg, "relative_shape":relative}
  325. self.parameters["Morph Target"] = mt
  326. self.parameters["Name"] = ob # this is redundant but it's OK since accessing the mt is tedious
  327. self.executed = True
  328. class DeformerMorphTargetDeform(MantisNode):
  329. '''A node representing an armature deformer'''
  330. def __init__(self, signature, base_tree):
  331. self.base_tree=base_tree
  332. self.signature = signature
  333. self.inputs = {
  334. "Deformer" : NodeSocket(is_input = True, name = "Deformer", node = self),
  335. "Use Shape Key" : NodeSocket(is_input = True, name = "Use Shape Key", node = self),
  336. "Use Offset" : NodeSocket(is_input = True, name = "Use Offset", node = self),
  337. }
  338. self.outputs = {
  339. "Deformer" : NodeSocket(is_input = False, name = "Deformer", node=self), }
  340. self.parameters = {
  341. "Name" : None,
  342. "Deformer" : None,
  343. "Deformer" : None,
  344. "Use Shape Key" : None,
  345. "Use Offset" : None,
  346. }
  347. # now set up the traverse target...
  348. self.inputs["Deformer"].set_traverse_target(self.outputs["Deformer"])
  349. self.outputs["Deformer"].set_traverse_target(self.inputs["Deformer"])
  350. self.node_type = "LINK"
  351. self.hierarchy_connections, self.connections = [], []
  352. self.hierarchy_dependencies, self.dependencies = [], []
  353. self.prepared = True
  354. self.executed = True
  355. self.bObject = None
  356. setup_custom_props(self)
  357. def GetxForm(self):
  358. return GetxForm(self)
  359. # bpy.data.node_groups["Morph Deform.045"].nodes["Named Attribute.020"].data_type = 'FLOAT_VECTOR'
  360. # bpy.context.object.add_rest_position_attribute = True
  361. def gen_morph_target_modifier(self, context):
  362. # first let's see if this is a no-op
  363. targets = []
  364. for k,v in self.inputs.items():
  365. if "Target" in k:
  366. targets.append(v)
  367. if not targets:
  368. return # nothing to do here.
  369. # at this point we make the node tree
  370. from .geometry_node_graphgen import gen_morph_target_nodes
  371. m, props_sockets = gen_morph_target_nodes(
  372. self.evaluate_input("Name"),
  373. self.GetxForm().bGetObject(),
  374. targets,
  375. context,
  376. use_offset=self.evaluate_input("Use Offset"))
  377. self.bObject = m
  378. evaluate_sockets(self, m, props_sockets)
  379. finish_drivers(self)
  380. def gen_shape_key(self, context): # TODO: make this a feature of the node definition that appears only when there are no prior deformers - and shows a warning!
  381. # TODO: the below works well, but it is quite slow. It does not seem to have better performence. Its only advantage is export to FBX.
  382. # there are a number of things I need to fix here
  383. # - reuse shape keys if possible
  384. # - figure out how to make this a lot faster
  385. # - edit the xForm stuff to delete drivers from shape key ID's, since they belong to the Key, not the Object.
  386. # first check if we need to do anythign
  387. targets = []
  388. for k,v in self.inputs.items():
  389. if "Target" in k:
  390. targets.append(v)
  391. if not targets:
  392. return # nothing to do here
  393. from time import time
  394. start_time = time()
  395. from bpy import data
  396. xf = self.GetxForm()
  397. ob = xf.bGetObject()
  398. dg = context.view_layer.depsgraph
  399. dg.update()
  400. if xf.has_shape_keys == False:
  401. m = data.meshes.new_from_object(ob, preserve_all_data_layers=True, depsgraph=dg)
  402. ob.data = m
  403. ob.add_rest_position_attribute = True
  404. ob.shape_key_clear()
  405. ob.shape_key_add(name='Basis', from_mix=False)
  406. else:
  407. m = ob.data
  408. xf.has_shape_keys = True
  409. # using the built-in shapekey feature is actually a lot harder in terms of programming because I need...
  410. # min/max, as it is just not a feature of the GN version
  411. # to carry info from the morph target node regarding relative shapes and vertex groups and all that
  412. # the drivers may be more difficult to apply, too.
  413. # hafta make new geometry for the object and add shape keys and all that
  414. # the benefit to all this being exporting to game engines via .fbx
  415. # first make a basis shape key
  416. keys={}
  417. props_sockets={}
  418. for i, t in enumerate(targets):
  419. mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
  420. if sk_ob is None:
  421. sk_ob = data.objects.new(mt_node.evaluate_input("Name"), data.meshes.new_from_object(ob))
  422. context.collection.objects.link(sk_ob)
  423. prOrange(f"WARN: no object found for f{mt_node}; creating duplicate of current object ")
  424. sk_ob = dg.id_eval_get(sk_ob)
  425. mt_name = sk_ob.name
  426. vg = mt_node.parameters["Morph Target"]["vertex_group"]
  427. if vg: mt_name = mt_name+"."+vg
  428. sk = ob.shape_key_add(name=mt_name, from_mix=False)
  429. # the shapekey data is absolute point data for each vertex, in order, very simple
  430. # SERIOUSLY IMPORTANT:
  431. # use the current position of the vertex AFTER SHAPE KEYS AND DEFORMERS
  432. # easiest way to do it is to eval the depsgraph
  433. # TODO: try and get it without depsgraph update, since that may be (very) slow
  434. sk_m = sk_ob.data#data.meshes.new_from_object(sk_ob, preserve_all_data_layers=True, depsgraph=dg)
  435. for j in range(len(m.vertices)):
  436. sk.data[j].co = sk_m.vertices[j].co # assume they match
  437. # data.meshes.remove(sk_m)
  438. sk.vertex_group = vg
  439. sk.slider_min = -10
  440. sk.slider_max = 10
  441. keys[mt_name]=sk
  442. props_sockets[mt_name]= ("Value."+str(i).zfill(3), 1.0)
  443. for i, t in enumerate(targets):
  444. mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
  445. if sk_ob is None: continue
  446. if rel := mt_node.parameters["Morph Target"]["relative_shape"]:
  447. sk = keys.get(mt_name)
  448. sk.relative_key = keys.get(rel)
  449. self.bObject = sk.id_data
  450. evaluate_sockets(self, sk.id_data, props_sockets)
  451. finish_drivers(self)
  452. prWhite(f"Initializing morph target took {time() -start_time} seconds")
  453. def bFinalize(self, bContext=None):
  454. prGreen(f"Executing Morph Deform node {self}")
  455. # if there is a not a prior deformer then there should be an option to use plain 'ol shape keys
  456. # GN is always desirable as an option though because it can be baked & many other reasons
  457. use_shape_keys = self.evaluate_input("Use Shape Key")
  458. if use_shape_keys: # check and see if we can.
  459. if self.inputs.get("Deformer"): # I guess this isn't available in some node group contexts... bad. FIXME
  460. if (links := self.inputs["Deformer"].links):
  461. if not links[0].from_node.parameters.get("Use Shape Key"):
  462. use_shape_keys = False
  463. elif links[0].from_node.parameters.get("Use Shape Key") == False:
  464. use_shape_keys = False
  465. self.parameters["Use Shape Key"] = use_shape_keys
  466. if use_shape_keys:
  467. self.gen_shape_key(bContext)
  468. else:
  469. self.gen_morph_target_modifier(bContext)