Bootstrap Chameleon Logo

获取Chameleon工具实例变量

需求

我们在开发ChameleonTools的过程中,会有在一个工具中获得另一个工具的Python实例的需求。举个例子,比如我们的一个父工具想要能够直接控制其他的“子工具”;或者在当前工具的任务完成之后,触发另外一个工具的其他任务等等。

例子

在这里,我们用MinimalExample作为“子工具”的例子,用一个新的工具来控制它。在这里,我们点击Tricker工具的按钮,会触发Miminal **Example**工具的OnClick

G38_call_on_other_chameleon

获取工具示例

想要控制"MinimalExample"工具的窗口,那么我们就需要获得"MinimalExample"工具的Python实例,也就是 chameleon_example。 这个变量是在MinimalExample.json 中的 InitPyCmd定义的,其中的Python代码会在界面工具打开的时候执行。


MinimalExample.json:

"InitPyCmd": "import Example, importlib; importlib.reload(Example); chameleon_example = Example.MinimalExample.MinimalExample(%JsonPath)",

命令行窗口

在工具打开之后,我们可以在命令行窗口中输入print(chameleon_example),来查看这个变量的值。

可以看到,这个变量的值是一个MinimalExample的实例。

137_minimal_example_instance

事实上,我们可以在命令行窗口中调用chameleon_example.on_button_click() 来模拟点击"MinimalExample"工具中的按钮。

问题

如果我们在其他工具实例中的py文件中,尝试使用上面的chameleon_example.on_button_click(),会发现chameleon_example这个变量是不存在的。

原因是,它们两者处于不同的作用域。命令行和Json文件中的InitPyCmd都处于全局作用域,而其他工具实例中的py文件处于局部作用域。

全局作用域

全局作用域会有以下这些:

  • 命令行和REPL命令行
  • Chameleon JSON文件中的InitPyCmd
  • Chameleon JSON文件中的Command,OnClick等字段中的Python代码
  • 通过unreal.PythonBPLib.exec_python_command执行的代码,即使在工具内部执行,也是在全局作用域中。

工具代码内

由于unreal.PythonBPLib.exec_python_command执行的代码是在全局作用域中,因此我们可以在其他工具的Python代码中触发MinimalExampleon_button_click

MinimalExampleTricker.py:

import unreal
from Utilities.Utils import Singleton

class MinimalExampleTricker(metaclass=Singleton):
    def __init__(self, json_path: str):
        self.json_path = json_path
        self.data: unreal.ChameleonData = unreal.PythonBPLib.get_chameleon_data(self.json_path)

    def on_click(self):
        unreal.PythonBPLib.exec_python_command("chameleon_example.on_button_click()")

同理,我们也可以将其赋值给当前工具中的变量,这样我们就可以不再使用通过字符串来调用了。chameleon_example_ticker为当前工具的实例变量名,由于是在全局作用域,我们无法通过self来访问当前实例。

MinimalExampleTricker.py:

    ...
    def set_minimal_example(self, chalemeon_instance):
        self.minimal_example = chalemeon_instance

    def on_click(self):
        if not self.minimal_example:
            # set local variable, so that we can access it in other function
            # PythonBPLib.exec_python_command executes in global scope
            unreal.PythonBPLib.exec_python_command(
                "chameleon_example_ticker.set_minimal_example(chameleon_example)"
            )
        if self.minimal_example:
            self.minimal_example.on_button_click()

通过inspect获取全局变量中的Chameleon实例

我们可以通过Python标准库中inspect模块获取当前Scope的上一级Scope中的变量,从而获取到全局变量中的Chameleon实例。

例如下面的例子中,我们用到了:

  • inspect.currentframe 它将返回当前的frame
  • inspect.getouterframes 它将返回一个当前FrameInfo和所有外部FrameInfo的列表,最后一个条目代表堆栈上最外层的FrameInfo。
    def on_click(self):
        if not self.minimal_example:
            parent_frame = inspect.getouterframes(inspect.currentframe())[1].frame
            self.minimal_example = parent_frame.f_globals.get("chameleon_example")
        if self.minimal_example:
            self.minimal_example.on_button_click()

为了演示简单,我们直接硬编码[1]来获取当前frame的上一级frame。然后通过frame.f_globals来获取上一个Scrope全局变量中的chameleon_example变量。

更进一步,我们也可以将这个逻辑封装成一个函数,这样就可以在其他工具中使用了。

Utils.py

def get_chameleon_instance_in_parent_frame(name:str):
    for frame in inspect.getouterframes(inspect.currentframe()):
        if name in frame.frame.f_globals:
            return frame.frame.f_globals[name]

参考