How to connect nodes using script commands?

I am a beginner in scripting in python. I wanted to make a node setup in Composting tab by script command, so that when I run the script, the node setup gets built from scratch. (automating the process)

I am able to add new nodes using: bpy.ops.node.add_node(type='CompositorNodeMovieClip', use_transform=False). Here a new node “Movie Clip” gets added. Similarly I added an “Alpha Over node”. What command should I use now to make a connection between the two nodes(alpha over and movie clip).

Please help me.

1 Like

Like this:

node_tree = scene.node_tree
node_1 = node_tree.nodes.new("CompositorNodeMovieClip")
node_2 = node_tree.nodes.new("CompositorNodeAlphaOver")
node_tree.links.new(node_1.outputs["Image"], node_2.inputs[1])

Check the API docs for more details on the NodeTree class: https://docs.blender.org/api/current/bpy.types.NodeTree.html#bpy.types.NodeTree

5 Likes

What is scene here?
It gives me an error that scene is not defined.

scene = bpy.context.scene

And to set your node properties.
node_1.use_transform = False

1 Like

As AFWS said, scene could come from bpy.context.scene, but depending on the situation your code runs in, you might get the scene from other sources as well.
It should be the instance of bpy.types.Scene which contains your compositing node tree.

1 Like

@BYOB @AFWS
I finally was able to create a node setup from only script commands. Here is my result:


I have the script below, is there a efficient and more shorter way to do the same job?

import bpy

bpy.context.area.ui_type = 'CompositorNodeTree'

scene = bpy.context.scene
nodetree = scene.node_tree
# adding movie clip node 1
node1 = nodetree.nodes.new("CompositorNodeMovieClip")
bpy.ops.node.translate_attach(TRANSFORM_OT_translate={"value":(0, 200, 0), "orient_type":'GLOBAL', "orient_matrix":((1, 0, 0), (0, 1, 0), (0, 0, 1)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), "mirror":True, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False}, NODE_OT_attach={}, NODE_OT_insert_offset={})
bpy.ops.node.select_all(action='DESELECT')
# adding movie clip 2
node2 = nodetree.nodes.new("CompositorNodeMovieClip")
bpy.ops.node.translate_attach(TRANSFORM_OT_translate={"value":(0, -200, 0), "orient_type":'GLOBAL', "orient_matrix":((1, 0, 0), (0, 1, 0), (0, 0, 1)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), "mirror":True, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False}, NODE_OT_attach={}, NODE_OT_insert_offset={})
bpy.ops.node.select_all(action='DESELECT')
# adding alphaover node
node3 = nodetree.nodes.new("CompositorNodeAlphaOver")
bpy.ops.node.translate_attach(TRANSFORM_OT_translate={"value":(500, 0, 0), "orient_type":'GLOBAL', "orient_matrix":((1, 0, 0), (0, 1, 0), (0, 0, 1)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), "mirror":True, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False}, NODE_OT_attach={}, NODE_OT_insert_offset={})
bpy.ops.node.select_all(action='DESELECT')
# adding mask node
node4 = nodetree.nodes.new("CompositorNodeMask")
bpy.ops.node.translate_attach(TRANSFORM_OT_translate={"value":(200, 100, 0), "orient_type":'GLOBAL', "orient_matrix":((1, 0, 0), (0, 1, 0), (0, 0, 1)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), "mirror":True, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False}, NODE_OT_attach={}, NODE_OT_insert_offset={})
bpy.ops.node.select_all(action='DESELECT')
# adding compositor node
node5 = nodetree.nodes.new("CompositorNodeComposite")
bpy.ops.node.translate_attach(TRANSFORM_OT_translate={"value":(1000, 0, 0), "orient_type":'GLOBAL', "orient_matrix":((1, 0, 0), (0, 1, 0), (0, 0, 1)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), "mirror":True, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False}, NODE_OT_attach={}, NODE_OT_insert_offset={})
bpy.ops.node.select_all(action='DESELECT')
# connecting nodes
nodetree.links.new(node1.outputs["Image"],node3.inputs[1])
nodetree.links.new(node2.outputs["Image"],node3.inputs[2])
nodetree.links.new(node4.outputs["Mask"],node3.inputs[0])
nodetree.links.new(node3.outputs["Image"],node5.inputs[0])

bpy.context.area.ui_type = 'TEXT_EDITOR'

To translate the node to different position do I have to use this long piece of code always?

bpy.ops.node.translate_attach(TRANSFORM_OT_translate={“value”:(1000, 0, 0), “orient_type”:‘GLOBAL’, “orient_matrix”:((1, 0, 0), (0, 1, 0), (0, 0, 1)), “orient_matrix_type”:‘GLOBAL’, “constraint_axis”:(False, False, False), “mirror”:True, “use_proportional_edit”:False, “proportional_edit_falloff”:‘SMOOTH’, “proportional_size”:1, “use_proportional_connected”:False, “use_proportional_projected”:False, “snap”:False, “snap_target”:‘CLOSEST’, “snap_point”:(0, 0, 0), “snap_align”:False, “snap_normal”:(0, 0, 0), “gpencil_strokes”:False, “cursor_transform”:False, “texture_space”:False, “remove_on_cancel”:False, “release_confirm”:False, “use_accurate”:False}, NODE_OT_attach={}, NODE_OT_insert_offset={})

No, you can just assign to node.location.
e.g. node4.location = (100, 123.45)

If you do that, you can also remove the calls to bpy.ops.node.select_all(action='DESELECT').

1 Like

@blend_adarsh

I put this together to help me with getting node location. It adds it under Item > Node > under Label setting.

import bpy

def node_location(self, context):
    layout = self.layout
    col = layout.column(align=True)

    space_data = bpy.context.space_data
    act_node = space_data.node_tree.nodes.active

    col.label(text="Node Location:")
    col.label(text=f"X = {act_node.location.x}")
    col.label(text=f"Y = {act_node.location.y}")
                                       
bpy.types.NODE_PT_active_node_generic.append(node_location)
1 Like

@AFWS
Sorry, I am unable to understand you code completely. These are my specific doubts:

  1. What does bpy.types.NODE_PT_active_node_generic.append(node_location do?
  2. Why col.label(text="[text]") is called three times here? (i really didn’t understand this part)

Can you please brief a little more about this code of yours. It seems interesting.

Copy in text editor and run script. You’ll see exactly what it does.

I may sound dumb but please bare with me.
I ran the code. I do not see anything happening (nothing in Info, cmd, or interactive shell). Am I supposed to tweak or add something to see the output?

1 Like

Here is my final code for creating a node setup.

import bpy

bpy.context.area.ui_type = 'CompositorNodeTree'
nodetree = bpy.context.scene.node_tree

# adding movie clip node 1
node1 = nodetree.nodes.new("CompositorNodeMovieClip")
node1.location = (0,200)

# adding movie clip 2
node2 = nodetree.nodes.new("CompositorNodeMovieClip")
node2.location = (0,-200)

# adding alphaover node
node3 = nodetree.nodes.new("CompositorNodeAlphaOver")
node3.location = (500,0)

# adding mask node
node4 = nodetree.nodes.new("CompositorNodeMask")
node4.location = (200,100)

# adding compositor node
node5 = nodetree.nodes.new("CompositorNodeComposite")
node5.location = (1000,0)

# connecting nodes
nodetree.links.new(node1.outputs["Image"],node3.inputs[1])
nodetree.links.new(node2.outputs["Image"],node3.inputs[2])
nodetree.links.new(node4.outputs["Mask"],node3.inputs[0])
nodetree.links.new(node3.outputs["Image"],node5.inputs[0])

bpy.context.area.ui_type = 'TEXT_EDITOR'

I wanted to make this as an add-on. Like, by hitting a button the add-on should setup this node setup is Compositor workspace. I tried to learn on how to make an add-from YouTube but all of them say about adding a mesh or object in 3D viewport only. I do not know how to make an add-on for Compositor tab. Please help me.

You can also get rid of the bpy.context.area.ui_type assignments, they were only needed because you were using operators which have to be executed in the right context.

If you want to progress with your script you should take a look at the official api documentation:
https://docs.blender.org/api/current/index.html

Additionally in the text editor under templates>python you will find some useful examples.

Note that before you make an addon your next step would be to turn your script into an operator. Then you will be able to expose it as a button or assign shortcuts.

1 Like