Bootstrap Chameleon Logo

编辑器扩展

为指定的资源编辑器,加上独特的功能

编辑器扩展

除了给主菜单,ContentBrowser,加上菜单项之外,还可以给指定的资源编辑器加上菜单项,比如给材质编辑器,物理资源编辑器加上菜单项, 为当前正在编辑的资源扩展功能

菜单的入口和菜单项,也都是在MenuConfig.json中配置的

当前支持的资源编辑器有:

  • Material Editor
  • Physics Asset Editor
  • Control Rig Editor

材质编辑器

Menu Extension in Unreal Engine Material Editor

以材质编辑器为例, 当我们MenuConfig.json 中添加以下配置后,就可以在材质编辑器中看到上图中菜单项

MenuConfig.json

{: .my-border }

```JSON
"OnMaterialEditorMenu": {
    "name": "Python Menu On Material Editor",
    "items":
    [
        {
            "name": "TA Python Material Example",
            "items": [
                {
                    "name": "Print Editing Material / MF",
                    "command": "print(%asset_paths)"
                },
                {
                    "name": "Log Editing Nodes",
                    "command": "editing_asset = unreal.load_asset(%asset_paths[0]); unreal.PythonMaterialLib.log_editing_nodes(editing_asset)"
                },
                {
                    "name": "Selected Nodes --> global variable _r",
                    "command": "_r = unreal.PythonMaterialLib.get_selected_nodes_in_material_editor(unreal.load_asset(%asset_paths[0]))"
                },
                {
                    "name": "Selected Node --> _r",
                    "command": "_r = unreal.PythonMaterialLib.get_selected_nodes_in_material_editor(unreal.load_asset(%asset_paths[0]))[0]"
                }
            ]
        }
    ]
}

与在主菜单中添加菜单项不同,材质编辑器在Unreal Engine编辑器中是会同时出现多个窗口的情况的。因此我们在菜单项中执行的Python代码,需要能够明确区分当前正在编辑的材质资源,这里我们通过%asset_paths 变量占位符。TAPython中的其他变量占位符可见这里:变量占位符

具体来看其中的配置项,例如第一个菜单项"Print Editing Material / MF",它实际执行的代码为print(%asset_paths),其中%asset_paths变量占位符,在运行时会被实际的值替换。这里的实际值为当前正在编辑的材质资源的路径数组。比如['/Game/ LevelPrototyping/Materials/M_PrototypeGrid']

NOTE
通常情况下,%asset_paths实际是一个长度为1的资源路径数组

第二个菜单项"Log Editing Nodes":

{
    "name": "Log Editing Nodes",
    "command": "editing_asset = unreal.load_asset(%asset_paths[0]); unreal.PythonMaterialLib.log_editing_nodes(editing_asset)"
},

这里的command中,通过unreal.load_asset获得当前正在编辑的材质资源,然后调用unreal.PythonMaterialLib.log_editing_nodes,用于打印材质资源的节点信息。

其他和材质节点相关的编辑和修改操作,可见这里:Manipulate Material Expression Nodes of Material with Python in Unreal Engine

物理资源编辑器

和材质编辑器类似,当我们在MenuConfig.json的OnPhysicsAssetEditorMenu中添加以下配置后,就可以在物理资源编辑器如下图的菜单项。

Custom menu in Unreal Engine Physics Asset Editor

MenuConfig.json

"OnPhysicsAssetEditorMenu": {
    "name": "Python Menu On Physics Asset Editor",
    "items":
    [
        {
            "name": "TA Python Physics Asset Example",
            "items": [
                {
                    "name": "Print Physics Asset",
                    "command": "print(%asset_paths)"
                },
                {
                    "name": "Physics Tool",
                    "ChameleonTools": "../Python/ControlRiggingTools/physics_tools.json"
                }
            ]
        }
    ]
},

其中不仅通过command来执行Python代码,还通过ChameleonTools来加载Chameleon中的工具。例如,下面是一个用来编辑物理资源中Bodies的简单工具有演示。

G28_physics_asset_tools_preview
{
    "name": "Physics Tool",
    "ChameleonTools": "../Python/ControlRiggingTools/physics_tools.json"
}

细心的用户,在这里可能已经发现了,我们在配置ChameleonTools:"Physics Tool"的时候,并没有指定%asset_paths这样的变量占位符,但physics_tools工具中是一定需要知道当前正在编辑的物理资源的。具体我们看一下physics_tools.py中的做法:

  1. 在工具的__init__函数中主动调用了self.update_physics_asset()
  2. 在工具的execute函数中,self.data.get_context_strings() 获取了当前正在编辑的物理资源的路径,然后通过unreal.load_asset加载了物理资源。

这里的self.data.get_context_strings()的返回值是一个字符串,我们使用eval将其转换成一个字符串数组。这个字符串数组和%asset_paths的内容是等价的。

WARNING
这个函数返回的是字符串数组序列化后的格式,这样设计是为了考虑到后续可能还会有其他类型的数据需要一并传递给 Python。这样的设计可以更加灵活地处理不同类型的数据。

class PhysicsTools(metaclass=Singleton):
    def __init__(self, jsonPath: str):
        self.jsonPath = jsonPath
        self.data = unreal.PythonBPLib.get_chameleon_data(self.jsonPath)
        self._physics_asset = None
        self.update_physics_asset()

    def update_physics_asset(self):
        context_strs = self.data.get_context_strings()
        if context_strs:
            context = eval(self.data.get_context_strings())
            if len(context ) == 1:
                asset = unreal.load_asset(context[0])
                self.physics_asset = asset
                print(f"self.physics_asset: {self.physics_asset}")
            else:
                unreal.log_warning("Physics context len != 1")
        else:
            assert False, "Can't get context"

实例中修改物理资源中Bodies的API在PythonPhysicsAssetLib

ControlRig编辑器

同理,OnControlRigEditorMenu字段可以为ControlRig编辑器添加菜单项。下面是一个简单的例子:

Menus created by TAPython in Unreal Engine Control Rig Editor

MenuConfig.json

    "OnControlRigEditorMenu": {
        "name": "Python Menu On Control Rig Editor",
        "items":
        [
            {
                "name": "TA Python ControlRig Example",
                "items": [
                    {
                        "name": "Print ControlRig",
                        "command": "print(%asset_paths)"
                    },
                    {
                        "name": "Rigging Tools",
                        "command": "unreal.ChameleonData.launch_chameleon_tool('../Python/ControlRiggingTools/control_rigging_tools.json'); chameleon_control_rig.set_control_rig(%asset_paths)",
                        "icon": {
                            "style": "ChameleonStyle",
                            "name": "Resources.Chameleon_32x_png"
                        }
                    }
                ]
            }
        ]
    },

和材质编辑器中的例子一样,%asset_paths是这样的变量占位符,是实际编辑的ControlRig资源的路径数组。

Rigging Tools菜单项中,我们不仅可以通过类似Physics Tool中的例子,通过self.data.get_context_strings()获取当前正在编辑的ControlRig资源的路径,而且也可以利用%asset_paths这个变量占位符,在command中直接将当前正在编辑的ControlRig资源的路径传递给chameleon_control_rig这个Python工具的实例。两者做法不同,但效果是一样的。

小结

  • 通过在MenuConfig.ini中配置OnMaterialEditorMenu, OnPhysicsAssetEditorMenu, OnControlRigEditorMenu,我们可以为材质编辑器,物理资源编辑器,ControlRig编辑器添加菜单项
  • %asset_paths这个变量占位符,可以让我们在菜单项的command中,获取实际正在编辑的资源路径
  • Chameleon工具中的 self.data.get_context_strings(),可以获取序列化之后的,正在编辑的资源路径数组的字符串
  • PythonPhysicsAssetLibPythonMaterialLib 中有针对物理资源和材质的API

参考

Manipulate Material Expression Nodes of Material with Python in Unreal Engine