Spiiin's blog

daScript: C++ auto-bindings, assimp

Продолжение предыдущего поста с генерацией модулей daScript к С++-библиотекам с помощью автоматического генератора dasBind.
daScript: C++ auto-bindings, msgpack

На этот раз чуть более сложный случай с библиотекой assimp, которая позволяет загружать 3d-модели в различных форматах. Для начала — собираем библиотеку из исходников. Я бы рекомендовал версию 4.1.0, как стабильную, в более новых сломаны некоторые настройки постпроцессинга загруженных мешей.

Привязки assimp к другим языкам в основном репозитории малость заброшены, так что попутно починим и их.

Python

Самый простой случай — достаточно поправить синтаксические отличия между Python 2 и 3.

DotNet, open3mod

Живёт тут, необходимо:

  • Собрать библиотеку assimp в DLL, переименовать в Assimp32.dll или Assimp64.dll.
  • Собрать библиотеку AssimNet.dll с классами обёртками.
  • Собрать проект AssimpNet.Interop.Generator, который нужен, чтобы пропатчить IL-код из AssimpNet размерами типов, полученных из PDB файла. Для этой техники используется библиотека Mono.Cecil.
  • Пропатчить библиотеку (данный шаг прописан в PostBuild-степ в решении, но лучше убедиться, что он корректно отработал)
  • Собрать и запустить open3Mod - просмотрщик, в котором можно проверить работоспособность библиотеки.

daScript

Автоматический генератор привязок dasBind не работает с C++ template-кодом, но все заголовочные файлы assimp поддерживают также c-интерфейс. Однако без некоторой “доработки напильником” воспользоваться этим интерфейсом не удастся, так как, несмотря на то, что генератору привязок можно указать, чтобы он парсил заголовочные файлы как c-код, возникают ошибки двойного определения типов при использования этих же хидеров в C++ коде самого проекта, который будет использовать эти привязки. Причины такого поведения — попытка различать, какой из интерфейсов использовать с помощью макроса __cplusplus, который всегда определён в C++ коде (exrern "C" его не отключает).

Однако можно пойти на хитрость, и просто заменить во всех исходниках макрос __cplusplus на какой-нибудь кастомный, который не будет определен в коде, чтобы как dasBind, так и использующий сгенерированный C++-модуль dasAssimp, “увидели” именно сишный интерфейс к библиотеке.

#replace_cplusplus_to_custom.py
#Script for replace __cplusplus text to some other build directive (__CUSTOM_CPP_DEFINE)

import os

HEADERS_PATH = "."

for fname in os.listdir(HEADERS_PATH):
if os.path.isfile(os.path.join(HEADERS_PATH, fname)) and os.path.splitext(fname)[1] != ".py":
print("Proccessing", fname)
with open(fname, "rt") as f:
lines = f.readlines()
newlines = []
for line in lines:
newlines.append(line.replace("__cplusplus", "__CUSTOM_CPP_DEFINE"))
with open(fname, "wt") as f:
f.writelines(newlines)

Ну и дальше, аналогично инструкции в предыдущем посте:

class AssimpGen : CppGenBind
    override func_to_stdout = false
    unique_functions : table<string; bool>

    def AssimpGen
        bind_root = "{get_das_root()}/modules/dasAssimp/src"
        bind_module = "assimp"
        bind_das_module = "assimp"
        let pfn = "assimp/include_all_import.h"
        //# тут пользуемся сгенерированными сишными заголовочными файлами
        let pfp = "{get_das_root()}/modules/dasAssimp/assimp/include_c/"

        let args <- [{string
            "-xc++-header";
            "-std=c++1z";
            "-I{get_full_file_name(pfp)}";
            "-DSWIG"
        }]

и дописываем функцию преобразования dasString в std::string, чтобы генератор привязок мог понять, как с ней работать

const char* das_aiString_to_string(aiString* string) {
return string->data;
}

void Module_assimp::initMain() {
addExtern<DAS_BIND_FUN(das_aiString_to_string)>(*this, lib, "assimp_str",
SideEffects::worstDefault, "das_aiString_to_string");
}

После генерации модуля можно попробовать им воспользоваться (предварительно не забыть прилинковать к проекту lib файлы от библиотеки assimp и сделать доступным путь к собранный dll, если ассимп был собран как динамическая библиотека):

require assimp
require strings
require daslib/defer

def printNodesHierarchy(depth:int; var node:aiNode?&)
    unsafe
        print("{repeat("-",depth)}{assimp_str(addr(node.mName))}\n")
        for i in range(0, int(node.mNumChildren))
            printNodesHierarchy(depth+1, node.mChildren[i])

[export]
def main
    unsafe
        let path = "character.dae"
        var scene = aiImportFile(path, 8u)
        defer <|
            scene |> aiReleaseImport
       
        var mesh = scene.mMeshes
        print("Meshes: {int(scene.mNumMeshes)}\n")
        print("Vertices:{int(mesh[0].mNumVertices)}\n")
        print("Faces:{int(mesh[0].mNumFaces)}\n")

        var rootNode = scene.mRootNode
        print("Node hierarchy:\n")
        printNodesHierarchy(1, rootNode)

Данный код загружает модель из файла character.dae, и печатает иерархию костей скелета в этой модели:

Meshes: 1
Vertices:14487
Faces:4829
Node hierarchy:
-character.dae
--root
---M_spine_1_joint
----M_spine_2_joint
-----M_spine_3_joint
------M_spine_4_joint
------M_spine_5_joint
-------L_shoulder_joint
--------L_arm_1_joint
---------L_arm_2_joint
...
-------R_leg_4_joint
--------R_leg_5_joint
------R_leg_2_twist_1_joint
-------R_leg_2_twist_2_joint
-----R_leg_1_twist_1_joint
------R_leg_1_twist_2_joint
--body_geo

Сырой код модуля, когда-нибудь надо будет причесать