Intro
In my post Improving the MegaAssemblies Workflow I mentioned that the method we were using to create Blueprint Functions via Python was no longer supported by Epic and would need to be revisited. After some explorations and carefully looking into the way Epic appears to want users to interface with Python in Blueprints, I have landed on a new way to integrate Python modules into Unreal which adheres to Epic's current rules.
Prerequisites
This solution assumes you have already enabled the Python Editor Script Plugin and set up your Unreal Python environment with appropriate paths to your tools, and possibly set up any additional pathing through a startup script. More information about this process can be found here on Epic's website.
The Problem
As referenced in that previous post, Epic redirects users to using the Execute Python Script, Execute Python Command, and Execute Python Command (Advanced) nodes which are available in Editor-Only Blueprints. This allows you to write Python snippets and code directly in the Blueprint, but this is a limited way to write code in general, let alone when seen from the scope of a studio and pipeline which makes use of a large code base.
When I first started investigating ways to do this dynamically, I attempted to use the Execute Python Script node, as it accepts any number of inputs and can return any number of outputs. This worked as expected, and using the network and code in the following snippets I was able to pass the name of a module and import it when running a Bluetility tool. However, I immediately found a catch with this method.
import importlib # Using built in __import__ so we can pass the name of the module as a variable tool = __import__(tool_name) # Reload the tool to make sure it's up to date importlib.reload(tool) # Run the tool via the built in run func tool.run()
As a matter of modularity and simplicity for other Tech Artists, I tried to migrate my new Blueprint network to a Blueprint Function Library. Doing this would make creating additional Python tools for Bluetility uses much easier, as users would only have to create their Bluetility, call my import_python_tool node, and pass the name of their module. For reasons unknown, the Execute Python Script node is not available within Blueprint Function Libraries. My best guess is that Blueprint Function Libraries are currently geared toward gameplay related Blueprints, and therefore limit access to the Python nodes as Python is not supported for runtime applications. This immediately broke the plan, so back to the drawing board we went.
The Solution
During my investigation I noticed I could still call the Execute Python Command node from the Blueprint Function Library. This node only accepts a single input for our Python command, and no additional inputs or outputs are available. However, utilizing a Format Text node, we can format text very similar to using str.format() in Python. This allows us to pass a variable into our text, then push that text to the Execute Python Command node as our script and run it. While it is effectively a workaround for the functionality of the Execute Python Script node, it does allow us to create modular Blueprint Functions for our Blueprint Function Library which accept variables perform tasks through Python.
In this first image is the Blueprint Function for our Blueprint Function Library. You can see that all we are doing is passing a Text Input into a Format Text node in the first argument position. A slight note here is I did notice this node acts up at times, and I recommend typing out your code before connecting your input as that seemed the most stable to me.
import importlib # import tool, reload tool via importlib to ensure it's up to date import {0} importlib.reload({0}) # run tool{0}.run()
In this code snippet, we are importing importlib so we can refresh our code each time it's run, which is great for both testing and making sure any updates to the module from the Tools directory are passed to users even if they leave the editor open. You'll notice the {0} entries in the code, which is where we are sending our text input. You can of course use multiple inputs, designating them {0} - {N} to match the number of the inputs on the Format Text node. Lastly, we call a function called run in our module, which will run our script. You can of course do this differently, but for us this was a simple standard to include a run function for all our Unreal tools written in Python.
Using Our New Solution
Now that we have a Blueprint Function Library that contains our import script, we simply need to call this function from any Bluetility we need to import a Python script in. From there, we simply pass in the exact name of the module we want to import, exactly like you would type it in an import statement in Python. At this point, on calling our Bluetility, the script will import and refresh our Python script, then call it's run function.
One caveat to this is that, as I alluded to earlier, it appears Epic makes the assumption that all Blueprint Function Libraries will be used in game, and to run the script we must provide a World Context input. This is easily done by calling Get Active World and passing it into the appropriate slot. This does not really do anything in our situation, but is mandatory to compile the script. Hopefully in future we will see some expansions on the Blueprint Function Library which will allow us to designate a library as editor only to bypass this.