PyYAML и необычные теги

Я работаю над проектом, который использует движок 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!" что я не смог понять, что это значит или как что-то подобное будет вести (моя дикая необразованная догадка говорит, что это похоже на псевдоним или пространство имен).

Я могу взломать и вырезать теги, но это не идеальный способ сделать это. Я ищу помощь по решению, которое будет правильно обрабатывать теги и позволит мне анализировать и кодировать данные таким образом, чтобы сохранить правильный формат.

Спасибо, -R

Ответ 1

У меня также была эта проблема, и интернет был не очень полезен. После того, как я наткнулся на эту проблему в течение 3 дней, я смог разобраться с ней или хотя бы получить рабочее решение. Если кто-то хочет добавить дополнительную информацию, пожалуйста. Но вот что я получил.

1) Документация по файлу Unity YAML (они называют это "текстовым файлом сцены", потому что он содержит текст, который читается человеком) - http://docs.unity3d.com/Manual/TextualSceneFormat.html

Это формат, совместимый с YAML 1.1. Поэтому вы можете использовать PyYAML или любую другую библиотеку YAML Python для загрузки объекта 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. Вот документация по этому поводу: http://docs.unity3d.com/Manual/ClassIDReference.html

В принципе, каждый поток определяется как новый класс объекта... соответствующий идентификаторам в этой ссылке. Итак, "GameObject" - "1" и т.д. Строка выглядит так:

--- !u!1 &100000

Итак, "---" определяет новый поток. "! U!" является псевдонимом для "tag: unity3d.com, 2011" , а "& 100000" - это идентификатор файла для этого объекта (внутри этого файла, если что-то ссылается на этот объект, он использует этот идентификатор.... помните, что YAML является node, поэтому идентификатор используется для обозначения соединения node).

Следующая строка - это корень объекта YAML, который является именем класса Unity... example "GameObject" . Поэтому оказывается, что нам действительно не нужно переводить с класса ID на тип Human readable node. Это прямо здесь. Если вам когда-нибудь понадобится его использовать, просто возьмите корень node. И если вам нужно построить объект YAML для Unity, просто держите словарь на основе этой ссылки документации, чтобы перевести "GameObject" на "1" и т.д.

Другая проблема заключается в том, что большинство парней YAML (PyYAML - это тот, который я тестировал) поддерживают только 3 типа объектов YAML:

  • Скалярное
  • Последовательность
  • Отображение

Вы можете определить/расширить пользовательские узлы. Но это сводится к написанию собственного анализатора YAML, потому что вам нужно ОПИСАТЬ, как создается каждый конструктор YAML и выводит. Почему я должен использовать библиотеку, например PyYAML, а затем написать свой собственный синтаксический анализатор для чтения этих пользовательских узлов? Весь смысл использования библиотеки - использовать предыдущую работу и получить всю эту функциональность с первого дня. Я потратил 2 дня, пытаясь создать новый конструктор для каждого идентификатора класса в единстве. Он никогда не работал, и я попал в сорняки, которые пытались правильно построить конструкторы.

ХОРОШИЕ НОВОСТИ/РЕШЕНИЕ:

Оказывается, все узлы Unity, с которыми я когда-либо сталкивался, являются базовыми узлами "Mapping" в YAML. Таким образом, вы можете выбросить пользовательское сопоставление node и просто позволить PyYAML автоматически определять тип node. Оттуда все отлично работает!

В PyYAML вы можете передать файл-объект или строку. Итак, мое решение состояло в том, чтобы написать простой 5-строчный предварительный парсер, чтобы вырезать биты, которые путают PyYAML (биты, которые Unity неправильно синтаксически) и подают эту новую строку в PyYAML.

1) полностью удалите строку 2 или просто проигнорируйте ее:

%TAG !u! tag:unity3d.com,2011:

Нам все равно. Мы знаем, что это файл единства. И тег ничего не делает для нас.

2) Для каждого объявления потока удалите псевдоним тега ( "! u!" ) и удалите идентификатор класса. Оставьте файлID. Пусть PyYAML автоматически определит node как отображение node.

--- !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

Чтобы создать объект PyYAML из файла текстовой сцены Unity, вызовите функцию pre-parser в файле:

import yaml

# This fixes Unity 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 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] )

Надеюсь, что это поможет!

-Vlad

PS. Чтобы ответить на следующую проблему при ее использовании:

Вам также нужно пройти весь каталог проекта и проанализировать все файлы ".meta" для "GUID", который является ссылкой между файлами Unity. Итак, когда вы видите ссылку в файле Unity YAML для чего-то вроде:

m_Materials:
  - {fileID: 2100000, guid: 4b191c3a6f88640689fc5ea3ec5bf3a3, type: 2}

Этот файл находится где-то в другом месте. И вы можете повторно открывать это, чтобы узнать какие-либо зависимости.

Я просто разорвал игровой проект и сохранил словарь GUID: Filepath Key: пары значений, с которыми я могу сопоставлять.