需求¶
我们在开发ChameleonTools的过程中,会有在一个工具中获得另一个工具的Python实例的需求。举个例子,比如我们的一个父工具想要能够直接控制其他的“子工具”;或者在当前工具的任务完成之后,触发另外一个工具的其他任务等等。
例子¶
在这里,我们用MinimalExample
作为“子工具”的例子,用一个新的工具来控制它。在这里,我们点击Tricker
工具的按钮,会触发Miminal **Example**
工具的OnClick
获取工具示例¶
想要控制"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
的实例。
事实上,我们可以在命令行窗口中调用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代码中触发MinimalExample
的on_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
它将返回当前的frameinspect.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]