PyYAML 和不寻常的标签
Posted
技术标签:
【中文标题】PyYAML 和不寻常的标签【英文标题】:PyYAML and unusual tags 【发布时间】:2014-02-23 17:38:15 【问题描述】:我正在开发一个使用 Unity3D 游戏引擎的项目。对于某些管道要求,最好能够使用 Python 从外部工具更新一些文件。 Unity 的元和动画文件在 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
...
当我尝试读取文件时出现此错误:
could not determine a constructor for the tag 'tag:unity3d.com,2011:74'
现在在查看了所有其他问题之后,这个标签方案似乎与那些问题和答案并不相似。例如,此文件使用“!u!”我无法弄清楚它的含义或类似的行为会如何(我未经教育的狂野猜测说它看起来像别名或命名空间)。
我可以做一个 hack 方法并去掉标签,但这不是尝试这样做的理想方法。我正在寻求一种解决方案的帮助,该解决方案将正确处理标签并允许我以保留正确格式的方式解析和编码数据。
谢谢, -R
【问题讨论】:
【参考方案1】:我也有这个问题,而且互联网不是很有帮助。在对这个问题进行了 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:
这意味着在您看到“!u!”的任何地方,将其替换为“tag:unity3d.com,2011”。
2b) 然后它继续使用“!u!”在每个对象流之前的所有地方。但问题是——为了符合 YAML 1.1——它实际上应该为每个流声明一个标签别名(任何时候一个新对象以“---”开头)。在顶部声明一次并且不再声明仅对第一个流有效,而下一个流对“!u!”一无所知,因此会出错。
另外,这个标签是没用的。它基本上将“tag:unity3d.com,2011”附加到流中的每个条目。我们不在乎。我们已经知道它是一个 Unity YAML 文件。为什么要弄乱数据?
3) 对象类型由 Unity 的 Class ID 给出。以下是相关文档: http://docs.unity3d.com/Manual/ClassIDReference.html
基本上,每个流都被定义为一个新的对象类...对应于该链接中的 ID。所以“GameObject”是“1”,等等。这条线看起来像这样:
--- !u!1 &100000
所以“---”定义了一个新的流。 “!你!”是“tag:unity3d.com,2011”的别名,“&100000”是这个对象的文件 ID(在这个文件中,如果有东西引用这个对象,它会使用这个 ID....记住 YAML 是一个节点-基于表示,因此 ID 用于表示节点连接)。
下一行是 YAML 对象的根,恰好是 Unity 类的名称...例如“GameObject”。所以事实证明,我们实际上不需要从 Class ID 转换为 Human Readable 节点类型。它就在那里。如果您需要使用它,只需获取根节点即可。如果您需要为 Unity 构建 YAML 对象,只需根据该文档链接保留一本字典,以将“GameObject”翻译为“1”等。
另一个问题是大多数 YAML 解析器(PyYAML 是我测试过的)只支持 3 种类型的 YAML 对象:
-
标量
序列
映射
您可以定义/扩展自定义节点。但这相当于手动编写您自己的 YAML 解析器,因为您必须明确定义每个 YAML 构造函数的创建方式和输出方式。为什么我要使用像 PyYAML 这样的库,然后继续编写自己的解析器来读取这些自定义节点?使用库的全部意义在于利用以前的工作并从一开始就获得所有这些功能。我花了 2 天时间尝试为每个类 ID 统一创建一个新的构造函数。它从来没有奏效,我陷入了试图正确构建构造函数的杂草中。
好消息/解决方案:
事实证明,到目前为止我遇到的所有 Unity 节点都是 YAML 中的基本“映射”节点。所以你可以扔掉自定义节点映射,让 PyYAML 自动检测节点类型。从那里开始,一切都很好!
在 PyYAML 中,您可以传递文件对象或字符串。因此,我的解决方案是编写一个简单的 5 行预解析器来去除混淆 PyYAML 的位(Unity 语法错误的位)并将这个新字符串提供给 PyYAML。
1) 完全删除第 2 行,或者直接忽略它:
%TAG !u! tag:unity3d.com,2011:
我们不在乎。我们知道这是一个统一文件。而标签对我们没有任何作用。
2) 对于每个流声明,删除标签别名(“!u!”)并删除类 ID。留下文件ID。让 PyYAML 自动将该节点检测为 Mapping 节点。
--- !u!1 &100000
变成……
--- &100000
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
要从 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] )
希望有帮助!
-弗拉德
PS。回答下一个问题以使其可用:
您还需要遍历整个项目目录并解析所有“.meta”文件以获取“GUID”,这是 Unity 的文件间引用。因此,当您在 Unity YAML 文件中看到类似以下内容的引用时:
m_Materials:
- fileID: 2100000, guid: 4b191c3a6f88640689fc5ea3ec5bf3a3, type: 2
那个文件在别的地方。你可以递归地打开那个来找出任何依赖关系。
我刚刚浏览了游戏项目并保存了一个 GUID:Filepath Key:Value 对的字典,我可以匹配它。
【讨论】:
以上是关于PyYAML 和不寻常的标签的主要内容,如果未能解决你的问题,请参考以下文章