How to add nodes to event graph via python or other script?

What I’m trying to accomplish:
I want to create an addon that reads nodes from a Blender Geometry Node group within a .blend file, and then ‘translates’ those nodes into nodes used by Unreal’s Geometry Script.
Parts I know how to do:
How to create a python script.

Parts I need help with:
How to read from .Blend file (I’ll search Blender Forums for this)
How to create nodes in a Geometry Script Object via another script<-- This is what I need the most help with. Basically, how do I create a blueprint, add dynamic mesh component, add nodes to event graph, and connect/move nodes?
How to create an addon (I vaguely understand this, just drag and drop files into a “plugins” sub-folder created from ‘create new addon’ button)

If I have to use different scripting language like C#, let me know.

Update: I’ve managed to get UE to read python scripts. After 6+ hours, I located the command to create a blueprint and save it… after 10 more hours of looking, I can’t find how to add nodes to the event graph, to save my life.

It appears I have to do something like .modify() then I’m utterly lost. API documentation is a disaster. -_-

Just started looking into this myself. There are references in unreal to EdGraph and EdGraphNode, but nothing is exposed. Would love to be wrong about this.

From what I can tell, in Python you need to get a Blueprint object, then get the controller for the blueprint.

It’s the MVC programming pattern; model, view, controller. The model is the graph itself, the view is the viewport, and the controller is what allows the two to interact.

However, I was only able to accomplish this with ControlRig and using control rig’s python log in the messages window.

As an example, here’s the code I used in Python after getting the control rig’s Blueprint:

    def add_forward_solve(cls, bp_rig: unreal.ControlRigBlueprint):
        controller = bp_rig.get_controller_by_name('RigVMModel')

        script_path = '/Script/ControlRig.RigUnit_'

        # Add the Forward Solve Event node
        node_name = 'BeginExecution'
        controller.add_unit_node_from_struct_path(script_struct_path=script_path + node_name,
                                                  position=unreal.Vector2D(0.0, 0.0),
                                                  node_name='RigUnit_' + node_name)

        # Add the for_each node
        node = unreal.StringLibrary.conv_string_to_name(
            'DISPATCH_RigVMDispatch_ArrayIterator(in Array,out Element,out Index,out Count,out Ratio)'
        )
        controller.add_template_node(node,
                                     unreal.Vector2D(256.0, 0.0),
                                     'DISPATCH_RigVMDispatch_ArrayIterator')

        # add the GetAllControls node
        node_name = 'CollectionGetAll'
        controller.add_unit_node_from_struct_path(script_struct_path= script_path + node_name,
                                                  position=unreal.Vector2D(-160.0, 80.0),
                                                  node_name=node_name)
        controller.set_pin_default_value(pin_path='CollectionGetAll.TypeToSearch',
                                         default_value='Control',
                                         resize_arrays=False)
        controller.add_link('CollectionGetAll.Items',
                            'DISPATCH_RigVMDispatch_ArrayIterator.Array')

        # Attach Execution from Forward Solve
        controller.add_link('RigUnit_BeginExecution.ExecuteContext',
                            'DISPATCH_RigVMDispatch_ArrayIterator.ExecuteContext')

        # Get Bone Transform from Element
        node_name = 'GetTransform'
        controller.add_unit_node_from_struct_path(script_struct_path= script_path + node_name,
                                                  position=unreal.Vector2D(512.0, 208.0),
                                                  node_name=node_name)
        controller.set_pin_expansion('GetTransform.Item', False)
        controller.set_pin_default_value('GetTransform.Space', 'GlobalSpace')
        controller.set_pin_default_value('GetTransform.bInitial', 'False')
        controller.add_link('DISPATCH_RigVMDispatch_ArrayIterator.Element',
                            'GetTransform.Item')

        # Set Bone Transform
        node = unreal.StringLibrary.conv_string_to_name(
            'Set Transform::Execute(in Item,in Space,in bInitial,in Value,in Weight,in bPropagateToChildren)',
        )
        controller.add_template_node(notation=node,
                                     position=unreal.Vector2D(869.0, 0.0),
                                     node_name='Set Transform')
        controller.set_pin_expansion('Set Transform.Item', True)
        controller.set_pin_default_value('Set Transform.Item',
                                         '(Type=Bone,Name="None")')
        controller.set_pin_default_value('Set Transform.Space', 'GlobalSpace')
        controller.set_pin_default_value('Set Transform.bInitial', 'False')
        controller.set_pin_default_value('Set Transform.Weight', '1.0')
        controller.set_pin_default_value('Set Transform.bPropagateToChildren', 'True')
        controller.add_link('GetTransform.Transform', 'Set Transform.Value')

        # Add the Name replacement node
        node = unreal.StringLibrary.conv_string_to_name('Replace::Execute(in Name,in Old,in New,out Result)')
        controller.add_template_node(notation=node,
                                     position=unreal.Vector2D(576.0, 64.0),
                                     node_name='Replace')
        controller.add_link('DISPATCH_RigVMDispatch_ArrayIterator.Element.Name',
                            'Replace.Name')
        controller.set_pin_default_value('Replace.Old','CRC_', False)
        controller.add_link('Replace.Result', 'Set transform.Item.Name')
        controller.add_link('DISPATCH_RigVMDispatch_ArrayIterator.ExecuteContext',
                            'Set Transform.ExecuteContext')

Figuring out the names and types of nodes to add to the graph is probably going to be very difficult, but as I need this for work, I’ll continue to naturally chase down where these things can be found.

Well, I’m literally creating the nodes myself, so finding the name isn’t hard. :wink:
I eventually went the route of creating a plugin that adds all the necessary code to add nodes to blueprints… and turned those into nodes themselves. So, I have a blueprint that makes a new blueprint, and adds a series of nodes to it, and connects them all up.
It first reads from a text file that a list of nodes/connections exported from Blender’s Geometry nodes, and re-creates the node tree natively in Unreal’s Geometry Scripting.

To find the name of nodes, just r-click a node, click copy, go to a text editor, paste. It’s a string of text, and buried in there is the category and name. :slight_smile:

So, if you want to be able to add nodes to blueprints from within a blueprint, it’s included in my plugin here.

1 Like

Yep. I forget exactly how I did it, but I did some weird stuff to expose those functions.
Made some insane progress with the help of ChatGPT. :slight_smile:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.