xForm_nodes.py 44 KB

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