versioning.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. #Versioning Tasks
  2. # this will be the new versioning system, and will deprecate the old SOCKETS_ADDED and such
  3. from bpy.types import Node, NodeSocket
  4. from bpy.app import version as bpy_version
  5. from .utilities.utilities import (prRed, prGreen, prPurple, prWhite,
  6. prOrange,
  7. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  8. wrapOrange,
  9. get_socket_maps, do_relink, )
  10. # CHECKLIST FOR DUMMIES:
  11. # Did you make sure to turn off and re-enable tree updates?
  12. # Did you restore connections to the node socket after changing things?
  13. # Did you test FUNCTIONALITY in your test rigs instead of just checking if the tree
  14. # would generate? CHANGED FUNCTIONALITY IS STILL A BUG!!
  15. # Did you make sure to toggle the is_updating flag for ANY node that is touched by
  16. # the edits you are doing to the current node (example: removing a socket causes
  17. # linked group and schema nodes to update)
  18. # (add more whenever a weird or difficult to debug problem occurs)
  19. # Did you check the update script on a tree that doesn't need it?
  20. # Did you use a >= when you should have used a > (or similar)?
  21. # Did you make sure that nodes with array inputs are sorting them properly?
  22. def version_upgrade_very_old(*args, **kwargs):
  23. node = kwargs['node']
  24. current_major_version = node.id_data.mantis_version[0]
  25. current_minor_version = node.id_data.mantis_version[1]
  26. if current_major_version > 0: return# major version must be 0
  27. if current_minor_version > 11: return# minor version must be 12 or less
  28. # this is the old node update, very inneficient and strange and badly organized
  29. NODES_REMOVED=["xFormRootNode"]
  30. # Node bl_idname, # Socket Name
  31. SOCKETS_REMOVED=[("UtilityDriverVariable", "Transform Channel"),
  32. ("xFormRootNode","World Out"),
  33. ("UtilitySwitch","xForm"),
  34. ("LinkDrivenParameter", "Enable")]
  35. # Node Class #Prior bl_idname # prior name # new bl_idname # new name, # Multi
  36. SOCKETS_RENAMED=[ ("LinkDrivenParameter", "DriverSocket", "Driver", "FloatSocket", "Value", False),
  37. ("DeformerHook", "IntSocket", "Index", "UnsignedIntSocket", "Point Index", False),
  38. ("SchemaConstOutput", "IntSocket", "Expose when N==", "UnsignedIntSocket", "Expose at Index", False),]
  39. # NODE CLASS NAME IN_OUT SOCKET TYPE SOCKET NAME INDEX MULTI DEFAULT
  40. SOCKETS_ADDED=[("DeformerMorphTargetDeform", 'INPUT', 'BooleanSocket', "Use Shape Key", 1, False, False),
  41. ("DeformerMorphTargetDeform", 'INPUT', 'BooleanSocket', "Use Offset", 2, False, True),
  42. ("UtilityFCurve", 'INPUT', "eFCrvExtrapolationMode", "Extrapolation Mode", 0, False, 'CONSTANT'),
  43. ("LinkCopyScale", 'INPUT', "BooleanSocket", "Additive", 3, False, False),
  44. ("DeformerHook", 'INPUT', "FloatFactorSocket", "Influence",3, False, 1.0),
  45. ("DeformerHook", 'INPUT', "UnsignedIntSocket", "Spline Index", 2, False, 0),
  46. ("DeformerHook", 'INPUT', "BooleanSocket", "Auto-Bezier", 5, False, True),
  47. ("UtilityCompare", 'INPUT', "EnumCompareOperation", "Comparison", 0, False, 'EQUAL'),
  48. ("UtilityMatrixFromCurve", 'INPUT', "UnsignedIntSocket", "Spline Index", 1, False, 0),
  49. ("UtilityMatricesFromCurve", 'INPUT', "UnsignedIntSocket", "Spline Index", 1, False, 0),
  50. ("UtilityPointFromCurve", 'INPUT', "UnsignedIntSocket", "Spline Index", 1, False, 0),
  51. ("LinkCopyScale", 'INPUT', "FloatFactorSocket", "Power", 5, False, 1.0),
  52. ]
  53. rename_jobs = []
  54. node_tree = kwargs['node_tree']
  55. try:
  56. if node.bl_idname in NODES_REMOVED:
  57. print(f"INFO: removing node {node.name} of type {node.bl_idname} because it has been deprecated.")
  58. node.inputs.remove(socket)
  59. return
  60. for i, socket in enumerate(node.inputs.values()):
  61. if (node.bl_idname, socket.identifier) in SOCKETS_REMOVED:
  62. print(f"INFO: removing socket {socket.identifier} of node {node.name} of type {node.bl_idname} because it has been deprecated.")
  63. node.inputs.remove(socket)
  64. for old_class, old_bl_idname, old_name, new_bl_idname, new_name, multi in SOCKETS_RENAMED:
  65. if (node.bl_idname == old_class and socket.bl_idname == old_bl_idname and socket.name == old_name):
  66. rename_jobs.append((socket, i, new_bl_idname, new_name, multi))
  67. for i, socket in enumerate(node.outputs.values()):
  68. if (node.bl_idname, socket.identifier) in SOCKETS_REMOVED:
  69. print(f"INFO: removing socket {socket.identifier} of node {node.name} of type {node.bl_idname} because it has been deprecated.")
  70. node.outputs.remove(socket)
  71. for old_class, old_bl_idname, old_name, new_bl_idname, new_name, multi in SOCKETS_RENAMED:
  72. if (node.bl_idname == old_class and socket.bl_idname == old_bl_idname and socket.name == old_name):
  73. rename_jobs.append((socket, i, new_bl_idname, new_name, multi))
  74. for bl_idname, in_out, socket_type, socket_name, index, use_multi_input, default_val in SOCKETS_ADDED:
  75. if node.bl_idname != bl_idname:
  76. continue
  77. if in_out == 'INPUT' and node.inputs.get(socket_name) is None:
  78. print(f"INFO: adding socket \"{socket_name}\" of type {socket_type} to node {node.name} of type {node.bl_idname}.")
  79. s = node.inputs.new(socket_type, socket_name, use_multi_input=use_multi_input)
  80. try:
  81. s.default_value = default_val
  82. except AttributeError:
  83. pass # the socket is read-only
  84. node.inputs.move(len(node.inputs)-1, index)
  85. socket_map = None
  86. if rename_jobs:
  87. socket_maps = get_socket_maps(node)
  88. for socket, socket_index, new_bl_idname, new_name, multi in rename_jobs:
  89. old_id = socket.identifier
  90. print (f"Renaming socket {socket.identifier} to {new_name} in node {node.name}")
  91. if socket.is_output:
  92. index = 1
  93. in_out = "OUTPUT"
  94. node.outputs.remove(socket)
  95. s = node.outputs.new(new_bl_idname, new_name, identifier=new_name, use_multi_input=multi)
  96. node.outputs.move(len(node.outputs)-1, socket_index)
  97. socket_map = socket_maps[1]
  98. else:
  99. index = 0
  100. in_out = "INPUT"
  101. node.inputs.remove(socket)
  102. s = node.inputs.new(new_bl_idname, new_name, identifier=new_name, use_multi_input=multi)
  103. node.inputs.move(len(node.inputs)-1, socket_index)
  104. socket_map = socket_maps[0]
  105. socket_map[new_name] = socket_map[old_id]
  106. if new_name != old_id: del socket_map[old_id] # sometimes rename just changes the socket type or multi
  107. do_relink(node, s, socket_map)
  108. except Exception as e:
  109. prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
  110. print(e)
  111. def version_upgrade_bone_0_12_0_from_older(*args, **kwargs):
  112. # we need to check if it has an array collection input and a color input
  113. # then we need to solve each task
  114. node = kwargs['node']
  115. live_updates = node.id_data.do_live_update
  116. node.id_data.do_live_update = False
  117. current_major_version = node.id_data.mantis_version[0]
  118. current_minor_version = node.id_data.mantis_version[1]
  119. if current_major_version > 0: # major version must be 0
  120. return
  121. if current_minor_version >= 12: # minor version must be 11 or less
  122. return
  123. # sub version doesn't matter since any subversion of 11 should trigger this task
  124. collection_input_is_array = False
  125. try:
  126. collection_input_is_array = node.inputs['Bone Collection'].is_multi_input
  127. except KeyError: # I suppose this happens in very, very old trees
  128. pass # but it is extremely unlikely
  129. toggle_updating = []
  130. was_updating = {}
  131. try:
  132. if not collection_input_is_array: # it must be made into an array!
  133. prPurple(f"Updating \"Bone Collection\" Socket in {node.name}")
  134. from .utilities.utilities import get_socket_maps
  135. socket_maps = get_socket_maps(node)
  136. socket_map = socket_maps[0]
  137. for i, socket in enumerate(node.inputs):
  138. if socket.name == 'Bone Collection': break
  139. # Always guard socket removal in case of connection to e.g. schema
  140. for socket_name, links in socket_map.items():
  141. if isinstance(links, list):
  142. for link in links:
  143. if hasattr(link, 'node') and hasattr(link.node, 'is_updating'):
  144. toggle_updating.append(link.node)
  145. was_updating[link.node.name] = link.node.is_updating
  146. link.node.is_updating = True
  147. node.inputs.remove(socket)
  148. s = node.inputs.new('BoneCollectionSocket', 'Bone Collection',
  149. identifier='Bone Collection', use_multi_input=True)
  150. node.inputs.move(len(node.inputs)-1, i)
  151. do_relink(node, s, socket_map, in_out='INPUT')
  152. if node.inputs.get('Color') is None:
  153. prPurple(f"Adding \"Color\" Socket to {node.name}")
  154. s = node.inputs.new('ColorSetSocket', 'Color',)
  155. node.inputs.move(len(node.inputs)-1, 22)
  156. except Exception as e:
  157. prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
  158. print(e)
  159. finally:
  160. # Now we have to clean up by removing the unneeded sockets and repositioning.
  161. for other_node in toggle_updating:
  162. other_node.is_updating = was_updating[other_node.name]
  163. node.id_data.do_live_update = live_updates
  164. def up_0_12_1_add_inherit_color(*args, **kwargs):
  165. # add an inherit color input.
  166. node = kwargs['node']
  167. current_major_version = node.id_data.mantis_version[0]
  168. current_minor_version = node.id_data.mantis_version[1]
  169. current_sub_version = node.id_data.mantis_version[2]
  170. if current_major_version > 0: return# major version must be 0
  171. if current_minor_version > 12: return# minor version must be 12 or less
  172. if current_minor_version == 12 and current_sub_version > 9: return # sub version must be 8 or less
  173. # I am having it do 8 or less because there was a bug in this function prior to 9
  174. # sub version doesn't matter since any subversion of 11 should trigger this task
  175. prPurple(f"Adding \"Inherit Color\" socket to {node.name}")
  176. try:
  177. inh_color = node.inputs.get('Inherit Color')
  178. if inh_color and inh_color.bl_idname != 'BooleanSocket':
  179. node.inputs.remove(inh_color)
  180. inh_color = None
  181. if inh_color is None:
  182. s = node.inputs.new('BooleanSocket', 'Inherit Color',)
  183. node.inputs.move(len(node.inputs)-1, 23)
  184. s.default_value=True
  185. except Exception as e:
  186. prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
  187. print(e)
  188. def up_0_12_13_add_widget_scale(*args, **kwargs):
  189. node = kwargs['node']
  190. current_major_version = node.id_data.mantis_version[0]
  191. current_minor_version = node.id_data.mantis_version[1]
  192. current_sub_version = node.id_data.mantis_version[2]
  193. if current_major_version > 0: return# major version must be 0
  194. if current_minor_version > 12: return# minor version must be 12 or less
  195. if current_minor_version == 12 and current_sub_version > 12: return
  196. # sub version must be 12 or less
  197. prPurple(f"Adding \"Scale\" socket to {node.name}")
  198. try:
  199. scale = node.inputs.get('Scale')
  200. if scale is None:
  201. s = node.inputs.new('VectorScaleSocket', 'Scale',)
  202. s.default_value=[1.0,1.0,1.0]
  203. node.inputs.move(len(node.inputs)-1, 1)
  204. except Exception as e:
  205. prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
  206. print(e)
  207. def up_0_12_25_replace_floor_offset_type(*args, **kwargs):
  208. node = kwargs['node']
  209. current_major_version = node.id_data.mantis_version[0]
  210. current_minor_version = node.id_data.mantis_version[1]
  211. current_sub_version = node.id_data.mantis_version[2]
  212. if current_major_version > 0: return# major version must be 0
  213. if current_minor_version > 12: return# minor version must be 12 or less
  214. if current_minor_version == 12 and current_sub_version > 24: return
  215. socket_maps = get_socket_maps(node)
  216. prPurple(f"Fixing \"Offset\" socket in {node.name}")
  217. try:
  218. offset = node.inputs["Offset"]
  219. node.inputs.remove(offset)
  220. s = node.inputs.new('FloatSocket', 'Offset',)
  221. node.inputs.move(len(node.inputs)-1, 2)
  222. do_relink(node, s, socket_maps[0])
  223. except Exception as e:
  224. prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
  225. print(e)
  226. def get_absolute_location(node): # using this for version compatibility
  227. location = node.location.copy() # FUTURE: deprecate old blenders and use absolute_location
  228. while(node.parent):
  229. node = node.parent
  230. location+= node.location
  231. return location
  232. def up_0_13_0_new_custom_prop_node(*args, **kwargs):
  233. tree, node = kwargs['node_tree'], kwargs['node']
  234. current_major_version = tree.mantis_version[0]
  235. if current_major_version > 0: return# major version must be 0
  236. current_minor_version = tree.mantis_version[1]
  237. if current_minor_version >= 13: return# minor version must be 12 or less
  238. remove_me, prop_nodes = [], []
  239. from .xForm_nodes_ui import xFormBoneNode
  240. socket_map_in, socket_map_out = get_socket_maps(node)
  241. if isinstance(node, xFormBoneNode):
  242. if node.inputs.get('Custom Properties'):
  243. return # already done here
  244. for custom_input in reversed(list(node.inputs)):
  245. if 'Parameter' not in custom_input.bl_idname:
  246. continue
  247. custom_prop_node = tree.nodes.new('UtilityCustomProperty')
  248. prop_type = None
  249. if custom_input.bl_idname == 'ParameterBoolSocket':
  250. prop_type = 'BOOL'
  251. custom_prop_node.inputs['Default (Bool)'].default_value = getattr(custom_input, "default_value")
  252. elif custom_input.bl_idname == 'ParameterIntSocket':
  253. prop_type = 'INT'
  254. custom_prop_node.inputs['Default (Int)'].default_value = int( getattr(custom_input, "default_value") )
  255. custom_prop_node.inputs['Min (Int)'].default_value = int( getattr(custom_input, "min") )
  256. custom_prop_node.inputs['Max (Int)'].default_value = int( getattr(custom_input, "max") )
  257. custom_prop_node.inputs['Soft Min (Int)'].default_value = int( getattr(custom_input, "soft_min") )
  258. custom_prop_node.inputs['Soft Max (Int)'].default_value = int( getattr(custom_input, "soft_max") )
  259. elif custom_input.bl_idname == 'ParameterFloatSocket':
  260. prop_type = 'FLOAT'
  261. custom_prop_node.inputs['Default (Float)'].default_value = getattr(custom_input, "default_value")
  262. custom_prop_node.inputs['Min (Float)'].default_value = getattr(custom_input, "min")
  263. custom_prop_node.inputs['Max (Float)'].default_value = getattr(custom_input, "max")
  264. custom_prop_node.inputs['Soft Min (Float)'].default_value = getattr(custom_input, "soft_min")
  265. custom_prop_node.inputs['Soft Max (Float)'].default_value = getattr(custom_input, "soft_max")
  266. elif custom_input.bl_idname == 'ParameterVectorSocket':
  267. prop_type = 'VECTOR'
  268. custom_prop_node.inputs['Default (Vector)'].default_value = getattr(custom_input, "default_value")
  269. try:
  270. custom_prop_node.inputs['Min (Float)'].default_value = getattr(custom_input, "min")
  271. custom_prop_node.inputs['Max (Float)'].default_value = getattr(custom_input, "max")
  272. custom_prop_node.inputs['Soft Min (Float)'].default_value = getattr(custom_input, "soft_min")
  273. custom_prop_node.inputs['Soft Max (Float)'].default_value = getattr(custom_input, "soft_max")
  274. except AttributeError: # I don't know if I just never implemented these or if it is version specific
  275. pass # anyways this isn't critical and should not trigger even a warning
  276. elif custom_input.bl_idname == 'ParameterStringSocket':
  277. prop_type = 'STRING'
  278. custom_prop_node.inputs['Default (String)'].default_value = getattr(custom_input, "default_value")
  279. custom_prop_node.inputs['Name'].default_value = custom_input.name
  280. custom_prop_node.inputs['Type'].default_value = prop_type
  281. prop_nodes.append(custom_prop_node)
  282. remove_me.append(custom_input)
  283. # now we need to relink things, because the custom property socket could have had an
  284. # input supplying the default value
  285. relink_value = socket_map_in.get(custom_input.identifier, None)
  286. if isinstance(relink_value, list): # this indicates it is a list of links
  287. # CRITICALLY, we need to force display update before doing this.
  288. custom_prop_node.display_update({}, None) # parsed_tree and context, not needed
  289. input_socket = None
  290. match prop_type:
  291. case 'BOOL':
  292. input_socket = "Default (Bool)"
  293. case 'INT':
  294. input_socket = "Default (Int)"
  295. case 'FLOAT':
  296. input_socket = "Default (Float)"
  297. case 'VECTOR':
  298. input_socket = "Default (Vector)"
  299. case 'STRING':
  300. input_socket = "Default (String)"
  301. case _: # Should never never happen
  302. raise RuntimeError("Unexpected Code Path Reached, this is a bug.")
  303. do_relink(custom_prop_node, custom_prop_node.inputs[input_socket],
  304. {input_socket:relink_value})
  305. while remove_me:
  306. custom_input = remove_me.pop()
  307. node.inputs.remove(custom_input)
  308. new_input = node.inputs.new("CustomPropSocket", "Custom Properties", use_multi_input=True)
  309. from mathutils import Vector
  310. for i, prop_node in enumerate(prop_nodes):
  311. tree.links.new(input=prop_node.outputs[0], output=new_input)
  312. prop_node.location = get_absolute_location(node)
  313. prop_node.location = prop_node.location - Vector((180, -260*i))
  314. prop_node.select = False # it is annoying to select them all by default
  315. else: # just need to add the socket.
  316. if node.inputs.get('Custom Properties'):
  317. return # already done here
  318. new_input = node.inputs.new("CustomPropSocket", "Custom Properties", use_multi_input=True)
  319. def schema_enable_custom_interface_types(*args, **kwargs):
  320. tree = kwargs['tree']
  321. current_major_version = tree.mantis_version[0]
  322. current_minor_version = tree.mantis_version[1]
  323. current_sub_version = tree.mantis_version[2]
  324. if current_major_version > 0: return# major version must be 0
  325. if current_minor_version >= 13: return# minor version must be 12 or less
  326. # we need to set the new interface values on the schema interface stuff
  327. prGreen(f"Updating Schema tree {tree.name} to support new, improved UI!")
  328. tree.do_live_update = False
  329. for n in tree.nodes:
  330. if hasattr(n, "is_updating"):
  331. n.is_updating = True
  332. try:
  333. for item in tree.interface.items_tree:
  334. if item.item_type == 'PANEL':
  335. continue
  336. parent_name = 'Constant'
  337. if item.parent:
  338. parent_name=item.parent.name
  339. if hasattr(item, "is_array"):
  340. # if is_array exists we're in the custom interface class
  341. # so we'll assume the other attributes exist
  342. if parent_name == 'Array':
  343. item.is_array = True
  344. if parent_name == 'Connection':
  345. item.is_array = False
  346. item.is_connection=True
  347. item.connected_to=item.name
  348. # since heretofore it has been a requirement that the names match
  349. except Exception as e:
  350. prRed(f"Error updating version in tree: {tree.name}; see error:")
  351. print(e)
  352. finally:
  353. for n in tree.nodes:
  354. if hasattr(n, "is_updating"):
  355. n.is_updating = False
  356. tree.do_live_update = True
  357. def update_fcurve_and_armature_node_to_multi_socket(*args, **kwargs):
  358. from .base_definitions import SchemaUINode
  359. node = kwargs['node']
  360. live_updates = node.id_data.do_live_update
  361. node.id_data.do_live_update = False
  362. # First we have to get the socket maps and re-init the sockets
  363. convert_keys = {'UtilityFCurve' : [ 'Keyframe', ],
  364. 'LinkArmature' : [ 'Target', 'Weight', ], }
  365. # first test if we need to do the versioning
  366. if (socket := node.inputs.get(convert_keys[node.bl_idname][0]+'s')):
  367. if socket.is_multi_input: return
  368. else: # this will never happen, doesn't hurt to handle it
  369. node.inputs.remove(socket) # must be an array
  370. my_convert_keys = convert_keys[node.bl_idname]
  371. sockets_in, _sockets_out = get_socket_maps(node,)
  372. # NOTE IMPORTANT BUG INFO
  373. # I was using node.inputs.clear() here before unguarded...
  374. # this caused connected Schema nodes to update and mutate the socket map
  375. # which, in turn, connected the new socket to the wildcard and extended the schema
  376. # resulting in trees that fail to run. This was particularly bad because it was
  377. # so hard to debug (due to spooky action at a distance mutation)
  378. # So we have to mark the connected nodes as "updating" before we modify the tree.
  379. # TODO: should I just mark all nodes as updating during versioning?
  380. toggle_updating = []
  381. was_updating = {}
  382. for socket_name, links in sockets_in.items():
  383. if isinstance(links, list):
  384. for link in links:
  385. if hasattr(link, 'node') and hasattr(link.node, 'is_updating'):
  386. toggle_updating.append(link.node)
  387. was_updating[link.node.name] = link.node.is_updating
  388. link.node.is_updating = True
  389. socket_templates_original = node.socket_templates
  390. node.inputs.clear() # this is VERY DANGEROUS without the above
  391. add_me_sockets = [st for st in node.socket_templates if st.is_input == True]
  392. node.init_sockets(add_me_sockets)
  393. node.socket_templates = socket_templates_original
  394. try:
  395. new_sockets_in = {}
  396. for convert_key in my_convert_keys:
  397. for socket_name, links in sockets_in.items():
  398. if links is None: continue
  399. if convert_key+'.' in socket_name:
  400. if new_sockets_in.get(convert_key+'s'):
  401. # has to be in this order to come back alright.
  402. # assuming, that is, that the keys are sorted. they should be.
  403. new_sockets_in[convert_key+'s'] = sockets_in[socket_name] + new_sockets_in[convert_key+'s']
  404. else:
  405. new_sockets_in[convert_key+'s'] = sockets_in[socket_name]
  406. if new_sockets_in:
  407. do_relink(node, node.inputs[convert_key+'s'], new_sockets_in, in_out="INPUT",)
  408. for socket_name, links in sockets_in.items():
  409. proceed = True
  410. for convert_key in my_convert_keys:
  411. if convert_key in socket_name:
  412. proceed = False
  413. if proceed:
  414. do_relink(node, node.inputs[socket_name], sockets_in, in_out='INPUT')
  415. finally:
  416. # Now we have to clean up by removing the unneeded sockets and repositioning.
  417. for other_node in toggle_updating:
  418. other_node.is_updating = was_updating[other_node.name]
  419. node.id_data.do_live_update = live_updates
  420. # These BBone Scale-In, Scale-Out properties had the wrong defaults
  421. # now that they are implemented properly,
  422. # since they were not implemented before, we'll do all of them at once.
  423. # Normally it is wrong to set things directly like this, but they didn't
  424. # do anything before. Oh well.
  425. def fix_bbone_default_scale_in_and_out(*args, **kwargs):
  426. tree = kwargs['tree']
  427. current_major_version = tree.mantis_version[0]
  428. current_minor_version = tree.mantis_version[1]
  429. current_sub_version = tree.mantis_version[2]
  430. if current_major_version > 0: return# major version must be 0
  431. if current_minor_version > 13: return# minor version must be 12 or less
  432. if current_minor_version == 13 and current_sub_version >= 5: return# sub version must be 4 or less
  433. prPurple(f"Fixing BBone Settings for {tree.name}")
  434. live_updates = tree.do_live_update
  435. tree.do_live_update = False
  436. bones = []
  437. for node in tree.nodes:
  438. if hasattr(node, 'is_updating'):
  439. node.is_updating = True
  440. if node.bl_idname == 'xFormBoneNode':
  441. bones.append(node)
  442. try:
  443. for bone_node in bones:
  444. bone_node.inputs['BBone Scale-In'].default_value = (1,1,1)
  445. bone_node.inputs['BBone Scale-Out'].default_value = (1,1,1)
  446. finally:
  447. for node in tree.nodes:
  448. if hasattr(node, 'is_updating'):
  449. node.is_updating = False
  450. tree.do_live_update = live_updates
  451. # New display features, Display Type, new Custom Object gizmo/pivot stuff
  452. def add_new_display_features_v0_13_5(*args, **kwargs):
  453. tree = kwargs['tree']
  454. current_major_version = tree.mantis_version[0]
  455. current_minor_version = tree.mantis_version[1]
  456. current_sub_version = tree.mantis_version[2]
  457. if current_major_version > 0:return# major version must be 0
  458. if current_minor_version > 13: return# minor version must be 12 or less
  459. if current_minor_version == 13 and current_sub_version >= 5: return# sub version must be 4 or less
  460. prPurple(f"Adding new Display Features to {tree.name}")
  461. live_updates = tree.do_live_update; tree.do_live_update = False
  462. bones = []; armatures = []
  463. for node in tree.nodes:
  464. if hasattr(node, 'is_updating'): node.is_updating = True
  465. if node.bl_idname == 'xFormBoneNode': bones.append(node)
  466. if node.bl_idname == 'xFormArmatureNode': armatures.append(node)
  467. try:
  468. for bone_node in bones:
  469. # we'll just try this...
  470. from .xForm_socket_templates import (BoneDisplayType,
  471. CustomObjectWireWidth,
  472. UseTransformAtCustomShape,
  473. UseTransformAroundCustomShape,
  474. xFormBoneSockets)
  475. if bone_node.inputs.get(BoneDisplayType.name):
  476. continue # Already fixed
  477. input_templates = ( BoneDisplayType,
  478. CustomObjectWireWidth,
  479. UseTransformAroundCustomShape,
  480. UseTransformAtCustomShape, )
  481. bone_node.init_sockets(input_templates)
  482. bone_node.socket_templates = xFormBoneSockets
  483. for i, inp in enumerate(bone_node.inputs):
  484. if inp.name == 'Custom Object xForm Override':
  485. # As confusing as this is, it is the least terrible way to do this
  486. # We add the sockets to the end, then move them from the end to the right place
  487. bone_node.inputs.move(len(bone_node.inputs)-1, i+1)
  488. bone_node.inputs.move(len(bone_node.inputs)-1, i+2)
  489. bone_node.inputs.move(len(bone_node.inputs)-1, i+3)
  490. bone_node.inputs.move(len(bone_node.inputs)-1, i+4)
  491. break
  492. for armature_node in armatures:
  493. # we'll just try this...
  494. from .xForm_socket_templates import (ArmatureDisplayType,
  495. xFormArmatureSockets)
  496. if bone_node.inputs.get(ArmatureDisplayType.name):
  497. continue # Already fixed
  498. input_templates = ( ArmatureDisplayType, )
  499. armature_node.init_sockets(input_templates)
  500. armature_node.socket_templates = xFormArmatureSockets
  501. for i, inp in enumerate(armature_node.inputs):
  502. if inp.name == 'Relationship':
  503. armature_node.inputs.move(len(armature_node.inputs)-1, i+1)
  504. break
  505. finally:
  506. for node in tree.nodes:
  507. if hasattr(node, 'is_updating'):
  508. node.is_updating = False
  509. tree.do_live_update = live_updates
  510. def up_0_13_5_modes_for_matrix_from_curve_and_compose_matrix(*args, **kwargs):
  511. node = kwargs['node']
  512. if 'Mode' in node.inputs:
  513. return # done already
  514. tree = node.id_data
  515. current_major_version = tree.mantis_version[0]
  516. current_minor_version = tree.mantis_version[1]
  517. current_sub_version = tree.mantis_version[2]
  518. if current_major_version > 0: return# major version must be 0
  519. if current_minor_version >= 13: return# minor version must be 12 or less
  520. if current_minor_version == 13 and current_sub_version >= 5: return# sub version must be 4 or less
  521. live_updates = tree.do_live_update
  522. tree.do_live_update = False
  523. for other_node in tree.nodes:
  524. if hasattr(other_node, 'is_updating'):
  525. other_node.is_updating = True
  526. try:
  527. socket_templates = node.socket_templates
  528. if node.bl_idname == 'UtilityMatrixFromCurve':
  529. from .misc_nodes_socket_templates import (MatrixFromCurveMode,
  530. MatrixFromCurveFactor,
  531. MatrixFromCurveTailFactor, )
  532. input_templates = ( MatrixFromCurveMode, MatrixFromCurveFactor,
  533. MatrixFromCurveTailFactor, )
  534. node.init_sockets(input_templates)
  535. node.inputs.move(len(node.inputs)-3, 0) # move Mode to first place
  536. elif node.bl_idname == 'UtilityMatrixCompose':
  537. from .misc_nodes_socket_templates import (ComposeMatrixBoneLength,
  538. MatrixComposeMode,
  539. ComposeMatrixRotation,)
  540. input_templates = ( ComposeMatrixBoneLength, MatrixComposeMode,
  541. ComposeMatrixRotation,)
  542. node.init_sockets(input_templates)
  543. node.inputs.move(len(node.inputs)-1, 0) # These go in the top, so make em in order
  544. node.inputs.move(len(node.inputs)-1, 0) # and move 'em one at a time in reverse order
  545. node.socket_templates = socket_templates
  546. finally:
  547. for other_node in tree.nodes:
  548. if hasattr(other_node, 'is_updating'):
  549. other_node.is_updating = False
  550. tree.do_live_update = live_updates
  551. # versioning tasks that involve Blender versions rather than Mantis versions:
  552. error_description_4_5_0_LTS = 'There is a bug in Blender 4.5.0 LTS, that is why these sockets are blue.'\
  553. ' This will be fixed in future Blender versions.'
  554. def cleanup_4_5_0_LTS_interface_workaround(*args, **kwargs):
  555. # this is a function for cleaning up the workaround up above
  556. tree = kwargs['tree']
  557. if bpy_version == (4,5,0): return
  558. if not hasattr(tree, "interface_helper"): return
  559. import json
  560. try:
  561. interface_helper = json.loads(tree.interface_helper)
  562. except:
  563. return # this should mean it was never a problem to begin with.
  564. prPurple(f"Restoring Tree Interface for {tree.name}.")
  565. for interface_item in tree.interface.items_tree:
  566. if interface_item.item_type == 'PANEL': continue
  567. # now we need to restore the socket types, which are in the interface helper
  568. # keep the interface stuff around, I'll use it for marking arrays in the future
  569. int_info = interface_helper[interface_item.identifier]
  570. socket_type = int_info['bl_socket_idname']
  571. interface_item.socket_type = socket_type
  572. if interface_item.description == error_description_4_5_0_LTS:
  573. interface_item.description = ''
  574. versioning_tasks = [
  575. # node bl_idname task required keyword arguments
  576. (['ALL'], version_upgrade_very_old, ['node_tree', 'node'],),
  577. (['xFormBoneNode'], version_upgrade_bone_0_12_0_from_older, ['node'],),
  578. (['xFormBoneNode'], up_0_12_1_add_inherit_color, ['node'],),
  579. (['MantisTree', 'SchemaTree'], cleanup_4_5_0_LTS_interface_workaround, ['tree']),
  580. (['InputWidget'], up_0_12_13_add_widget_scale, ['node']),
  581. (['LinkFloor'], up_0_12_25_replace_floor_offset_type, ['node']),
  582. (['XFORM'], up_0_13_0_new_custom_prop_node, ['node', 'node_tree']),
  583. (['SchemaTree'], schema_enable_custom_interface_types, ['tree']),
  584. (['UtilityFCurve', 'LinkArmature'],
  585. update_fcurve_and_armature_node_to_multi_socket, ['node']),
  586. (['MantisTree', 'SchemaTree'], fix_bbone_default_scale_in_and_out, ['tree']),
  587. (['MantisTree', 'SchemaTree'], add_new_display_features_v0_13_5, ['tree']),
  588. (['UtilityMatrixFromCurve', 'UtilityMatrixCompose'],
  589. up_0_13_5_modes_for_matrix_from_curve_and_compose_matrix,
  590. ['node',], ),
  591. ]
  592. # WORKAROUNDS for bugs should go here:
  593. # 4.5.0 LTS valid socket types bug:
  594. def workaround_4_5_0_interface_update(tree, name, in_out, sock_type, parent_name, do_parent=False):
  595. # TODO: dedupe this code from i_o.py
  596. prRed("There is a bug in Blender 4.5.0 regarding node-group interface sockets. Working around it.")
  597. sock = tree.interface.new_socket(name=name, in_out=in_out, socket_type="NodeSocketGeometry")
  598. import json
  599. interface_helper = {} # initialize it if it is empty
  600. if tree.interface_helper: # may be empty, check here
  601. interface_helper = json.loads(tree.interface_helper)
  602. error_message = error_description_4_5_0_LTS
  603. interface_helper[sock.identifier] = {
  604. 'name' : sock.name,
  605. 'identifier' : sock.identifier,
  606. 'in_out' : sock.in_out,
  607. 'socket_type' : sock_type,
  608. 'bl_socket_idname' : sock_type,
  609. 'mantis_socket_category' : parent_name,
  610. 'description' : error_message,
  611. }
  612. sock.description = error_message # this tells the user why the socket looks weird.
  613. tree.interface_helper = json.dumps(interface_helper)
  614. if do_parent and (parent := tree.interface.items_tree.get(parent_name)):
  615. tree.interface.move_to_parent(
  616. sock,
  617. parent,
  618. 0, # what to do here?
  619. )
  620. return sock
  621. def socket_add_workaround_for_4_5_0_LTS(item, socket_collection, multi):
  622. import json
  623. tree = item.id_data
  624. interface_helper = json.loads(tree.interface_helper)
  625. socket_info = interface_helper.get(item.identifier)
  626. if not socket_info: raise RuntimeError(f"There has been an error adding the socket {item.name}")
  627. s = socket_collection.new(
  628. type=socket_info['bl_socket_idname'],
  629. name=item.name, # in case the user has changed it
  630. identifier=item.identifier,
  631. use_multi_input=multi, )
  632. return s