xForm_nodes.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. from .node_common import *
  2. from .base_definitions import MantisNode, NodeSocket
  3. from .xForm_socket_templates import *
  4. from .mantis_dataclasses import xForm_info
  5. def TellClasses():
  6. return [
  7. # xForm
  8. xFormArmature,
  9. xFormBone,
  10. xFormGeometryObject,
  11. xFormObjectInstance,
  12. xFormCurvePin,
  13. xFormGetBone,
  14. ]
  15. #*#-------------------------------#++#-------------------------------#*#
  16. # X - F O R M N O D E S
  17. #*#-------------------------------#++#-------------------------------#*#
  18. def reset_object_data(ob):
  19. # moving this to a common function so I can figure out the details later
  20. ob.id_properties_clear() # we must remove the custom properties so we can make them again.
  21. ob.constraints.clear()
  22. ob.animation_data_clear() # this is a little dangerous. TODO find a better solution since this can wipe animation the user wants to keep
  23. ob.modifiers.clear() # I would also like a way to copy modifiers and their settings, or bake them down. oh well
  24. def get_parent_node(mantis_node, type = 'XFORM'):
  25. # type variable for selecting whether to get either
  26. # the parent xForm or the inheritance node
  27. node_line, _last_socket = trace_single_line(mantis_node, "Relationship")
  28. for i in range(len(node_line)):
  29. # check each of the possible parent types.
  30. if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
  31. try: # it's the next one
  32. if (type == 'XFORM'):
  33. return node_line[ i + 1 ]
  34. else: # type = 'LINK'
  35. return node_line[ i ]
  36. except IndexError: # if there is no next one...
  37. return None # then there's no parent!
  38. return None
  39. def get_matrix(node):
  40. matrix = node.evaluate_input('Matrix')
  41. if matrix is None:
  42. node_line, socket = trace_single_line(node, "Matrix")
  43. raise RuntimeError(wrapRed(f"No matrix found for Armature {node}"))
  44. return matrix
  45. def set_object_parent(node):
  46. from bpy import data
  47. parent_xForm_info = get_parent_xForm_info(node)
  48. if parent_xForm_info.object_type == '':
  49. return # no parent
  50. elif parent_xForm_info.object_type in ['armature', 'object']:
  51. parent = data.objects.get(parent_xForm_info.self_edit_name)
  52. if parent_xForm_info.parent_edit_name and parent is None:
  53. raise GraphError(f"Could not get parent object for node {node}.")
  54. node.bGetObject().parent = parent
  55. else: # it is a bone
  56. armOb = data.objects.get(parent_xForm_info.root_armature)
  57. node_ob = node.bGetObject()
  58. node_ob.parent = armOb; node_ob.parent_type = 'BONE'
  59. # this one expects a string.
  60. node_ob.parent_bone = parent_xForm_info.self_edit_name
  61. class xFormNode(MantisNode):
  62. def __init__(self, signature, base_tree, socket_templates=[]):
  63. super().__init__(signature, base_tree, socket_templates)
  64. self.node_type = 'XFORM'
  65. self.bObject=None
  66. # because new objects are created during prep phase
  67. def reset_execution(self):
  68. super().reset_execution()
  69. self.prepared=False
  70. def setup_custom_props(self, ob, input):
  71. from .mantis_dataclasses import custom_prop_template
  72. # detect custom inputs
  73. for i, l in enumerate(self.inputs[input].links):
  74. # we need to trace back all inputs
  75. custom_prop_data = self.evaluate_input(input, i)
  76. if not isinstance(custom_prop_data, custom_prop_template): continue
  77. from .drivers import MantisDriver
  78. name = custom_prop_data.name
  79. if name in ob.keys():
  80. raise RuntimeError("Error: cannot create a custom property with "
  81. "the same name as an existing property.")
  82. prop_type = custom_prop_data.prop_type
  83. match prop_type:
  84. case 'BOOL':
  85. value = custom_prop_data.default_value_bool
  86. case 'INT':
  87. value = custom_prop_data.default_value_int
  88. case 'FLOAT':
  89. value = custom_prop_data.default_value_float
  90. case 'VECTOR':
  91. value = custom_prop_data.default_value_vector
  92. case 'STRING':
  93. value = custom_prop_data.default_value_string
  94. if (value is None):
  95. raise RuntimeError("Could not set value of custom parameter")
  96. # it creates a more confusing error later sometimes, better to catch it here.
  97. ob[name] = value
  98. ui_data = ob.id_properties_ui(name)
  99. ui_data.update(
  100. default=value,
  101. description=custom_prop_data.description)
  102. #if a number, set the min/max values. it may overflow on the C side
  103. if type(value) in [float, int]:
  104. for prop_name in ['min', 'max', 'soft_min', 'soft_max', ]:
  105. prop_value = getattr(custom_prop_data, prop_name)
  106. if type(value) == int: prop_value = int(prop_value)
  107. # DO: figure out the right way to prevent an oveflow
  108. try: # we have to do this as a keyword argument like this
  109. ui_data.update( **{prop_name:prop_value} )
  110. except OverflowError: #this occurs when the value is inf
  111. prRed(f"invalid value {prop_value} for custom prop {prop_name}"
  112. f" of type {type(value)} in {self}. It will remain unset.")
  113. ob.property_overridable_library_set(f"[\"{name}\"]", True)
  114. pass
  115. class xFormArmature(xFormNode):
  116. '''A node representing an armature object'''
  117. def __init__(self, signature, base_tree):
  118. super().__init__(signature, base_tree, xFormArmatureSockets)
  119. self.init_parameters()
  120. self.set_traverse([("Relationship", "xForm Out")])
  121. setup_custom_property_inputs_outputs(self)
  122. def bPrepare(self, bContext=None):
  123. self.parameters['Matrix'] = get_matrix(self)
  124. self.prepared = True
  125. def bTransformPass(self, bContext = None,):
  126. # from .utilities import get_ui_node
  127. import bpy
  128. if (not isinstance(bContext, bpy.types.Context)):
  129. raise RuntimeError("Incorrect context")
  130. name = self.evaluate_input("Name")
  131. matrix = self.parameters['Matrix']
  132. reset_transforms = False
  133. #check if an object by the name exists
  134. if (name) and (ob := bpy.data.objects.get(name)):
  135. if (ob.animation_data):
  136. while (ob.animation_data.drivers):
  137. ob.animation_data.drivers.remove(ob.animation_data.drivers[-1])
  138. for pb in ob.pose.bones:
  139. # clear it, even after deleting the edit bones,
  140. # if we create them again the pose bones will be reused
  141. while (pb.constraints):
  142. pb.constraints.remove(pb.constraints[-1])
  143. if reset_transforms:
  144. pb.location = (0,0,0)
  145. pb.rotation_euler = (0,0,0)
  146. pb.rotation_quaternion = (1.0,0,0,0)
  147. pb.rotation_axis_angle = (0,0,1.0,0)
  148. pb.scale = (1.0,1.0,1.0)
  149. # feels ugly and bad, whatever
  150. collections = []
  151. for bc in ob.data.collections:
  152. collections.append(bc)
  153. for bc in collections:
  154. ob.data.collections.remove(bc)
  155. del collections
  156. # end ugly/bad
  157. else:
  158. # Create the Object
  159. ob = bpy.data.objects.new(name, bpy.data.armatures.new(name)) #create ob
  160. if (ob.name != name):
  161. raise RuntimeError("Could not create xForm object", name)
  162. # it seems this data sticks around even if the object was deleted, so clear it here.
  163. ob.id_properties_clear()
  164. self.bObject = ob.name
  165. ob.matrix_world = matrix.copy()
  166. ob.data.pose_position = 'REST'
  167. set_object_parent(self)
  168. # Link to Scene:
  169. if (ob.name not in bContext.view_layer.active_layer_collection.collection.objects):
  170. bContext.view_layer.active_layer_collection.collection.objects.link(ob)
  171. print( wrapGreen("Created Armature object: ")+ wrapWhite(ob.name))
  172. # Finalize the action
  173. # oddly, overriding context doesn't seem to work
  174. try:
  175. bpy.ops.object.select_all(action='DESELECT')
  176. except RuntimeError:
  177. pass # we're already in edit mode, should be OK to do this.
  178. bContext.view_layer.objects.active = ob
  179. selected=[]
  180. for other_ob in bpy.data.objects:
  181. if other_ob.mode == "EDIT":
  182. selected.append(other_ob)
  183. selected.append(ob)
  184. context_override = {"active_object":ob, "selected_objects":selected}
  185. print("Changing Armature Mode to " +wrapPurple("EDIT"))
  186. with bContext.temp_override(**context_override):
  187. bpy.ops.object.mode_set(mode='EDIT')
  188. if ob.mode != "EDIT":
  189. prRed("eh?")
  190. # clear it
  191. while (len(ob.data.edit_bones) > 0):
  192. ob.data.edit_bones.remove(ob.data.edit_bones[0])
  193. # bContext.view_layer.objects.active = prevAct
  194. # This will OVERWRITE the root armature since bones have to trace back to this one.
  195. root_armature = ob.name
  196. parent_xForm_info = get_parent_xForm_info(self)
  197. my_info = xForm_info(
  198. object_type='armature',
  199. root_armature= ob.name,
  200. parent_pose_name=parent_xForm_info.self_pose_name,
  201. parent_edit_name=parent_xForm_info.self_edit_name,
  202. self_pose_name=ob.name,
  203. self_edit_name=ob.name,
  204. )
  205. self.parameters['xForm Out'] = my_info
  206. self.executed = True
  207. def bFinalize(self, bContext = None):
  208. # custom properties
  209. self.setup_custom_props(self.bGetObject(), "Custom Properties")
  210. finish_drivers(self)
  211. def bGetObject(self, mode = ''):
  212. import bpy; return bpy.data.objects[self.parameters['xForm Out'].self_pose_name]
  213. bone_inputs= [
  214. "Name",
  215. "Rotation Order",
  216. "Matrix",
  217. "Relationship",
  218. # IK settings
  219. "IK Stretch",
  220. "Lock IK",
  221. "IK Stiffness",
  222. "Limit IK",
  223. "X Min",
  224. "X Max",
  225. "Y Min",
  226. "Y Max",
  227. "Z Min",
  228. "Z Max",
  229. # Visual stuff
  230. "Bone Collection",
  231. "Hide",
  232. "Custom Object",
  233. "Custom Object xForm Override",
  234. "Custom Object Scale to Bone Length",
  235. "Custom Object Wireframe",
  236. "Custom Object Scale",
  237. "Custom Object Translation",
  238. "Custom Object Rotation",
  239. "Color",
  240. "Inherit Color",
  241. # Deform Stuff
  242. "Deform",
  243. "Envelope Distance",
  244. "Envelope Weight",
  245. "Envelope Multiply",
  246. "Envelope Head Radius",
  247. "Envelope Tail Radius",
  248. # BBone stuff:
  249. "BBone Segments",
  250. "BBone X Size",
  251. "BBone Z Size",
  252. "BBone HQ Deformation",
  253. "BBone X Curve-In",
  254. "BBone Z Curve-In",
  255. "BBone X Curve-Out",
  256. "BBone Z Curve-Out",
  257. "BBone Roll-In",
  258. "BBone Roll-Out",
  259. "BBone Inherit End Roll",
  260. "BBone Scale-In",
  261. "BBone Scale-Out",
  262. "BBone Ease-In",
  263. "BBone Ease-Out",
  264. "BBone Easing",
  265. "BBone Start Handle Type",
  266. "BBone Custom Start Handle",
  267. "BBone Start Handle Scale",
  268. "BBone Start Handle Ease",
  269. "BBone End Handle Type",
  270. "BBone Custom End Handle",
  271. "BBone End Handle Scale",
  272. "BBone End Handle Ease",
  273. # locks
  274. "Lock Location",
  275. "Lock Rotation",
  276. "Lock Scale",
  277. "Custom Properties",
  278. ]
  279. class xFormBone(xFormNode):
  280. '''A node representing a bone in an armature'''
  281. # DO: make a way to identify which armature this belongs to
  282. def __init__(self, signature, base_tree):
  283. super().__init__(signature, base_tree)
  284. outputs = [
  285. "xForm Out",
  286. ]
  287. self.inputs.init_sockets(bone_inputs)
  288. self.outputs.init_sockets(outputs)
  289. self.socket_templates=xFormBoneSockets
  290. # TODO: implement socket templates completely for Bone
  291. # currently it is waiting on BBone and refactoring/cleanup.
  292. self.init_parameters()
  293. self.set_traverse([("Relationship", "xForm Out")])
  294. setup_custom_property_inputs_outputs(self)
  295. def bGetParentArmature(self):
  296. parent_xForm_info = get_parent_xForm_info(self)
  297. from bpy import data
  298. return data.objects.get(parent_xForm_info.root_armature)
  299. def bSetParent(self, eb):
  300. from bpy import data
  301. parent_xForm_info = get_parent_xForm_info(self)
  302. parent_armature = data.objects.get( parent_xForm_info.root_armature)
  303. if parent_xForm_info.self_edit_name != parent_xForm_info.root_armature:
  304. parent_bone = parent_armature.data.edit_bones.get( parent_xForm_info.self_edit_name)
  305. eb.parent = parent_bone
  306. parent_mantis_node = get_parent_node(self, type = 'LINK') # get the link node.
  307. # TODO probably need to send the parenting info or at least the signatures of intervening nodes.
  308. eb.use_connect = parent_mantis_node.evaluate_input("Connected")
  309. eb.use_inherit_rotation = parent_mantis_node.evaluate_input("Inherit Rotation")
  310. eb.inherit_scale = parent_mantis_node.evaluate_input("Inherit Scale")
  311. def bPrepare(self, bContext=None):
  312. self.parameters['Matrix'] = get_matrix(self)
  313. self.prepared = True
  314. def bTransformPass(self, bContext = None,): #possibly will need to pass context?
  315. import bpy
  316. from mathutils import Vector
  317. if not (name := self.evaluate_input("Name")):
  318. raise RuntimeError(wrapRed(f"Could not set name for bone in {self}"))
  319. if (not isinstance(bContext, bpy.types.Context)):
  320. raise RuntimeError("Incorrect context")
  321. if not (xF := self.bGetParentArmature()):
  322. raise RuntimeError("Could not create edit bone: ", name, " from node:", self, " Reason: No armature object to add bone to.")
  323. matrix = self.parameters['Matrix']
  324. length = matrix[3][3]
  325. if (xF):
  326. if (xF.mode != "EDIT"):
  327. raise RuntimeError("Armature Object Not in Edit Mode, exiting...")
  328. #
  329. # Create the Object
  330. d = xF.data
  331. eb = d.edit_bones.new(name)
  332. # Bone Collections:
  333. # We treat each separate string as a Bone Collection that this object belongs to
  334. # Bone Collections are fully qualified by their hierarchy.
  335. # Separate Strings with "|" and indicate hierarchy with ">". These are special characters.
  336. # NOTE: if the user names the collections differently at different times, this will take the FIRST definition and go with it
  337. if self.inputs['Bone Collection'].links:
  338. bCol_groups = []
  339. for i, l in enumerate(self.inputs['Bone Collection'].links):
  340. bCol_group = self.evaluate_input("Bone Collection", index=i)
  341. bCol_groups.append(bCol_group)
  342. bCols = '|'.join(bCol_groups)
  343. else:
  344. bCols = self.evaluate_input("Bone Collection")
  345. if bCols: # it is actually possible to add a bone to no collections. odd.
  346. bone_collections = bCols.split("|")
  347. for collection_list in bone_collections:
  348. hierarchy = collection_list.split(">")
  349. col_parent = None
  350. for bCol in hierarchy:
  351. if bCol is None: continue # I think this
  352. if bCol == '': continue
  353. if ( col := d.collections_all.get(bCol) ) is None:
  354. col = d.collections.new(bCol)
  355. col.parent = col_parent
  356. col_parent = col
  357. if hierarchy[-1]: # will be "" or None if the box is empty
  358. d.collections_all.get(hierarchy[-1]).assign(eb)
  359. if (eb.name != name):
  360. prRed(f"Expected bone of name: {name}, got {eb.name} instead.")
  361. raise RuntimeError("Could not create bone ", name, "; Perhaps there is a duplicate bone name in the node tree?")
  362. eb.matrix = matrix.copy()
  363. tailoffset = Vector((0,length,0)) #Vector((0,self.tailoffset, 0))
  364. tailoffset = matrix.copy().to_3x3() @ tailoffset
  365. eb.tail = eb.head + tailoffset
  366. if (eb.name != name):
  367. raise RuntimeError("Could not create edit bone: ", name)
  368. assert (eb.name), "Bone must have a name."
  369. self.bObject = eb.name
  370. # The bone should have relationships going in at this point.
  371. self.bSetParent(eb)
  372. if eb.head == eb.tail:
  373. raise RuntimeError(wrapRed(f"Could not create edit bone: {name} because bone head was located in the same place as bone tail."))
  374. # Setup Deform attributes...
  375. eb.use_deform = self.evaluate_input("Deform")
  376. eb.envelope_distance = self.evaluate_input("Envelope Distance")
  377. eb.envelope_weight = self.evaluate_input("Envelope Weight")
  378. eb.use_envelope_multiply = self.evaluate_input("Envelope Multiply")
  379. eb.head_radius = self.evaluate_input("Envelope Head Radius")
  380. eb.tail_radius = self.evaluate_input("Envelope Tail Radius")
  381. print( wrapGreen("Created Bone: ") + wrapOrange(eb.name) + wrapGreen(" in ") + wrapWhite(self.bGetParentArmature().name))
  382. parent_xForm_info = get_parent_xForm_info(self)
  383. my_info = xForm_info(
  384. object_type='bone',
  385. root_armature= xF.name,
  386. parent_pose_name=parent_xForm_info.self_pose_name,
  387. parent_edit_name=parent_xForm_info.self_edit_name,
  388. self_pose_name=eb.name,
  389. self_edit_name=eb.name,
  390. )
  391. self.parameters['xForm Out'] = my_info
  392. self.executed = True
  393. def set_bone_color(self, b, inherit_color, bContext):
  394. color_values = self.evaluate_input('Color')
  395. if color_values is None:
  396. prOrange(f"Warning: No color information found for {b.name}. This should not happen.")
  397. return
  398. if inherit_color and b.parent:
  399. b.color.palette=b.parent.color.palette
  400. if b.color.palette == 'CUSTOM':
  401. b.color.custom.active=b.parent.color.custom.active
  402. b.color.custom.normal=b.parent.color.custom.normal
  403. b.color.custom.select=b.parent.color.custom.select
  404. return
  405. from mathutils import Color
  406. color_active = Color(color_values[:3])
  407. color_normal = Color(color_values[3:6])
  408. color_select = Color(color_values[6:])
  409. is_theme_colors = False
  410. theme = bContext.preferences.themes[0]
  411. for i, color_set in enumerate(theme.bone_color_sets):
  412. if ((color_active == color_set.active) and
  413. (color_normal == color_set.normal) and
  414. (color_select == color_set.select) ):
  415. is_theme_colors=True; break
  416. if is_theme_colors: # add 1, not 0-indexed
  417. b.color.palette = 'THEME'+str(i+1).zfill(2)
  418. elif ((color_active == theme.view_3d.bone_pose_active) and
  419. (color_normal == theme.view_3d.bone_solid) and
  420. (color_select == theme.view_3d.bone_pose) ):
  421. b.color.palette = 'DEFAULT'
  422. else:
  423. b.color.palette = 'CUSTOM'
  424. b.color.custom.active=color_active
  425. b.color.custom.normal=color_normal
  426. b.color.custom.select=color_select
  427. def bFinalize(self, bContext = None):
  428. b = self.bGetParentArmature().data.bones[self.bObject]
  429. # let's do bone colors first
  430. inherit_color = self.evaluate_input("Inherit Color")
  431. if len(self.inputs['Color'].links) > 0:
  432. inherit_color = False # use the link instead
  433. # try: # just in case, this shouldn't cause a failure
  434. self.set_bone_color(b, inherit_color, bContext)
  435. # except Exception as e:
  436. # prRed("WARNING: failed to set color because of error, see report below:")
  437. # prOrange(e)
  438. #
  439. do_bb=False
  440. b.bbone_x = self.evaluate_input("BBone X Size"); b.bbone_x = max(b.bbone_x, 0.0002)
  441. b.bbone_z = self.evaluate_input("BBone Z Size"); b.bbone_z = max(b.bbone_z, 0.0002)
  442. if (segs := self.evaluate_input("BBone Segments")) > 1:
  443. do_bb=True
  444. b.bbone_segments = segs
  445. b.bbone_x = self.evaluate_input("BBone X Size")
  446. b.bbone_z = self.evaluate_input("BBone Z Size")
  447. if self.evaluate_input("BBone HQ Deformation"):
  448. b.bbone_mapping_mode = "CURVED"
  449. # 'bbone_handle_type_start' : ("BBone Start Handle Type", "AUTO"),
  450. # 'bbone_handle_type_end' : ("BBone End Handle Type", "AUTO"),
  451. # 'bbone_custom_handle_start' : ("BBone Custom Start Handle", "AUTO"),
  452. # 'bbone_custom_handle_end' : ("BBone Custom End Handle", "AUTO"),
  453. if handle_type := self.evaluate_input("BBone Start Handle Type"):
  454. b.bbone_handle_type_start = handle_type
  455. if handle_type := self.evaluate_input("BBone End Handle Type"):
  456. b.bbone_handle_type_end = handle_type
  457. try:
  458. if (custom_handle := self.evaluate_input("BBone Custom Start Handle")):
  459. b.bbone_custom_handle_start = self.bGetParentArmature().data.bones[custom_handle]
  460. if (custom_handle := self.evaluate_input("BBone Custom End Handle")):
  461. b.bbone_custom_handle_end = self.bGetParentArmature().data.bones[custom_handle]
  462. except KeyError:
  463. prRed("Warning: BBone start or end handle not set because of missing bone in armature.")
  464. bone_props_socket= {
  465. 'bbone_curveinx' : ("BBone X Curve-In", 0.0),
  466. 'bbone_curveinz' : ("BBone Z Curve-In", 0.0),
  467. 'bbone_curveoutx' : ("BBone X Curve-Out", 0.0),
  468. 'bbone_curveoutz' : ("BBone Z Curve-Out", 0.0),
  469. }
  470. evaluate_sockets(self, b, bone_props_socket)
  471. # TODO this section should be done with props-socket thing
  472. b.bbone_handle_use_scale_start = self.evaluate_input("BBone Start Handle Scale")
  473. b.bbone_handle_use_scale_end = self.evaluate_input("BBone End Handle Scale")
  474. import bpy
  475. pb = self.bGetParentArmature().pose.bones[self.bObject]
  476. rotation_mode = self.evaluate_input("Rotation Order")
  477. if rotation_mode == "AUTO": rotation_mode = "XYZ"
  478. pb.rotation_mode = rotation_mode
  479. pb.id_properties_clear()
  480. # these are kept around unless explicitly deleted.
  481. # from .utilities import get_ui_node
  482. # np = get_ui_node(self.signature, self.base_tree)
  483. driver = None
  484. do_prints=False
  485. # custom properties
  486. self.setup_custom_props(pb, "Custom Properties")
  487. if (pb.is_in_ik_chain):
  488. # this props_socket thing wasn't really meant to work here but it does, neat
  489. props_sockets = {
  490. 'ik_stretch' : ("IK Stretch", 0),
  491. 'lock_ik_x' : (("Lock IK", 0), False),
  492. 'lock_ik_y' : (("Lock IK", 1), False),
  493. 'lock_ik_z' : (("Lock IK", 2), False),
  494. 'ik_stiffness_x' : (("IK Stiffness", 0), 0.0),
  495. 'ik_stiffness_y' : (("IK Stiffness", 1), 0.0),
  496. 'ik_stiffness_z' : (("IK Stiffness", 2), 0.0),
  497. 'use_ik_limit_x' : (("Limit IK", 0), False),
  498. 'use_ik_limit_y' : (("Limit IK", 1), False),
  499. 'use_ik_limit_z' : (("Limit IK", 2), False),
  500. 'ik_min_x' : ("X Min", 0),
  501. 'ik_max_x' : ("X Max", 0),
  502. 'ik_min_y' : ("Y Min", 0),
  503. 'ik_max_y' : ("Y Max", 0),
  504. 'ik_min_z' : ("Z Min", 0),
  505. 'ik_max_z' : ("Z Max", 0),
  506. }
  507. evaluate_sockets(self, pb, props_sockets)
  508. if do_bb:
  509. props_sockets = {
  510. 'bbone_curveinx' : ("BBone X Curve-In", pb.bone.bbone_curveinx),
  511. 'bbone_curveinz' : ("BBone Z Curve-In", pb.bone.bbone_curveinz),
  512. 'bbone_curveoutx' : ("BBone X Curve-Out", pb.bone.bbone_curveoutx),
  513. 'bbone_curveoutz' : ("BBone Z Curve-Out", pb.bone.bbone_curveoutz),
  514. 'bbone_easein' : ("BBone Ease-In", 0),
  515. 'bbone_easeout' : ("BBone Ease-Out", 0),
  516. 'bbone_rollin' : ("BBone Roll-In", 0),
  517. 'bbone_rollout' : ("BBone Roll-Out", 0),
  518. 'bbone_scalein' : ("BBone Scale-In", (1,1,1)),
  519. 'bbone_scaleout' : ("BBone Scale-Out", (1,1,1)),
  520. }
  521. evaluate_sockets(self, pb, props_sockets)
  522. # we need to clear this stuff since our only real goal was to get some drivers from the above
  523. for attr_name in props_sockets.keys():
  524. try:
  525. setattr(pb, attr_name, 0) # just clear it
  526. except ValueError:
  527. setattr(pb, attr_name, (1.0,1.0,1.0)) # scale needs to be set to 1
  528. # important TODO... all of the drivers and stuff should be handled this way, right?
  529. # time to set up drivers!
  530. # just gonna add this to the end and build off it I guess
  531. props_sockets = {
  532. "lock_location" : ("Lock Location", [False, False, False]),
  533. "lock_rotation" : ("Lock Rotation", [False, False, False]),
  534. "lock_scale" : ("Lock Scale", [False, False, False]),
  535. 'custom_shape_scale_xyz' : ("Custom Object Scale", (0.0,0.0,0.0) ),
  536. 'custom_shape_translation' : ("Custom Object Translation", (0.0,0.0,0.0) ),
  537. 'custom_shape_rotation_euler' : ("Custom Object Rotation", (0.0,0.0,0.0) ),
  538. 'use_custom_shape_bone_size' : ("Custom Object Scale to Bone Length", True,)
  539. }
  540. evaluate_sockets(self, pb, props_sockets)
  541. # this could probably be moved to bTransformPass
  542. props_sockets = {
  543. 'hide' : ("Hide", False),
  544. 'show_wire' : ("Custom Object Wireframe", False),
  545. }
  546. evaluate_sockets(self, pb.bone, props_sockets)
  547. if (driver):
  548. pass
  549. # whatever I was doing there.... was stupid. CLEAN UP TODO
  550. # this is the right thing to do.
  551. finish_drivers(self)
  552. #
  553. # OK, visual settings
  554. #
  555. # Get the override xform's bone:
  556. pb.custom_shape_transform = None
  557. pb.custom_shape = None
  558. if len(self.inputs["Custom Object xForm Override"].links) > 0:
  559. trace = trace_single_line(self, "Custom Object xForm Override")
  560. try:
  561. pb.custom_shape_transform = trace[0][1].bGetObject()
  562. except AttributeError:
  563. pass
  564. if len(self.inputs["Custom Object"].links) > 0:
  565. trace = trace_single_line(self, "Custom Object")
  566. try:
  567. ob = trace[0][1].bGetObject()
  568. except AttributeError:
  569. ob=None
  570. if type(ob) in [bpy.types.Object]:
  571. pb.custom_shape = ob
  572. def bGetObject(self, mode = 'POSE'):
  573. if self.bObject is None: return None
  574. if mode in ["POSE", "OBJECT"] and self.bGetParentArmature().mode == "EDIT":
  575. raise RuntimeError("Cannot get Bone or PoseBone in Edit mode.")
  576. elif mode == "EDIT" and self.bGetParentArmature().mode != "EDIT":
  577. raise RuntimeError("Cannot get EditBone except in Edit mode.")
  578. try:
  579. if (mode == 'EDIT'):
  580. return self.bGetParentArmature().data.edit_bones[self.bObject]
  581. elif (mode == 'OBJECT'):
  582. return self.bGetParentArmature().data.bones[self.bObject]
  583. elif (mode == 'POSE'):
  584. return self.bGetParentArmature().pose.bones[self.bObject]
  585. except Exception as e:
  586. prRed ("Cannot get bone for %s" % self)
  587. raise e
  588. class xFormGeometryObject(xFormNode):
  589. '''A node representing an armature object'''
  590. def __init__(self, signature, base_tree):
  591. super().__init__(signature, base_tree, xFormGeometryObjectSockets)
  592. self.init_parameters()
  593. self.set_traverse([("Relationship", "xForm Out")])
  594. self.has_shape_keys = False
  595. setup_custom_property_inputs_outputs(self)
  596. def bPrepare(self, bContext = None,):
  597. import bpy
  598. if not self.evaluate_input("Name"):
  599. self.prepared = True
  600. self.executed = True
  601. # and return an error if there are any dependencies:
  602. if self.hierarchy_connections:
  603. raise GraphError(wrapRed(f"Cannot Generate object {self} because the chosen name is empty or invalid."))
  604. return
  605. self.bObject = bpy.data.objects.get(self.evaluate_input("Name"))
  606. trace = trace_single_line(self, "Geometry")
  607. if (not self.bObject):
  608. if trace[-1]:
  609. self.bObject = bpy.data.objects.new(self.evaluate_input("Name"), trace[-1].node.bGetObject())
  610. # handle mismatched data.
  611. data_wrong = False; data = None
  612. if (self.inputs["Geometry"].is_linked and self.bObject.type == "EMPTY"):
  613. data_wrong = True; data = trace[-1].node.bGetObject()
  614. elif (not self.inputs["Geometry"].is_linked and not self.bObject.type == "EMPTY"):
  615. data_wrong = True
  616. # clumsy but functional
  617. if data_wrong:
  618. unlink_me = self.bObject
  619. unlink_me.name = "MANTIS_TRASH.000"
  620. for col in unlink_me.users_collection:
  621. col.objects.unlink(unlink_me)
  622. self.bObject = bpy.data.objects.new(self.evaluate_input("Name"), data)
  623. if self.bObject and (self.inputs["Geometry"].is_linked and self.bObject.type in ["MESH", "CURVE"]):
  624. self.bObject.data = trace[-1].node.bGetObject()
  625. # NOW: find out if we need to duplicate the object data.
  626. dupe_data=False
  627. node_line = trace_single_line(self, "Deformer")[0]
  628. from .deformer_nodes import DeformerHook
  629. for deformer in node_line:
  630. if isinstance(deformer, DeformerHook) and \
  631. deformer.evaluate_input("Affect Curve Radius") == True and \
  632. self.bObject.type == 'CURVE':
  633. print(f"INFO: Duplicating data {self.bObject.data.name} in {self} so it can be used for drivers.")
  634. dupe_data=True; break
  635. if dupe_data:
  636. name = self.bObject.data.name
  637. # it has to be a curve
  638. data = bpy.data.curves.get(self.bObject.data.name+"_MANTIS")
  639. if data: # Delete it and regenerate it if it exists.
  640. data.name+="_TRASH.000" # but we can't actually delete it here
  641. # since previous executions of the graph may be using it.
  642. # instead, we'll rename it and use a new copy. This will probably
  643. # be deleted on its own by Blender's garbage collector.
  644. # if it isn't, then it is still in use and I may NOT touch it.
  645. data=self.bObject.data.copy(); data.animation_data_clear()
  646. data.name = name+"_MANTIS"
  647. self.bObject.data = data
  648. reset_object_data(self.bObject)
  649. matrix= get_matrix(self)
  650. self.parameters['Matrix'] = matrix
  651. try:
  652. set_object_parent(self)
  653. except: # I guess it isn't ready yet. we'll do it later
  654. pass # (This can happen when solving schema.)
  655. self.bObject.matrix_world = matrix
  656. parent_xForm_info = get_parent_xForm_info(self)
  657. root_armature = parent_xForm_info.root_armature
  658. my_info = xForm_info(
  659. object_type='object',
  660. root_armature= root_armature,
  661. parent_pose_name=parent_xForm_info.self_edit_name,
  662. parent_edit_name=parent_xForm_info.self_pose_name,
  663. self_pose_name=self.bObject.name,
  664. self_edit_name=self.bObject.name,
  665. )
  666. self.parameters['xForm Out'] = my_info
  667. self.prepared = True
  668. def bTransformPass(self, bContext = None,):
  669. try:
  670. bContext.collection.objects.link(self.bObject)
  671. except RuntimeError: #already in; but a dangerous thing to pass.
  672. pass
  673. self.has_shape_keys = False
  674. # putting this in bTransformPass simply prevents it from being run more than once.
  675. # maybe I should do that with the rest of bPrepare, too.
  676. props_sockets = {
  677. 'hide_viewport' : ("Hide in Viewport", False),
  678. 'hide_render' : ("Hide in Render", False),
  679. }
  680. evaluate_sockets(self, self.bObject, props_sockets)
  681. self.executed = True
  682. def bFinalize(self, bContext = None):
  683. matrix= get_matrix(self)
  684. set_object_parent(self)
  685. self.bObject.matrix_world = matrix
  686. # custom properties
  687. self.setup_custom_props(self.bGetObject(), "Custom Properties")
  688. for i, (driver_key, driver_item) in enumerate(self.drivers.items()):
  689. print (wrapGreen(i), wrapWhite(self), wrapPurple(driver_key))
  690. prOrange(driver_item)
  691. finish_drivers(self)
  692. def bGetObject(self, mode = 'POSE'):
  693. return self.bObject
  694. class xFormObjectInstance(xFormNode):
  695. """Represents an instance of an existing geometry object."""
  696. def __init__(self, signature, base_tree):
  697. super().__init__(signature, base_tree, xFormGeometryObjectInstanceSockets)
  698. self.init_parameters()
  699. # TODO: I think this field is a leftover from a test or something. see if it can be removed.
  700. self.links = {} # leave this empty for now!
  701. self.set_traverse([("Relationship", "xForm Out")])
  702. self.has_shape_keys = False # Shape Keys will make a dupe so this is OK
  703. setup_custom_property_inputs_outputs(self)
  704. def ui_modify_socket(self, ui_socket, socket_name=None):
  705. if ui_socket.name == 'As Instance':
  706. change_handled = True
  707. try:
  708. self.bObject.modifiers[0]['Socket_1'] = ui_socket.default_value
  709. except Exception as e:
  710. print("Failed to update mantis socket because of %s" % e,
  711. "Updating tree instead.")
  712. return change_handled
  713. else:
  714. return super().ui_modify_socket(ui_socket, socket_name)
  715. def bPrepare(self, bContext = None,):
  716. from bpy import data
  717. empty_mesh = data.meshes.get("MANTIS_EMPTY_MESH")
  718. if not empty_mesh:
  719. empty_mesh = data.meshes.new("MANTIS_EMPTY_MESH")
  720. if not self.evaluate_input("Name"):
  721. self.prepared = True
  722. self.executed = True
  723. # and return an error if there are any dependencies:
  724. if self.hierarchy_connections:
  725. raise GraphError(wrapRed(f"Cannot Generate object {self} because the chosen name is empty or invalid."))
  726. return
  727. self.bObject = data.objects.get(self.evaluate_input("Name"))
  728. if (not self.bObject):
  729. self.bObject = data.objects.new(self.evaluate_input("Name"), empty_mesh)
  730. reset_object_data(self.bObject)
  731. matrix= get_matrix(self)
  732. self.parameters['Matrix'] = matrix
  733. set_object_parent(self)
  734. self.bObject.matrix_world = matrix
  735. parent_xForm_info = get_parent_xForm_info(self)
  736. root_armature = parent_xForm_info.root_armature
  737. my_info = xForm_info(
  738. object_type='object',
  739. root_armature= root_armature,
  740. parent_pose_name=parent_xForm_info.self_edit_name,
  741. parent_edit_name=parent_xForm_info.self_pose_name,
  742. self_pose_name=self.bObject.name,
  743. self_edit_name=self.bObject.name,
  744. )
  745. self.parameters['xForm Out'] = my_info
  746. self.prepared = True
  747. def bTransformPass(self, bContext = None,):
  748. try:
  749. bContext.collection.objects.link(self.bObject)
  750. except RuntimeError: #already in; but a dangerous thing to pass.
  751. pass
  752. self.has_shape_keys = False
  753. # putting this in bTransformPass simply prevents it from being run more than once.
  754. # maybe I should do that with the rest of bPrepare, too.
  755. props_sockets = {
  756. 'hide_viewport' : ("Hide in Viewport", False),
  757. 'hide_render' : ("Hide in Render", False),
  758. }
  759. evaluate_sockets(self, self.bObject, props_sockets)
  760. self.executed = True
  761. def bFinalize(self, bContext = None):
  762. # now we need to set the object instance up.
  763. from bpy import data
  764. trace = trace_single_line(self, "Source Object")
  765. for node in trace[0]:
  766. if node is self: continue # lol
  767. if (node.node_type == 'XFORM'):
  768. source_ob = node.bGetObject(); break
  769. modifier = self.bObject.modifiers.new("Object Instance", type='NODES')
  770. ng = data.node_groups.get("Object Instance")
  771. if ng is None:
  772. from .geometry_node_graphgen import gen_object_instance_node_group
  773. ng = gen_object_instance_node_group()
  774. modifier.node_group = ng
  775. modifier["Socket_0"] = source_ob
  776. modifier["Socket_1"] = self.evaluate_input("As Instance")
  777. # custom properties
  778. self.setup_custom_props(self.bGetObject(), "Custom Properties")
  779. for i, (driver_key, driver_item) in enumerate(self.drivers.items()):
  780. print (wrapGreen(i), wrapWhite(self), wrapPurple(driver_key))
  781. prOrange(driver_item)
  782. finish_drivers(self)
  783. def bGetObject(self, mode = 'POSE'):
  784. return self.bObject
  785. class xFormCurvePin(xFormNode):
  786. """An xForm pinned to a specific location on a curve."""
  787. def __init__(self, signature, base_tree):
  788. super().__init__(signature, base_tree,xFormCurvePinSockets)
  789. self.init_parameters(additional_parameters={"Matrix":None})
  790. setup_custom_property_inputs_outputs(self)
  791. def prep_driver_values(self, constraint):
  792. from .misc_nodes import UtilityDriver, UtilitySwitch
  793. for socket_name in ["Curve Pin Factor", "Forward Axis","Up Axis",]:
  794. if self.inputs.get(socket_name) is None: continue # in case it has been bypassed
  795. if self.inputs[socket_name].is_linked:
  796. node_line, _last_socket = trace_single_line(self, socket_name)
  797. driver = None
  798. for other_node in node_line:
  799. if other_node.node_type == 'DRIVER':
  800. driver = other_node; break
  801. if isinstance(driver, UtilityDriver):
  802. prop_amount = driver.evaluate_input("Property")
  803. elif isinstance(driver, UtilitySwitch):
  804. xf=driver.GetxForm()
  805. prop_amount = xf.evaluate_input(driver.evaluate_input('Parameter'))
  806. else:
  807. return
  808. for template in self.socket_templates:
  809. if template.name == socket_name: break
  810. setattr(constraint, template.blender_property, prop_amount )
  811. def bPrepare(self, bContext = None,):
  812. from bpy import data
  813. if not bContext: # lol
  814. import bpy
  815. bContext = bpy.context
  816. ob = data.objects.get(self.evaluate_input("Name"))
  817. if not ob:
  818. ob = data.objects.new(self.evaluate_input("Name"), None)
  819. ob.lock_location = [True, True, True]
  820. ob.lock_rotation = [True, True, True]
  821. ob.lock_scale = [True, True, True]
  822. ob.lock_rotation_w = True
  823. ob.empty_display_type = 'CONE'
  824. ob.empty_display_size = 0.10
  825. self.bObject = ob
  826. reset_object_data(ob)
  827. node_line = trace_single_line(self, "Parent Curve")[0][1:] # slice excludes self
  828. for other_node in node_line:
  829. if other_node.node_type == 'XFORM':
  830. break
  831. else:
  832. raise GraphError(f"ERROR: {self} is not connected to a parent curve")
  833. if isinstance(other_node, (xFormArmature, xFormBone, xFormObjectInstance,)):
  834. raise GraphError(f"ERROR: {self} must be connected to curve,"
  835. " not {other_node.__class__.__name__}")
  836. curve=other_node.bGetObject()
  837. if curve.type != 'CURVE':
  838. raise GraphError(f"ERROR: {self} must be connected to curve,"
  839. " not {curve.type}")
  840. # we'll limit all the transforms so we can parent it
  841. # because it is annoying to have a cluttered outliner.
  842. #
  843. # always do this so that everything stays consistent.
  844. spline_index = self.evaluate_input("Spline Index")
  845. from .utilities import get_extracted_spline_object
  846. curve = get_extracted_spline_object(curve, spline_index, self.mContext)
  847. # Link to Scene:
  848. for link_me in [ob, curve]:
  849. if (link_me.name not in bContext.view_layer.active_layer_collection.collection.objects):
  850. bContext.view_layer.active_layer_collection.collection.objects.link(link_me)
  851. c = ob.constraints.new("LIMIT_LOCATION")
  852. for max_min in ['max','min']:
  853. for axis in "xyz":
  854. setattr(c, "use_"+max_min+"_"+axis, True)
  855. setattr(c, max_min+"_"+axis, 0.0)
  856. c = ob.constraints.new("LIMIT_ROTATION")
  857. for axis in "xyz":
  858. setattr(c, "use_limit_"+axis, True)
  859. setattr(c, max_min+"_"+axis, 0.0)
  860. c = ob.constraints.new("LIMIT_SCALE")
  861. for max_min in ['max','min']:
  862. for axis in "xyz":
  863. setattr(c, "use_"+max_min+"_"+axis, True)
  864. setattr(c, max_min+"_"+axis, 1.0)
  865. c = ob.constraints.new("FOLLOW_PATH")
  866. c.target = curve
  867. c.use_fixed_location = True
  868. c.use_curve_radius = True
  869. c.use_curve_follow = True
  870. c.name = "Curve Pin"
  871. props_sockets = self.gen_property_socket_map()
  872. constraint_props_sockets = props_sockets.copy()
  873. del constraint_props_sockets['name']; del constraint_props_sockets['empty_display_size']
  874. del props_sockets['offset_factor']; del props_sockets['forward_axis']
  875. del props_sockets['up_axis']
  876. evaluate_sockets(self, c, constraint_props_sockets)
  877. evaluate_sockets(self, self.bObject, props_sockets)
  878. # this isn't usually run on xForm nodes so for now I need to set the
  879. # driver's default values manually if I want a matrix now.
  880. # because the drivers may not have initialized yet.
  881. self.prep_driver_values(c)
  882. # now if all goes well... the matrix will be correct.
  883. dg = bContext.view_layer.depsgraph
  884. dg.update()
  885. # and the matrix should be correct now - copy because it may be modified
  886. self.parameters['Matrix'] = ob.matrix_world.copy()
  887. ob.parent=curve
  888. print( wrapGreen("Created Curve Pin: ") + wrapOrange(self.bObject.name) )
  889. parent_xForm_info = get_parent_xForm_info(self)
  890. root_armature = parent_xForm_info.root_armature
  891. my_info = xForm_info(
  892. object_type='object',
  893. root_armature= root_armature,
  894. parent_pose_name=curve.name,
  895. parent_edit_name=curve.name,
  896. self_pose_name=self.bObject.name,
  897. self_edit_name=self.bObject.name,
  898. )
  899. self.parameters['xForm Out'] = my_info
  900. self.prepared = True; self.executed = True
  901. def bFinalize(self, bContext = None):
  902. # custom properties
  903. self.setup_custom_props(self.bGetObject(), "Custom Properties")
  904. finish_drivers(self)
  905. def bGetObject(self, mode = 'POSE'):
  906. return self.bObject
  907. # special thank-you to Natalie Cuthbert, who submitted a patch for this
  908. # it turned out, I already had this patch laying around
  909. # because I was too busy with grad school I didn't succeed in collaborating
  910. # so while I apologize for failing to get your name in the commit history
  911. # now you get a special thank-you in Mantis instead!
  912. class xFormGetBone(xFormNode):
  913. """Represents an instance of an existing geometry object."""
  914. def __init__(self, signature, base_tree):
  915. super().__init__(signature, base_tree, xFormGetBoneSockets)
  916. self.init_parameters()
  917. # this is just a getter
  918. self.prepared=True; self.executed=True; self.execution_prepared=True
  919. def bGetParentArmature(self):
  920. from bpy import data
  921. return data.objects.get(self.evaluate_input("Parent Armature"))
  922. def bGetObject(self, mode = 'POSE'):
  923. bone = None
  924. self.bObject = self.evaluate_input("Bone")
  925. armature = self.bGetParentArmature()
  926. from bpy.types import Object
  927. assert armature is not None, f"{self} requires a parent armature input to operate."
  928. assert isinstance(armature, Object), f"{self}: The parent armature must be an armature object."
  929. if armature:
  930. assert armature.type == 'ARMATURE', f"{self}: The parent armature must be an armature object."
  931. match mode:
  932. case 'EDIT':
  933. bone = armature.data.edit_bones.get(self.evaluate_input("Bone"))
  934. case 'OBJECT':
  935. bone = armature.bones.get(self.evaluate_input("Bone"))
  936. case 'POSE':
  937. bone = armature.pose.bones.get(self.evaluate_input("Bone"))
  938. assert self.bObject is not None, f"{self} failed to get the desired bone. Check if the bone name exists."
  939. return bone