PyYAML和不寻常的标签

ren*_*box 11 python yaml pyyaml

我正在开发一个使用Unity3D游戏引擎的项目.对于某些管道要求,最好能够使用Python从外部工具更新某些文件.Unity的meta和anim文件都在YAML中,所以我认为使用PyYAML这将是足够的前进.

问题是Unity的格式使用自定义属性,我不知道如何使用它们,因为所有示例都显示了Python和Ruby使用的更常见的标记.

以下是文件的顶行:

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
  m_ObjectHideFlags: 0
  m_PrefabParentObject: {fileID: 0}
  ...
Run Code Online (Sandbox Code Playgroud)

当我尝试读取文件时,我收到此错误:

could not determine a constructor for the tag 'tag:unity3d.com,2011:74'
Run Code Online (Sandbox Code Playgroud)

现在,在查看了所有其他问题之后,这个标签方案似乎与那些问题和答案不相似.例如,此文件使用"!u!" 我无法弄清楚它的意义或类似的行为(我的野心未经猜测的猜测它看起来像一个别名或命名空间).

我可以做一个黑客方式并剥离标签,但这不是尝试这样做的理想方式.我正在寻找有关正确处理标签的解决方案的帮助,并允许我以保留正确格式的方式解析和编码数据.

谢谢,-R

小智 20

我也有这个问题,互联网不是很有帮助.在对这个问题进行了3天的抨击之后,我能够解决它...或者至少得到一个有效的解决方案.如果有人想添加更多信息,请执行.但这就是我得到的.

1)关于Unity的YAML文件格式的文档(它们称之为"文本场景文件",因为它包含人类可读的文本) - http://docs.unity3d.com/Manual/TextualSceneFormat.html

它符合YAML 1.1格式.因此,您应该能够使用PyYAML或任何其他Python YAML库来加载YAML对象.

好,太棒了.但它不起作用.每个YAML库都存在此文件的问题.

2)文件未正确形成.事实证明,Unity文件有一些语法问题,使YAML解析器出错.特别:

2a)在顶部,它使用%TAG指令为字符串"unity3d.com,2011"创建别名.看起来像:

%TAG !u! tag:unity3d.com,2011:
Run Code Online (Sandbox Code Playgroud)

这意味着你可以在任何地方看到"!u!",将其替换为"tag:unity3d.com,2011".

2b)然后继续使用"!u!" 在每个对象流之前的所有地方.但问题是 - 要符合YAML 1.1 - 它实际上应该为每个流声明一个标记别名(任何时候新对象以"---"开头).在顶部声明一次并且永远不会再次对第一个流有效,并且下一个流对"!u!"一无所知,所以它出错了.

此外,这个标签是无用的.它基本上将"tag:unity3d.com,2011"附加到流中的每个条目.我们不关心.我们已经知道它是一个Unity YAML文件.为什么混乱数据?

3)对象类型由Unity的类ID给出.以下是有关该文档的文档:http: //docs.unity3d.com/Manual/ClassIDReference.html

基本上,每个流被定义为一个新的对象类...对应于该链接中的ID.所以"GameObject"是"1"等等.这条线看起来像这样:

--- !u!1 &100000
Run Code Online (Sandbox Code Playgroud)

所以"---"定义了一个新流."!你!" 是"tag:unity3d.com,2011"的别名,"&100000"是此对象的文件ID(在此文件中,如果有人引用此对象,则使用此ID ....记住YAML是一个节点 - 基于表示,以便ID用于表示节点连接).

下一行是YAML对象的根,它恰好是Unity类的名称...示例"GameObject".事实证明,我们实际上并不需要从Class ID转换为Human Readable节点类型.它就在那里.如果您需要使用它,只需占用根节点即可.如果你需要为Unity构建一个YAML对象,只需根据该文档链接保留一个字典,将"GameObject"翻译为"1"等.

另一个问题是,大多数YAML解析器(PyYAML是我测试的解析器)仅支持3种类型的YAML对象:

  1. 纯量
  2. 序列
  3. 制图

您可以定义/扩展自定义节点.但这相当于编写自己的YAML解析器,因为您必须明确定义每个YAML构造函数的创建方式和输出.为什么我会使用像PyYAML这样的库,然后继续编写我自己的解析器来读取这些自定义节点?使用库的重点是利用以前的工作并从第一天开始就获得所有功能.我花了两天的时间尝试为每个类ID创建一个新的构造函数.它从来没有奏效,我进入了试图正确构建构造函数的杂草.

好消息/解决方案:

事实证明,到目前为止我遇到的所有Unity节点都是YAML中的基本"映射"节点.因此,您可以丢弃自定义节点映射,只需让PyYAML自动检测节点类型.从那里,一切都很棒!

在PyYAML中,您可以传递文件对象或字符串.因此,我的解决方案是编写一个简单的5行预解析器来去除混淆PyYAML(Unity错误地编写的位)的位,并将这个新字符串提供给PyYAML.

1)完全删除第2行,或者忽略它:

%TAG !u! tag:unity3d.com,2011:
Run Code Online (Sandbox Code Playgroud)

我们不在乎.我们知道它是一个统一文件.标签对我们没有任何作用.

2)对于每个流声明,删除标记别名("!u!")并删除类ID.保留fileID.让PyYAML自动检测节点作为Mapping节点.

--- !u!1 &100000
Run Code Online (Sandbox Code Playgroud)

成为...

--- &100000
Run Code Online (Sandbox Code Playgroud)

3)其余的,按原样输出.

预解析器的代码如下所示:

def removeUnityTagAlias(filepath):
    """
    Name:               removeUnityTagAlias()

    Description:        Loads a file object from a Unity textual scene file, which is in a pseudo YAML style, and strips the
                        parts that are not YAML 1.1 compliant. Then returns a string as a stream, which can be passed to PyYAML.
                        Essentially removes the "!u!" tag directive, class type and the "&" file ID directive. PyYAML seems to handle
                        rest just fine after that.

    Returns:                String (YAML stream as string)  


    """
    result = str()
    sourceFile = open(filepath, 'r')

    for lineNumber,line in enumerate( sourceFile.readlines() ): 
        if line.startswith('--- !u!'):          
            result += '--- ' + line.split(' ')[2] + '\n'   # remove the tag, but keep file ID
        else:
            # Just copy the contents...
            result += line

    sourceFile.close()  

    return result
Run Code Online (Sandbox Code Playgroud)

要从Unity文本场景文件创建PyYAML对象,请在文件上调用预解析器函数:

import yaml

# This fixes Unity's YAML %TAG alias issue.
fileToLoad = '/Users/vlad.dumitrascu/<SOME_PROJECT>/Client/Assets/Gear/MeleeWeapons/SomeAsset_test.prefab'

UnityStreamNoTags = removeUnityTagAlias(fileToLoad)

ListOfNodes = list()

for data in yaml.load_all(UnityStreamNoTags):
    ListOfNodes.append( data )

# Example, print each object's name and type
for node in ListOfNodes:
    if 'm_Name' in node[ node.keys()[0] ]:
        print( 'Name: ' + node[ node.keys()[0] ]['m_Name']  + ' NodeType: ' + node.keys()[0] )
    else:
        print( 'Name: ' + 'No Name Attribute'  + ' NodeType: ' + node.keys()[0] )
Run Code Online (Sandbox Code Playgroud)

希望有所帮助!

-Vlad

PS.要回答下一个问题,使其可用:

您还需要遍历整个项目目录并解析"GUID"的所有".meta"文件,这是Unity的文件间引用.因此,当您在Unity YAML文件中看到类似于以下内容的引用时:

m_Materials:
  - {fileID: 2100000, guid: 4b191c3a6f88640689fc5ea3ec5bf3a3, type: 2}
Run Code Online (Sandbox Code Playgroud)

该文件在其他地方.你可以重新抨击那个,找出任何依赖.

我刚刚翻阅游戏项目并保存了一个GUID字典:Filepath Key:我可以匹配的值对.