geometry_node_graphgen.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. from .utilities import (prRed, prGreen, prPurple, prWhite, prOrange,
  2. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  3. wrapOrange,)
  4. def gen_morph_target_nodes(mod_name, mod_ob, targets, context, use_offset=True):
  5. from bpy import data
  6. from bpy.app import version as bpy_version
  7. modifier = mod_ob.modifiers.new(mod_name, type='NODES')
  8. mod_ob.add_rest_position_attribute = True
  9. ng = data.node_groups.new(mod_name, "GeometryNodeTree")
  10. modifier.node_group = ng
  11. ng.interface.new_socket("Geometry", in_out="INPUT", socket_type="NodeSocketGeometry")
  12. ng.interface.new_socket("Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry")
  13. inp = ng.nodes.new("NodeGroupInput")
  14. out = ng.nodes.new("NodeGroupOutput")
  15. # TODO CLEANUP here
  16. if (position := ng.nodes.get("Position")) is None: position = ng.nodes.new("GeometryNodeInputPosition")
  17. if (index := ng.nodes.get("Index")) is None: index = ng.nodes.new("GeometryNodeInputIndex")
  18. rest_position = ng.nodes.new("GeometryNodeInputNamedAttribute")
  19. rest_position.inputs["Name"].default_value="rest_position"
  20. rest_position.data_type = 'FLOAT_VECTOR'
  21. if use_offset == False:
  22. rest_position = position
  23. add_these = []
  24. props_sockets={}
  25. props_sockets_b_objects={}
  26. object_map = {}
  27. for i, t in enumerate(targets):
  28. mt_node = t.links[0].from_node
  29. mt_ob = mt_node.GetxForm().bGetObject()
  30. if mt_ob is None: # create it
  31. mt_ob = data.objects.new(mt_node.evaluate_input("Name"), data.meshes.new_from_object(self_ob))
  32. context.collection.objects.link(mt_ob)
  33. prOrange(f"WARN: no object found for f{mt_node}; creating duplicate of current object ")
  34. mt_name = mt_ob.name
  35. vg = mt_node.parameters["Morph Target"]["vertex_group"]
  36. if vg: mt_name = mt_name+"."+vg
  37. try:
  38. ob_relative = t.links[0].from_node.inputs["Relative to"].links[0].from_node.bGetObject()
  39. except IndexError:
  40. ob_relative = None
  41. ng.interface.new_socket(mt_name, in_out = "INPUT", socket_type="NodeSocketObject")
  42. ng.interface.new_socket(mt_name+" Value", in_out = "INPUT", socket_type="NodeSocketFloat")
  43. ob_node = ng.nodes.new("GeometryNodeObjectInfo")
  44. sample_index = ng.nodes.new("GeometryNodeSampleIndex"); sample_index.data_type = 'FLOAT_VECTOR'
  45. subtract = ng.nodes.new("ShaderNodeVectorMath"); subtract.operation="SUBTRACT"
  46. scale1 = ng.nodes.new("ShaderNodeVectorMath"); scale1.operation="SCALE"
  47. ng.links.new(input=inp.outputs[mt_name], output=ob_node.inputs["Object"])
  48. ng.links.new(input=index.outputs["Index"], output=sample_index.inputs["Index"])
  49. ng.links.new(input=position.outputs["Position"], output=sample_index.inputs["Value"])
  50. ng.links.new(input=sample_index.outputs["Value"], output=subtract.inputs[0])
  51. ng.links.new(input=ob_node.outputs["Geometry"], output=sample_index.inputs["Geometry"])
  52. if ob_relative: # TODO: this should also be exposed as an input
  53. ob_node1 = ng.nodes.new("GeometryNodeObjectInfo"); ob_node1.inputs["Object"].default_value = ob_relative
  54. sample_index1 = ng.nodes.new("GeometryNodeSampleIndex"); sample_index1.data_type = 'FLOAT_VECTOR'
  55. ng.links.new(input=index.outputs["Index"], output=sample_index1.inputs["Index"])
  56. ng.links.new(input=position.outputs["Position"], output=sample_index1.inputs["Value"])
  57. ng.links.new(input=ob_node1.outputs["Geometry"], output=sample_index1.inputs["Geometry"])
  58. ng.links.new(input=sample_index1.outputs["Value"], output=subtract.inputs[1])
  59. else:
  60. # ng.links.new(input=rest_position.outputs["Attribute"], output=subtract.inputs[1])
  61. ng.links.new(input=rest_position.outputs[0], output=subtract.inputs[1])
  62. ng.links.new(input=subtract.outputs["Vector"], output=scale1.inputs[0])
  63. # TODO: this should be exposed as a node tree input
  64. if vg:= mt_node.evaluate_input("Vertex Group"): # works
  65. vg_att = ng.nodes.new("GeometryNodeInputNamedAttribute"); vg_att.inputs["Name"].default_value=vg
  66. multiply = ng.nodes.new("ShaderNodeMath"); multiply.operation = "MULTIPLY"
  67. ng.links.new(input=vg_att.outputs["Attribute"], output=multiply.inputs[1])
  68. ng.links.new(input=inp.outputs[mt_name+" Value"], output=multiply.inputs[0])
  69. ng.links.new(input=multiply.outputs[0], output=scale1.inputs["Scale"])
  70. else:
  71. ng.links.new(input=inp.outputs[mt_name+" Value"], output=scale1.inputs["Scale"])
  72. add_these.append(scale1)
  73. object_map["Socket_"+str((i+1)*2)] = mt_node.GetxForm().bGetObject()
  74. from ..mantis_dataclasses import MantisSocketFiller
  75. prop = "Socket_"+str((i+1)*2+1)
  76. if bpy_version < (5, 2, 0):
  77. socket_value = MantisSocketFiller(
  78. property_name = prop,
  79. property_group = prop,
  80. socket_name = (socket_name := "Value."+str(i).zfill(3)),
  81. default_value = 1.0
  82. )
  83. props_sockets_b_objects[prop] = modifier
  84. props_sockets[prop] = socket_value
  85. else:
  86. socket_value = MantisSocketFiller(
  87. property_name = prop,
  88. property_group = prop,
  89. socket_name = (socket_name := "Value."+str(i).zfill(3)),
  90. default_value = 1.0
  91. )
  92. props_sockets_b_objects[prop] = modifier
  93. props_sockets[prop] = socket_value
  94. set_position = ng.nodes.new("GeometryNodeSetPosition")
  95. bake = ng.nodes.new("GeometryNodeBake")
  96. if bpy_version >= (5,0,0):
  97. bake.bake_items.new("GEOMETRY", 'Geometry')
  98. ng.links.new(inp.outputs["Geometry"], output=set_position.inputs["Geometry"])
  99. ng.links.new(set_position.outputs["Geometry"], output=bake.inputs[0])
  100. ng.links.new(bake.outputs[0], output=out.inputs[0])
  101. if use_offset == True:
  102. prev_node = ng.nodes.new("FunctionNodeInputVector")
  103. else:
  104. prev_node = position
  105. for i, node in enumerate(add_these):
  106. add = ng.nodes.new("ShaderNodeVectorMath"); add.operation="ADD"
  107. ng.links.new(prev_node.outputs[0], output=add.inputs[0])
  108. ng.links.new(node.outputs[0], output=add.inputs[1])
  109. prev_node = add
  110. if use_offset == True:
  111. ng.links.new(add.outputs[0], output=set_position.inputs["Offset"])
  112. else:
  113. ng.links.new(add.outputs[0], output=set_position.inputs["Position"])
  114. try:
  115. from .utilities import SugiyamaGraph
  116. SugiyamaGraph(ng, 12)
  117. except ImportError:
  118. pass # this is unlikely to fail since I package the wheel but if it does it shouldn't crash out.
  119. for socket, ob in object_map.items():
  120. if bpy_version < (5, 2, 0):
  121. modifier[socket]=ob
  122. else: # the syntax for this was changed in Blender 5.2.0
  123. setattr( getattr(modifier.properties.inputs, socket), 'value', ob)
  124. return modifier, props_sockets, props_sockets_b_objects
  125. def gen_object_instance_node_group():
  126. from bpy import data
  127. ng = data.node_groups.new("Object Instance", "GeometryNodeTree")
  128. ng.interface.new_socket("Object", in_out = "INPUT", socket_type="NodeSocketObject")
  129. ng.interface.new_socket("As Instance", in_out = "INPUT", socket_type="NodeSocketBool")
  130. ng.interface.new_socket("Object Instance", in_out="OUTPUT", socket_type="NodeSocketGeometry")
  131. inp = ng.nodes.new("NodeGroupInput")
  132. ob_node = ng.nodes.new("GeometryNodeObjectInfo")
  133. out = ng.nodes.new("NodeGroupOutput")
  134. ng.links.new(input=inp.outputs["Object"], output=ob_node.inputs["Object"])
  135. ng.links.new(input=inp.outputs["As Instance"], output=ob_node.inputs["As Instance"])
  136. ng.links.new(input=ob_node.outputs["Geometry"], output=out.inputs["Object Instance"])
  137. inp.location = (-200, 0)
  138. out.location = ( 200, 0)
  139. return ng
  140. def gen_import_obj_node_group():
  141. import bpy
  142. from bpy import data, types
  143. from math import pi as PI
  144. tree=bpy.data.node_groups.new("Import OBJ","GeometryNodeTree")
  145. tree.is_modifier=True
  146. tree.interface.new_socket(name="Path",description="Path to a OBJ file",in_out="INPUT",socket_type="NodeSocketString")
  147. tree.interface.new_socket(name="Geometry",description="",in_out="OUTPUT",socket_type="NodeSocketGeometry")
  148. Group_Input = tree.nodes.new("NodeGroupInput")
  149. Group_Output = tree.nodes.new("NodeGroupOutput")
  150. Import_OBJ = tree.nodes.new("GeometryNodeImportOBJ")
  151. Realize_Instances = tree.nodes.new("GeometryNodeRealizeInstances")
  152. Rotate_Instances = tree.nodes.new("GeometryNodeRotateInstances")
  153. Rotate_Instances.inputs[2].default_value=[PI/2,0.0, 0.0] # assume standard axes
  154. tree.links.new(Group_Input.outputs[0],Import_OBJ.inputs[0])
  155. tree.links.new(Rotate_Instances.outputs[0],Realize_Instances.inputs[0])
  156. tree.links.new(Realize_Instances.outputs[0],Group_Output.inputs[0])
  157. tree.links.new(Import_OBJ.outputs[0],Rotate_Instances.inputs[0])
  158. try:
  159. from .utilities import SugiyamaGraph
  160. SugiyamaGraph(tree, 4)
  161. except: # there should not ever be a user error if this fails
  162. pass
  163. return tree
  164. def gen_scale_object_data_modifier():
  165. # A basic modifier that simply scales the object's data (for widgets)
  166. # I need to be able to scale at import so that the user can easily
  167. # control widget scale as inputs to components.
  168. import bpy
  169. from bpy import data, types
  170. tree=bpy.data.node_groups.new("Scale Object Data","GeometryNodeTree")
  171. tree.is_modifier=True
  172. tree.interface.new_socket(name="Geometry",description="",in_out="OUTPUT",socket_type="NodeSocketGeometry")
  173. tree.interface.new_socket(name="Geometry",description="",in_out="INPUT",socket_type="NodeSocketGeometry")
  174. tree.interface.new_socket(name="Scale",description="",in_out="INPUT",socket_type="NodeSocketVector")
  175. Group_Input = tree.nodes.new("NodeGroupInput")
  176. Group_Output = tree.nodes.new("NodeGroupOutput")
  177. Transform_Geometry = tree.nodes.new("GeometryNodeTransform")
  178. tree.links.new(Transform_Geometry.outputs[0],Group_Output.inputs[0])
  179. tree.links.new(Group_Input.outputs[0],Transform_Geometry.inputs[0])
  180. tree.links.new(Group_Input.outputs[1],Transform_Geometry.inputs[3])
  181. try:
  182. from .utilities import SugiyamaGraph
  183. SugiyamaGraph(tree, 4)
  184. except: # there should not ever be a user error if this fails
  185. pass
  186. return tree
  187. def gen_simple_flip_modifier():
  188. import bpy
  189. from bpy import data, types
  190. tree=bpy.data.node_groups.new("Simple Flip","GeometryNodeTree")
  191. tree.is_modifier=True
  192. tree.interface.new_socket(name="Geometry",description="",in_out="OUTPUT",socket_type="NodeSocketGeometry")
  193. tree.interface.new_socket(name="Geometry",description="",in_out="INPUT",socket_type="NodeSocketGeometry")
  194. tree.interface.new_socket(name="Flip X",description="",in_out="INPUT",socket_type="NodeSocketBool")
  195. tree.interface.new_socket(name="Flip Y",description="",in_out="INPUT",socket_type="NodeSocketBool")
  196. tree.interface.new_socket(name="Flip Z",description="",in_out="INPUT",socket_type="NodeSocketBool")
  197. Group_Input = tree.nodes.new("NodeGroupInput")
  198. Group_Output = tree.nodes.new("NodeGroupOutput")
  199. Set_Position = tree.nodes.new("GeometryNodeSetPosition")
  200. Position = tree.nodes.new("GeometryNodeInputPosition")
  201. Combine_XYZ = tree.nodes.new("ShaderNodeCombineXYZ")
  202. Map_Range = tree.nodes.new("ShaderNodeMapRange")
  203. Map_Range_001 = tree.nodes.new("ShaderNodeMapRange")
  204. Map_Range_002 = tree.nodes.new("ShaderNodeMapRange")
  205. Map_Range.inputs[3].default_value = 1.0
  206. Map_Range_001.inputs[3].default_value = 1.0
  207. Map_Range_002.inputs[3].default_value = 1.0
  208. Map_Range.inputs[4].default_value = -1.0
  209. Map_Range_001.inputs[4].default_value = -1.0
  210. Map_Range_002.inputs[4].default_value = -1.0
  211. Vector_Math = tree.nodes.new("ShaderNodeVectorMath")
  212. Vector_Math.operation = "MULTIPLY"
  213. tree.links.new(Set_Position.outputs[0],Group_Output.inputs[0])
  214. tree.links.new(Group_Input.outputs[0],Set_Position.inputs[0])
  215. tree.links.new(Group_Input.outputs[1],Map_Range.inputs[0])
  216. tree.links.new(Map_Range.outputs[0],Combine_XYZ.inputs[0])
  217. tree.links.new(Map_Range_001.outputs[0],Combine_XYZ.inputs[1])
  218. tree.links.new(Map_Range_002.outputs[0],Combine_XYZ.inputs[2])
  219. tree.links.new(Group_Input.outputs[2],Map_Range_001.inputs[0])
  220. tree.links.new(Group_Input.outputs[3],Map_Range_002.inputs[0])
  221. tree.links.new(Combine_XYZ.outputs[0],Vector_Math.inputs[1])
  222. tree.links.new(Position.outputs[0],Vector_Math.inputs[0])
  223. tree.links.new(Vector_Math.outputs[0],Set_Position.inputs[2])
  224. try:
  225. from .utilities import SugiyamaGraph
  226. SugiyamaGraph(tree, 4)
  227. except: # there should not ever be a user error if this fails
  228. pass
  229. return tree