node_common.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. from .utilities import (prRed, prGreen, prPurple, prWhite,
  2. prOrange,
  3. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  4. wrapOrange,)
  5. from ..base_definitions import GraphError, NodeSocket, MantisNode
  6. from collections.abc import Callable
  7. # BE VERY CAREFUL
  8. # the x_containers files import * from this file
  9. # so all the top-level imports are carried over
  10. from bpy.app import version as bpy_version
  11. # Utility to print links nicely for end user.
  12. def print_link_list(links):
  13. ret_str = ""
  14. for link in links:
  15. ret_str+= link.__repr__() + '\n'
  16. return ret_str
  17. # I've moved this to a separate function because the trace functions
  18. # are kind of hard to understand and the cycle check breaks the flow
  19. # of the code.
  20. def detect_cycle_in_trace(link, link_circle):
  21. if link.is_hierarchy:
  22. if link in link_circle:
  23. raise GraphError( "There is a cycle in the following links:\n" +
  24. print_link_list(link_circle))
  25. link_circle.append(link)
  26. # NOTE this is a dupe of trace_single_line, and it must be used
  27. # at any time before the tree is parsed (to check for cycles)
  28. # I don't want or need to pay the performance cost to check this everywhere
  29. def trace_single_line_safe(mantis_node, input_name, link_index=0):
  30. # DO: refactor this for new link class
  31. """Traces a line to its input."""
  32. nodes = [mantis_node]
  33. # a test for cyclic connections
  34. link_circle = []
  35. # Trace a single line
  36. if (socket := mantis_node.inputs.get(input_name) ):
  37. while (socket.is_linked):
  38. link = socket.links[link_index]; link_index = 0
  39. # should throw an error if it is in the set
  40. if detect_cycle_in_trace(link, link_circle):
  41. return
  42. if (socket := link.from_node.outputs.get(link.from_socket)):
  43. nodes.append(socket.node)
  44. if socket.can_traverse:
  45. socket = socket.traverse_target
  46. else: # this is an output.
  47. break
  48. else:
  49. break
  50. return nodes, socket
  51. def trace_single_line(mantis_node, input_name, link_index=0):
  52. # DO: refactor this for new link class
  53. """Traces a line to its input."""
  54. nodes = [mantis_node]
  55. # a test for cyclic connections
  56. if (socket := mantis_node.inputs.get(input_name) ):
  57. while (socket.is_linked):
  58. link = socket.links[link_index]; link_index = 0
  59. if (socket := link.from_node.outputs.get(link.from_socket)):
  60. nodes.append(socket.node)
  61. if socket.can_traverse:
  62. socket = socket.traverse_target
  63. else: # this is an output.
  64. break
  65. else:
  66. break
  67. return nodes, socket
  68. def trace_single_line_up(mantis_node, output_name,):
  69. """I use this to get the xForm from a link node."""
  70. nodes = [mantis_node]
  71. if hasattr(mantis_node, "outputs"):
  72. # Trace a single line
  73. if (socket := mantis_node.outputs.get(output_name) ):
  74. while (socket.is_linked):
  75. # This is bad, but it's efficient for nodes that only expect
  76. # one path along the given line
  77. link = socket.links[0] # TODO: find out if this is wise.
  78. other = link.to_node.inputs.get(link.to_socket)
  79. if (other):
  80. socket = other
  81. if socket.can_traverse:
  82. socket = socket.traverse_target
  83. nodes.append(socket.node)
  84. else: # this is an input.
  85. nodes.append(socket.node)
  86. break
  87. else:
  88. break
  89. return nodes, socket
  90. def trace_line_up_branching(mantis_node : MantisNode, output_name : str,
  91. break_condition : Callable = lambda mantis_node : False):
  92. """ Returns all leaf nodes at the ends of branching lines from an output."""
  93. leaf_nodes = []
  94. if hasattr(mantis_node, "outputs"):
  95. # Trace a single line
  96. if (socket := mantis_node.outputs.get(output_name) ):
  97. check_sockets={socket}
  98. while (check_sockets):
  99. # This is bad, but it's efficient for nodes that only expect
  100. # one path along the given line
  101. socket = check_sockets.pop()
  102. for link in socket.links:
  103. other = link.to_node.inputs.get(link.to_socket)
  104. if (other):
  105. socket = other
  106. if break_condition(socket.node):
  107. leaf_nodes.append(socket.node)
  108. elif socket.is_input and socket.can_traverse:
  109. check_sockets.add(socket.traverse_target)
  110. else: # this is an input.
  111. leaf_nodes.append(socket.node)
  112. return leaf_nodes
  113. # For this, we basically just evaluate the driver once and use the result
  114. def find_driven_default_value(mantis_node, socket_name):
  115. from ..misc_nodes import UtilityDriver, UtilitySwitch
  116. from .utilities import get_ui_node
  117. xf_inputs_values_var_names = []
  118. node_line, _last_socket = trace_single_line(mantis_node, socket_name)
  119. driver_node = None
  120. driver_type = 'SUM'
  121. keys = [ (0, 0), (1, 1) ]
  122. for driver_node in node_line:
  123. if driver_node.node_type == 'DRIVER': break
  124. else: # it's not being driven
  125. return mantis_node.evaluate_input(socket_name)
  126. if isinstance(driver_node, UtilityDriver):
  127. driver_type = driver_node.evaluate_input("Driver Type")
  128. # then get the variables
  129. for i, input in enumerate(driver_node.inputs):
  130. # unicode for 'a'
  131. var_name = chr(i - 2 + 96)
  132. if i == 0 : continue # the Driver Type doesn't interest us
  133. elif i ==1 : continue # for now. we do need the fCurve
  134. else:
  135. if not input.is_linked: continue # nothing to do here
  136. var_node = input.links[0].from_node
  137. # NOTE: this var_node will not be prepared on time unless we sort the graph by depth
  138. # and it doesn't seem to be possible to do that without breaking drivers... so
  139. # HACK it's safe to do this, but VERY ugly and bad.
  140. if not var_node.prepared:
  141. var_node.bPrepare()
  142. # end HACK
  143. var_data = var_node.parameters["Driver Variable"]
  144. var_type = var_data['type']
  145. var_channel = var_data['channel']
  146. prop_name = None; value = None
  147. xf1 = var_node.GetxForm(index=1)
  148. match var_type:
  149. case "SINGLE_PROP":
  150. prop_name = var_node.evaluate_input("Property")
  151. # we'll get the property value in the next step
  152. case "TRANSFORMS":
  153. matrix1 = xf1.evaluate_input['Matrix']
  154. # Mantis ensures that these are identity at creation time
  155. if "LOC" in var_channel or "ROT" in var_channel:
  156. value = 0
  157. else: # Scale, defaults to 1
  158. value = 1
  159. case "ROTATION_DIFF":
  160. xf2 = var_node.GetxForm(index=2)
  161. matrix1 = xf1.evaluate_input['Matrix']
  162. matrix2 = xf2.evaluate_input['Matrix']
  163. value = matrix1.to_quaternion().rotation_difference(matrix2.to_quaternion()).angle
  164. case "LOC_DIFF":
  165. xf2 = var_node.GetxForm(index=2)
  166. matrix1 = xf1.evaluate_input['Matrix']
  167. matrix2 = xf2.evaluate_input['Matrix']
  168. value = (matrix1.to_translation() - matrix2.to_translation()).length
  169. case "CONTEXT_PROP":
  170. raise NotImplementedError("Mantis Does not yet support Context Property")
  171. pass
  172. xf_inputs_values_var_names.append((xf1, prop_name, value, var_name))
  173. # Now we'll get the keyframe points and sort them, too!
  174. if driver_node.inputs['fCurve'].is_linked:
  175. fCurve_node = driver_node.inputs['fCurve'].links[0].from_node
  176. extrapolation_mode = 'CONSTANT'
  177. keys = []
  178. if fCurve_node:
  179. extrapolation_mode = fCurve_node.evaluate_input("Extrapolation Mode")
  180. for l in fCurve_node.inputs['Keyframes'].links:
  181. key_node = l.from_node
  182. if key_node:
  183. keys.append( (key_node.evaluate_input("Frame"), key_node.evaluate_input("Value")) )
  184. keys = sorted( keys, key = lambda a : a[0]) # sort by the frame
  185. elif isinstance(driver_node, UtilitySwitch):
  186. # Reimplementing the GetxForm from the switch here
  187. # because it is faster to inline this and store the trace result
  188. trace = trace_single_line(driver_node, "Parameter" )
  189. for node in trace[0]:
  190. if node.node_type == 'XFORM':
  191. xf = node # reuse trace HERE
  192. prop_name = trace[1].name # saves us from tracing again.
  193. xf_inputs_values_var_names.append(( xf, prop_name, None, "a")); break
  194. else:
  195. raise RuntimeError(f"ERROR: Cannot Set the default of the driven value {socket_name} \n"
  196. f"in node {mantis_node}")
  197. # We're gonna assume the xForm is sorting these right
  198. # but we'll check the name if it is cheap
  199. default_values = {}
  200. for xf, prop_name, value, var_name in xf_inputs_values_var_names:
  201. i=0
  202. for output in xf.outputs:
  203. if 'xForm Out' == output.name: continue
  204. if output.name == prop_name: break
  205. i+=1
  206. node_line, _last_socket = trace_single_line(xf, "Custom Properties", i)
  207. custom_prop_node = node_line[-1]
  208. if value:
  209. default_values[var_name] = value; continue
  210. if prop_name:
  211. if custom_prop_node.evaluate_input("Name") == prop_name:
  212. custom_prop_type = custom_prop_node.evaluate_input("Type")
  213. default_prop_name = ""
  214. match custom_prop_type:
  215. case "BOOL":
  216. default_prop_name = 'Default (Bool)'
  217. case "FLOAT":
  218. default_prop_name = 'Default (Float)'
  219. case "INT":
  220. default_prop_name = 'Default (Int)'
  221. case _:
  222. raise RuntimeError(f"ERROR: Cannot Set the default of the driven value {socket_name} \n"
  223. f"in node {mantis_node}"
  224. f"Reason: Custom Property {prop_name} is not a valid type." )
  225. default_values[var_name] = float(custom_prop_node.evaluate_input(default_prop_name))
  226. else:
  227. raise RuntimeError(f"ERROR: failed to get proeprty {prop_name} for node {xf}, \n"
  228. f"cannot Set the default of the driven value {socket_name} \n"
  229. f"in node {mantis_node}"
  230. "Reason: Cannot Find Custom Property Node")
  231. frame = 0.0
  232. if default_values:
  233. match driver_type:
  234. case "AVERAGE":
  235. frame = sum(default_values.values())/len(default_values)
  236. case "SUM":
  237. frame = sum(default_values.values())
  238. case "MIN":
  239. frame = min(default_values.values())
  240. case "MAX":
  241. frame = max(default_values.values())
  242. case "SCRIPTED":
  243. raise NotImplementedError (f"ERROR: Cannot Set the default of the driven value {socket_name} \n"
  244. f"in node {mantis_node} \n"
  245. "Reason: Driver Expressions are not yet implemented.")
  246. # Now we apply the fCurve
  247. # For now, We'll assume LINEAR keys TODO: come back to this during Pose update!
  248. if len(keys) >= 2:
  249. previous_frame = None
  250. next_frame = None
  251. previous_value = None
  252. next_value = None
  253. for key in keys:
  254. if key[0] <= frame: previous_frame, previous_value = key
  255. if key[0] >= frame: next_frame, next_value = key
  256. if previous_frame is not None and next_frame is not None: break
  257. else: # if our frame is out-of-bounds we need a slope to extrapolate on.
  258. if previous_frame and not next_frame:
  259. next_frame, next_value = previous_frame, previous_value
  260. if extrapolation_mode == 'LINEAR':
  261. previous_frame, previous_value = keys[-2] # we need the second last key to extrapolate
  262. elif next_frame and not previous_frame:
  263. previous_frame, previous_value = next_frame, next_value
  264. if extrapolation_mode == 'LINEAR':
  265. next_frame, next_value = keys[1] # we need the second key to extrapolate
  266. else:
  267. raise RuntimeError (f"ERROR: Cannot Set the default of the driven value {socket_name} \n"
  268. f"in node {mantis_node} \n"
  269. "Reason: Cannot place value on driver fCurve.")
  270. if previous_frame == next_frame: # the frame is exactly on a key
  271. return previous_value
  272. # this is just a simple lerp because we are assuming the keys are linear for now
  273. result = previous_value + ((frame - previous_frame) / (next_frame - previous_frame)) * (next_value - previous_value)
  274. elif len(keys) ==1:
  275. result = key[1] # flat curve, take the value of the only key
  276. else:
  277. raise RuntimeError (f"ERROR: Cannot Set the default of the driven value {socket_name} \n"
  278. f"in node {mantis_node} \n"
  279. "Reason: Invalid fCurve has 0 keyframes")
  280. return result
  281. # this function exists to walk back to the first ancestor node of XFORM or LINK without
  282. # breaking the traverse behaviour which I still need for various reasons.
  283. def get_parent_xForm_info(mantis_node, socket_name = 'Relationship'):
  284. from ..mantis_dataclasses import xForm_info
  285. # we re-implement the trace logic here for performance.
  286. # because this kind of connection has traversal (for Links to find their children), we must
  287. # simply seek back to the first available xForm or Link and get its xForm info.\
  288. parent_node = None
  289. if (socket := mantis_node.inputs.get(socket_name) ):
  290. while (socket.is_linked):
  291. link = socket.links[0]
  292. if (socket := link.from_node.outputs.get(link.from_socket)):
  293. if socket.node.node_type in ['LINK', 'XFORM']:
  294. parent_node = socket.node; break
  295. if socket.can_traverse:
  296. socket = socket.traverse_target
  297. else: # this is an output.
  298. break
  299. else:
  300. break
  301. parent_xForm_info = None
  302. if parent_node:
  303. if parent_node.node_type == 'XFORM':
  304. parent_xForm_info = parent_node.parameters['xForm Out']
  305. else:
  306. if 'Output Relationship' in parent_node.parameters.keys():
  307. parent_xForm_info = parent_node.parameters['Output Relationship']
  308. else: # TODO refactor Inherit Node to have the same output as the other link nodes.
  309. parent_xForm_info = parent_node.parameters['Inheritance']
  310. if parent_xForm_info is None: parent_xForm_info = xForm_info() # default to empty
  311. return parent_xForm_info
  312. def setup_custom_property_inputs_outputs(mantis_node):
  313. from .utilities import get_ui_node
  314. if mantis_node.signature[0] == 'SCHEMA_AUTOGENERATED':
  315. from ..base_definitions import custom_props_types
  316. if mantis_node.__class__.__name__ not in custom_props_types:
  317. raise RuntimeError(wrapRed(f"Custom Properties not allowed for class {mantis_node.__class__.__name__}"))
  318. return
  319. else:
  320. ui_node = get_ui_node(mantis_node.ui_signature, mantis_node.base_tree)
  321. if ui_node:
  322. setup_custom_props_from_np(mantis_node, ui_node)
  323. else:
  324. prRed(f"Failed to setup custom properties for: {mantis_node.ui_signature}")
  325. def setup_custom_props_from_np(mantis_node, ui_node):
  326. for inp in ui_node.inputs:
  327. if inp.identifier == "__extend__": continue
  328. if not (inp.name in mantis_node.inputs.keys()) :
  329. socket = NodeSocket(is_input = True, name = inp.name, node = mantis_node,)
  330. mantis_node.inputs[inp.name] = socket
  331. mantis_node.parameters[inp.name] = None
  332. for attr_name in ["min", "max", "soft_min", "soft_max", "description"]:
  333. try:
  334. setattr(socket, attr_name, getattr(inp, attr_name))
  335. except AttributeError:
  336. pass
  337. for out in ui_node.outputs:
  338. if out.identifier == "__extend__": continue
  339. if not (out.name in mantis_node.outputs.keys()) :
  340. mantis_node.outputs[out.name] = NodeSocket(is_input = False, name = out.name, node = mantis_node,)
  341. def prepare_parameters(mantis_node):
  342. # some nodes add new parameters at runtime, e.g. Drivers
  343. # so we need to take that stuff from the mantis_nodes that have
  344. # been executed prior to this node.
  345. for s_name, sock in mantis_node.inputs.items():
  346. if not (sock.is_linked):
  347. continue
  348. if (sock.name in sock.links[0].from_node.parameters.keys()):
  349. mantis_node.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]
  350. # should work, this is ugly.
  351. def check_for_driver(mantis_node, input_name, value_index = None, array_index = 0):
  352. prop = mantis_node.evaluate_input(input_name, array_index)
  353. if (value_index is not None):
  354. try:
  355. prop = prop[value_index]
  356. except KeyError: # We'll try to use the driver for all three channels.
  357. pass
  358. # TODO an Index or Key error here should have a reasonable error message
  359. return (prop.__class__.__name__ == 'MantisDriver')
  360. # this is neccesary because some things use dict properties for dynamic properties and setattr doesn't work
  361. def safe_setattr(ob, att_name, val):
  362. ob_class_name = ob.__class__.__name__
  363. match ob_class_name:
  364. case "NodesModifier":
  365. if bpy_version < (5, 2, 0):
  366. ob[att_name]=val
  367. else:
  368. setattr( getattr( ob.properties.inputs, att_name), 'value', val)
  369. case "Key":
  370. ob.key_blocks[att_name].value=val
  371. case _:
  372. setattr(ob, att_name, val)
  373. # do: make this function end in a single setattr
  374. def evaluate_sockets(mantis_node, b_objects, props_sockets,):
  375. # TODO: I think it is wasteful to run this once per b_object for e.g. Link nodes.
  376. # Should be able to get the results once and pass 'em along. Probably only saves
  377. # a handful of fn calls in a big graph. Not worth doing a big refactor right now
  378. # Because Mantis Nodes are sometimes a composition of different Blender objects,
  379. # We have to set properties on different Blender Objects and the easiest way
  380. # is to send the object in directly instead of trying to find e.g. nested properties
  381. for property_group, b_object in b_objects.items():
  382. ob_class_name = b_object.__class__.__name__
  383. for filler_item in props_sockets.values():
  384. filler_property_group = filler_item.property_group or "DEFAULT"
  385. prop = filler_item.property_name
  386. if filler_property_group != property_group: continue
  387. if b_object is None: continue
  388. input_name = filler_item.socket_name
  389. default = ( filler_item.default_value, )
  390. value_index = filler_item.value_index
  391. array_index = filler_item.array_index
  392. combo = filler_item.combo_property
  393. vector = filler_item.vector_property
  394. # Check for a driver -- this is inlined from check_for_driver()
  395. evaluated_value = mantis_node.evaluate_input(input_name, array_index)
  396. driver = evaluated_value
  397. drivers_exist = (evaluated_value.__class__.__name__ == 'MantisDriver', )
  398. single_driver_input = drivers_exist[0]
  399. if combo and vector: # we map an element of the socket's vector to a single scalar property
  400. evaluated_value = evaluated_value[value_index]
  401. drivers_exist = (evaluated_value.__class__.__name__ == 'MantisDriver', )
  402. if vector and not combo: # then we map a vector to a vector and have to check for drivers per-element
  403. default = filler_item.default_value
  404. # the evaluated value could either be a vector of scalar values (or scalar drivers) OR one driver
  405. if single_driver_input: # we have alredy checked for single-driver, so this is a vector otherwise
  406. drivers_exist = tuple( True for _unused in range(len(default)) )
  407. else:
  408. drivers_exist = tuple( val.__class__.__name__ == 'MantisDriver' for val in evaluated_value )
  409. assign_this_value = evaluated_value # Usually we just assign the value right away
  410. if single_driver_input and vector: # in this pernicious case, we need to lift
  411. assign_this_value = list(default) # the evaluated value from scalar to vector
  412. # We had to pack everything into this weird iterable format so we can handle
  413. # all 4 cases, (scalar, vector, scalar combo, vector combo) in one place
  414. # this isn't perfectly optimal but I expect it to cost a few milliseconds at most
  415. # and while it is kind of confusing it is nice to keep all this in one code path
  416. if any(drivers_exist):
  417. if vector and not combo and isinstance(assign_this_value, tuple):
  418. assign_this_value = list(assign_this_value) # because we have to mutate it
  419. for i, (default_value, driver_exists) in enumerate(zip(default, drivers_exist)):
  420. if driver_exists: # we need to pack data for the driver
  421. if filler_item.is_animatable == False:
  422. raise RuntimeError(f"Driver is not allowed for input \"{input_name}\" in node \"{mantis_node}\".")
  423. # NOTE at this point we can inject the driver's default resulting value
  424. # and we have to do so in Curve Pin where it has a special case
  425. # In the future I may implement this HERE in general
  426. printname = wrapOrange(mantis_node)
  427. if vector and not combo: assign_this_value[i] = default_value
  428. else: assign_this_value = default_value
  429. if mantis_node.node_type == 'LINK': printname = wrapOrange(b_object.id_data.name)
  430. elif mantis_node.node_type == 'XFORM': printname = wrapOrange(mantis_node.bGetObject().name)
  431. print(f"Adding driver to property: {wrapPurple(prop)} in {printname} in node: {wrapWhite(mantis_node.ui_signature[1:])}")
  432. # NOW we can set up the driver.
  433. # TODO: Maybe can prefill this in the socket template or cache the result per-socket
  434. driver_properties = {} # use this dict to pack this data without mutating the driver itself
  435. ob_class_name = b_object.__class__.__name__
  436. if ob_class_name == "NodesModifier":
  437. # this doesn't even support path_from_id because it is is a bpy_props property group
  438. # So we'll construct it directly
  439. if bpy_version < (5, 2, 0): # get the path as normal, but with ["brackets and quotes"]
  440. driver_properties["path_from_id"] = b_object.path_from_id(f"[\"{prop}\"]")
  441. else:
  442. driver_properties["path_from_id"] = b_object.path_from_id("properties") + f".inputs.{prop}.value"
  443. else:
  444. driver_properties["path_from_id"] = b_object.path_from_id(prop,)
  445. driver_properties['owner'] = b_object # it is the CALLER's responsibility to send the right object!!
  446. # we need a special case here for Bones :/
  447. # TODO: refactor so I don't need this
  448. if mantis_node.__class__.__name__ == 'xFormBone':
  449. from ..xForm_socket_templates import attributes_shared_by_bone_and_pose_bone
  450. if prop in attributes_shared_by_bone_and_pose_bone:
  451. driver_properties['owner'] = b_objects['pose_bone']
  452. driver_properties["path_from_id"] = b_objects['pose_bone'].path_from_id(prop,)
  453. if (vector and not combo):
  454. driver_properties['ind'] = i
  455. if vector and single_driver_input:
  456. driver_properties['ind'] = -1 # the driver will be added to all 3 channels
  457. # TODO: I would like the socket filler to handle the special cases here.
  458. # even if it means I have to pack another operator in the filler.
  459. resolved_prop_name = prop
  460. match ob_class_name:
  461. case "NodesModifier": # It is a dict element.
  462. resolved_prop_name = "[\""+prop+"\"]"
  463. case "Key": # that is, the outer data block that owns shape keys, not a key-frame.
  464. resolved_prop_name = "key_blocks[\""+prop+"\"].value"
  465. driver_properties["prop"] = resolved_prop_name
  466. driver_index = value_index if vector and not single_driver_input else None
  467. if driver_index: driver = driver[driver_index]
  468. mantis_node.drivers.append( (driver, driver_properties) )
  469. # We wait until now to skip this because it is important to do the
  470. # is_animatable check above and give the user a meaningful error
  471. if prop is None: continue
  472. # Some socket values need to be transformed, so we can use the operator to do that.
  473. if filler_item.property_value_operator:
  474. assign_this_value = filler_item.property_value_operator(mantis_node, evaluated_value)
  475. # at this point there is ONE filler per property and we just have to assign the value
  476. if ob_class_name not in {'NodesModifier', 'Key'}:
  477. setattr(b_object, prop, assign_this_value)
  478. elif ob_class_name == "NodesModifier":
  479. if bpy_version < (5, 2, 0):
  480. b_object[prop] = assign_this_value
  481. else:
  482. setattr( getattr( b_object.properties.inputs, prop), 'value', assign_this_value)
  483. else: # ob_class_name == "Key":
  484. b_object.key_blocks[prop].value = assign_this_value
  485. def finish_drivers(mantis_node):
  486. drivers = []
  487. for driver, driver_properties in mantis_node.drivers:
  488. drivers_from_socket = (driver,)
  489. if isinstance(driver, (tuple, list)):
  490. drivers_from_socket = driver # this is a collection e.g. from a Vector Combine
  491. for driver in drivers_from_socket:
  492. driver_copy = driver.copy() # it is safe to copy it here because it is guarenteed to be filled out.
  493. driver_copy.update(driver_properties)
  494. drivers.append(driver_copy)
  495. from .drivers import CreateDrivers
  496. CreateDrivers(drivers)