I'm trying to make a script that assigns a custom Geometry Node Tree to a bezier curve. The node tree contains 3 custom node groups. I have created the groups with code but I can't assign them to the Geometry Node Modifier/Root Tree. I basically want the script to create a bezier curve with the Geometry Nodes already assigned, how can I do that? Thanks!!
-
$\begingroup$ Hello ! So you want your script to 1. Create a bezier curve object 2. Assign an existing GN tree to the bezier curve object ? $\endgroup$– GorgiousCommented Jan 10, 2022 at 13:32
-
$\begingroup$ Yes! My script thud far creates the curve with a GN modifier, creates the custom node groups that will be inside the GN tree, but I can't create the actual node tree that is assigned to the curve.. $\endgroup$– Alex ZampetasCommented Jan 10, 2022 at 13:36
3 Answers
You need to access the node tree through the geometry nodes modifier. Here's the start of a script that would allow you to do that:
import bpy
from mathutils import Vector
bpy.ops.curve.primitive_bezier_curve_add()
bpy.ops.object.modifier_add(type='NODES')
curve = bpy.context.active_object
def new_GeometryNodes_group():
''' Create a new empty node group that can be used
in a GeometryNodes modifier.
'''
node_group = bpy.data.node_groups.new('GeometryNodes', 'GeometryNodeTree')
inNode = node_group.nodes.new('NodeGroupInput')
inNode.outputs.new('NodeSocketGeometry', 'Geometry')
outNode = node_group.nodes.new('NodeGroupOutput')
outNode.inputs.new('NodeSocketGeometry', 'Geometry')
node_group.links.new(inNode.outputs['Geometry'], outNode.inputs['Geometry'])
inNode.location = Vector((-1.5*inNode.width, 0))
outNode.location = Vector((1.5*outNode.width, 0))
return node_group
# In 3.2 Adding the modifier no longer automatically creates a node group.
# This test could be done with versioning, but this approach is more general
# in case a later version of Blender goes back to including a node group.
if curve.modifiers[-1].node_group:
node_group = curve.modifiers[-1].node_group
else:
node_group = new_GeometryNodes_group()
curve.modifiers[-1].node_group = node_group
nodes = node_group.nodes
Update: As per an edit by Gorgious, Blender 3.2 no longer creates a node tree when you add the modifier. I've updated that edit to handle both older versions of Blender and Blender 3.2
You still have to hook the Geometry output of the Group Input node to your first node and the Geometry output of your last node to the Geometry Input node of the Group Output node. Here's an example with a single node:
group_in = nodes.get('Group Input')
group_out = nodes.get('Group Output')
new_node = nodes.new('GeometryNodeMeshToPoints')
node_group.links.new(group_in.outputs['Geometry'], new_node.inputs['Mesh'])
node_group.links.new(new_node.outputs['Points'], group_out.inputs['Geometry'])
For Blender 3.6
import bpy
from mathutils import Vector
bpy.ops.curve.primitive_bezier_curve_add()
bpy.ops.object.modifier_add(type='NODES')
curve = bpy.context.active_object
def new_GeometryNodes_group():
''' Create a new empty node group that can be used
in a GeometryNodes modifier.
'''
node_group = bpy.data.node_groups.new('GeometryNodes', 'GeometryNodeTree')
inNode = node_group.nodes.new('NodeGroupInput')
node_group.outputs.new('NodeSocketGeometry', 'Geometry')
outNode = node_group.nodes.new('NodeGroupOutput')
node_group.inputs.new('NodeSocketGeometry', 'Geometry')
node_group.links.new(inNode.outputs['Geometry'], outNode.inputs['Geometry'])
inNode.location = Vector((-1.5*inNode.width, 0))
outNode.location = Vector((1.5*outNode.width, 0))
return node_group
# In 3.2 Adding the modifier no longer automatically creates a node group.
# This test could be done with versioning, but this approach is more general
# in case a later version of Blender goes back to including a node group.
if curve.modifiers[-1].node_group:
node_group = curve.modifiers[-1].node_group
else:
node_group = new_GeometryNodes_group()
curve.modifiers[-1].node_group = node_group
nodes = node_group.nodes
A list of node sockets can be added with reference to this document.
https://docs.blender.org/api/current/bpy.types.NodeSocketStandard.html
-
-
$\begingroup$ @gorgious thanks for the edit. I modified it so that it doesn't create an extra node tree in versions of blender prior to 3.2 As a bonus I threw in a function to create the node group. $\endgroup$ Commented Jun 23, 2022 at 17:07
-
$\begingroup$ Hello ! This setup throws on V3.4 a
RuntimeError: Error: Cannot add socket to built-in node
on this lineinNode.outputs.new("NodeSocketGeometry", "Geometry")
would you happen to know a solution ? I'm resorting to creating a temporary join geometry node to link the input and output, then deleting it but I don't like it :) $\endgroup$– GorgiousCommented Feb 25, 2023 at 23:07 -
$\begingroup$ In 3.5 using
node_group.outputs.new('NodeSocketGeometry', 'Geometry')
instead ofinNode.outputs.new('NodeSocketGeometry', 'Geometry')
appears to fix that issue. Same case forinNode.inputs
. $\endgroup$– akrestixCommented May 10, 2023 at 15:05 -
$\begingroup$ In case someone reached here when trying to create a new socket in Group Input/Output, this answer was helpful for me since I was creating the new socket in the Group Input node and called
new
directly from that node, while I should have been calling it from thenode_group.inputs
, e.g.bpy.context.active_object.modifiers['GeometryNodes'].node_group.inputs.new('NodeSocketMaterial', 'Material')
$\endgroup$– ofekpCommented Jul 27, 2023 at 14:09
Blender 4.0
inputs
and outputs
has been replaced by this:
node_group.interface.new_socket(name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry")
in_out=
can be "INPUT" or "OUTPUT"
Changelog: https://docs.blender.org/api/4.0/change_log.html#bpy-types-nodetree
Full script:
import bpy
from mathutils import Vector
bpy.ops.curve.primitive_bezier_curve_add()
bpy.ops.object.modifier_add(type='NODES')
curve = bpy.context.active_object
def new_GeometryNodes_group():
''' Create a new empty node group that can be used
in a GeometryNodes modifier.
'''
node_group = bpy.data.node_groups.new('GeometryNodes', 'GeometryNodeTree')
inNode = node_group.nodes.new('NodeGroupInput')
node_group.outputs.new('NodeSocketGeometry', 'Geometry')
# NEW IN BLENDER > 4.0 -> creates a Group Input Socket
node_group.interface.new_socket(name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry")
outNode = node_group.nodes.new('NodeGroupOutput')
# NEW IN BLENDER > 4.0 -> creates a Group Ouput Socket
node_group.interface.new_socket(name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry")
node_group.links.new(inNode.outputs['Geometry'], outNode.inputs['Geometry'])
inNode.location = Vector((-1.5*inNode.width, 0))
outNode.location = Vector((1.5*outNode.width, 0))
return node_group
# In 3.2 Adding the modifier no longer automatically creates a node group.
# This test could be done with versioning, but this approach is more general
# in case a later version of Blender goes back to including a node group.
if curve.modifiers[-1].node_group:
node_group = curve.modifiers[-1].node_group
else:
node_group = new_GeometryNodes_group()
curve.modifiers[-1].node_group = node_group
nodes = node_group.nodes
In Blender 4.1, the following script will produce the following screenshot:
import bpy
from mathutils import Vector
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.curve.primitive_bezier_curve_add(radius=2)
bezier_curve = bpy.context.active_object
bezier_curve.name = 'MyBezierCurve'
geo_nodes = bezier_curve.modifiers.new(name='GeometryNodes', type='NODES')
node_group = bpy.data.node_groups.new('GeometryNodes', 'GeometryNodeTree')
geo_nodes.node_group = node_group
inNode = node_group.nodes.new('NodeGroupInput')
outNode = node_group.nodes.new('NodeGroupOutput')
transformNode = node_group.nodes.new('GeometryNodeTransform')
node_group.interface.new_socket(name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry")
node_group.interface.new_socket(name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry")
node_group.links.new(inNode.outputs['Geometry'], transformNode.inputs['Geometry'])
node_group.links.new(transformNode.outputs['Geometry'], outNode.inputs['Geometry'])
inNode.location = Vector((-3 * inNode.width, 0))
transformNode.location = Vector((0, 0))
outNode.location = Vector((3 * outNode.width, 0))
transformNode.inputs['Translation'].default_value = (0, 2, 2)
bpy.context.view_layer.update()
-
1$\begingroup$ You don't have to use
Vector
just like you don't use it on line 27 (second last line). Welcome to BSE! $\endgroup$ Commented Jun 3 at 16:34 -
1$\begingroup$ Good point and thanks for the welcome! :) $\endgroup$– KolibrilCommented Jun 4 at 8:30