Переведи код на PyOpenGL с сохранением функционала программы.
import os
import base64
from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor

# Инициализация Panda3D
base = ShowBase()

def load_fbx_model(main_model_path, animation_files):
    """
    Загружает основную модель и её анимации.
    :param main_model_path: Путь к основной модели.
    :param animation_files: Список путей к файлам анимаций.
    :return: Actor объект с анимациями или None при ошибке.
    """
    try:
        print(f"Загрузка модели как Actor: {main_model_path}...")

        # Создаем словарь для анимаций
        animations = {}
        for anim_file in animation_files:
            anim_name = os.path.splitext(os.path.basename(anim_file))[0]
            print(f"Загрузка анимации: {anim_name} из {anim_file}...")
            animations[anim_name] = anim_file

        # Загружаем модель вместе с анимациями
        actor = Actor(main_model_path, animations)
        return actor
    except Exception as e:
        print(f"Ошибка при загрузке модели и анимаций: {e}")
        return None

def extract_model_data(model_node):
    """
    Извлекает данные из модели: вершины, нормали, UV, материалы, пути к текстурам.
    :param model_node: NodePath или Actor объекта модели.
    :return: Словарь с данными модели.
    """
    data = {
        'vertices': [],
        'normals': [],
        'uvs': [],
        'textures': {},
        'animations': {},
        'materials': []
    }

    # Проверяем, является ли модель Actor'ом
    is_actor = isinstance(model_node, Actor)

    if not is_actor:
        print("Извлечение геометрических данных...")

        # Поиск всех GeomNode (геометрических узлов)
        geom_nodes = model_node.find_all_matches('**/+GeomNode')
        if geom_nodes.get_num_paths() == 0:
            print("Внимание: Геометрические данные не найдены!")
        else:
            for geom_node in geom_nodes:
                geom = geom_node.node().get_geom(0)
                vdata = geom.get_vertex_data()

                # Извлечение вершин
                vertex_reader = GeomVertexReader(vdata, 'vertex')
                if not vertex_reader.has_column():
                    print("Внимание: Вершины не найдены!")
                else:
                    while not vertex_reader.is_at_end():
                        vertex = vertex_reader.getData3f()
                        data['vertices'].append(vertex)

                # Извлечение нормалей
                normal_reader = GeomVertexReader(vdata, 'normal')
                if not normal_reader.has_column():
                    print("Внимание: Нормали не найдены!")
                else:
                    while not normal_reader.is_at_end():
                        normal = normal_reader.getData3f()
                        data['normals'].append(normal)

                # Извлечение UV
                uv_reader = GeomVertexReader(vdata, 'texcoord')
                if not uv_reader.has_column():
                    print("Внимание: UV-координаты не найдены!")
                else:
                    while not uv_reader.is_at_end():
                        uv = uv_reader.getData2f()
                        data['uvs'].append(uv)

    # Обработка Actor'ов
    if is_actor:
        print("Извлечение геометрических данных из Actor...")

        # Получаем основной узел геометрии из Actor
        geom_node = model_node.find('**/+GeomNode')
        if not geom_node.is_empty():
            geom = geom_node.node().get_geom(0)
            vdata = geom.get_vertex_data()

            # Извлечение вершин
            vertex_reader = GeomVertexReader(vdata, 'vertex')
            if vertex_reader.has_column():
                while not vertex_reader.is_at_end():
                    vertex = vertex_reader.getData3f()
                    data['vertices'].append(vertex)

            # Извлечение нормалей
            normal_reader = GeomVertexReader(vdata, 'normal')
            if normal_reader.has_column():
                while not normal_reader.is_at_end():
                    normal = normal_reader.getData3f()
                    data['normals'].append(normal)

            # Извлечение UV
            uv_reader = GeomVertexReader(vdata, 'texcoord')
            if uv_reader.has_column():
                while not uv_reader.is_at_end():
                    uv = uv_reader.getData2f()
                    data['uvs'].append(uv)

    # Извлечение текстур
    textures = model_node.find_all_textures()
    if textures:
        print("Извлечение текстур...")
    else:
        print("Внимание: Текстуры не найдены!")
    for texture in textures:
        texture_name = texture.get_name()
        texture_path = texture.get_filename().get_fullpath()
        print(f"Текстура: {texture_name}, Путь: {texture_path}")

        with open(texture_path, "rb") as image_file:
            encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
            data['textures'][texture_name] = {
                'data': encoded_string,
                'format': os.path.splitext(texture_path)[1].lower()
            }

    # Извлечение материалов
    materials = model_node.find_all_materials()
    if materials:
        print("Извлечение материалов...")
    else:
        print("Внимание: Материалы не найдены!")
    for material in materials:
        data['materials'].append({
            'name': material.get_name(),
            'ambient': tuple(material.get_ambient()),
            'diffuse': tuple(material.get_diffuse()),
            'specular': tuple(material.get_specular()),
            'shininess': material.get_shininess()
        })

    # Извлечение анимаций (только для Actor'ов)
    if is_actor:
        print("Извлечение анимаций...")
        anims = model_node.getAnimNames()
        if not anims:
            print("Внимание: Анимации не найдены!")
        else:
            bones = model_node.getJoints()
            if not bones:
                print("Внимание: Скелет модели не найден!")
            else:
                print(f"Найдено {len(bones)} костей в скелете основной модели.")

            for anim_name in anims:
                print(f"Обработка анимации: {anim_name}")
                anim_control = model_node.getAnimControl(anim_name)
                if anim_control is None:
                    print(f"Внимание: AnimControl для анимации {anim_name} не найден!")
                    continue

                frame_rate = anim_control.get_frame_rate()
                num_frames = anim_control.get_num_frames()
                print(f"Анимация {anim_name}: частота кадров = {frame_rate}, количество кадров = {num_frames}")

                # Логирование количества костей в анимации
                anim_bones = model_node.getJoints()
                if not anim_bones:
                    print(f"Внимание: Кости для анимации {anim_name} не найдены!")
                else:
                    print(f"Найдено {len(anim_bones)} костей в анимации {anim_name}.")
                    if len(anim_bones) != len(bones):
                        print(f"Внимание: Количество костей в анимации {anim_name} ({len(anim_bones)}) "
                              f"не совпадает с количеством костей в основной модели ({len(bones)}).")

                data['animations'][anim_name] = {
                    'frame_rate': frame_rate,
                    'num_frames': num_frames,
                    'keyframes': []
                }

                # Извлечение ключевых кадров
                for frame in range(num_frames):
                    print(f"Обработка ключевого кадра {frame + 1}/{num_frames} для анимации {anim_name}")
                    anim_control.pose(frame)
                    pose_data = {}

                    # Извлечение данных о положении костей
                    bones = model_node.getJoints()
                    if not bones:
                        print(f"Внимание: Кости для анимации {anim_name} не найдены!")
                    else:
                        for bone in bones:
                            pose_data[bone.getName()] = {
                                'position': tuple(bone.getTransform().getPos()),
                                'rotation': tuple(bone.getTransform().getHpr())
                            }
                            print(f"Кость {bone.getName()}: position={pose_data[bone.getName()]['position']}, rotation={pose_data[bone.getName()]['rotation']}")

                    data['animations'][anim_name]['keyframes'].append(pose_data)

    return data

def generate_python_code(data, output_file):
    """
    Генерирует Python-код на основе извлеченных данных модели.
    :param data: Словарь с данными модели.
    :param output_file: Путь к выходному файлу.
    """
    with open(output_file, 'w') as f:
        f.write("from panda3d.core import *\n")
        f.write("from direct.showbase.ShowBase import ShowBase\n")
        f.write("import base64\n\n")

        # Генерация вершин
        f.write("vertices = [\n")
        for vertex in data['vertices']:
            f.write(f"    {tuple(vertex)},\n")
        f.write("]\n\n")

        # Генерация нормалей
        f.write("normals = [\n")
        for normal in data['normals']:
            f.write(f"    {tuple(normal)},\n")
        f.write("]\n\n")

        # Генерация UV
        f.write("uvs = [\n")
        for uv in data['uvs']:
            f.write(f"    {tuple(uv)},\n")
        f.write("]\n\n")

        # Генерация текстур
        f.write("textures = {\n")
        for texture_name, texture_info in data['textures'].items():
            f.write(f'    "{texture_name}": {{\n')
            f.write(f'        "data": """{texture_info["data"]}""",\n')
            f.write(f'        "format": "{texture_info["format"]}"\n')
            f.write(f'    }},\n')
        f.write("}\n\n")

        # Функция для декодирования текстур
        f.write("""
def decode_texture(texture_name, texture_data, texture_format):
    tex = Texture(texture_name)
    decoded_data = base64.b64decode(texture_data)
    temp_file = tempfile.NamedTemporaryFile(suffix=texture_format, delete=False)
    temp_file.write(decoded_data)
    temp_file.close()
    tex.read(Filename.from_os_specific(temp_file.name))
    return tex
""")

        # Генерация материалов
        f.write("materials = [\n")
        for material in data['materials']:
            f.write("    Material(\n")
            f.write(f'        name="{material["name"]}",\n')
            f.write(f'        ambient={material["ambient"]},\n')
            f.write(f'        diffuse={material["diffuse"]},\n')
            f.write(f'        specular={material["specular"]},\n')
            f.write(f'        shininess={material["shininess"]}\n')
            f.write("    ),\n")
        f.write("]\n\n")

        # Генерация анимаций
        f.write("animations = {\n")
        for anim_name, anim_data in data['animations'].items():
            f.write(f'    "{anim_name}": {{\n')
            f.write(f'        "frame_rate": {anim_data["frame_rate"]},\n')
            f.write(f'        "num_frames": {anim_data["num_frames"]},\n')
            f.write(f'        "keyframes": [\n')
            for keyframe in anim_data['keyframes']:
                f.write("            {\n")
                for bone_name, bone_data in keyframe.items():
                    f.write(f'                "{bone_name}": {{\n')
                    f.write(f'                    "position": {bone_data["position"]},\n')
                    f.write(f'                    "rotation": {bone_data["rotation"]}\n')
                    f.write(f'                }},\n')
                f.write("            },\n")
            f.write("        ]\n")
            f.write("    },\n")
        f.write("}\n\n")

if __name__ == "__main__":
    # Путь к основной модели и анимациям
    main_model_path = "characterMedium.fbx"
    animation_files = ["idle.fbx", "run.fbx", "jump.fbx"]
    texture_files = ["survivorMaleB.png"]

    # Загрузка основной модели и её анимаций
    main_model = load_fbx_model(main_model_path, animation_files)

    if main_model:
        # Извлечение данных из основной модели
        model_data = extract_model_data(main_model)

        # Добавление внешних текстур (если они не встроены в модель)
        for texture_file in texture_files:
            if os.path.exists(texture_file):
                texture_name = os.path.splitext(os.path.basename(texture_file))[0]
                with open(texture_file, "rb") as image_file:
                    encoded_string = base64.b64encode(image_file.read()).decode("utf-8")
                model_data['textures'][texture_name] = {
                    'data': encoded_string,
                    'format': os.path.splitext(texture_file)[1].lower()
                }
                print(f"Добавлена текстура: {texture_name}")
            else:
                print(f"Ошибка: Текстура {texture_file} не найдена!")

        # Генерация Python-кода
        output_script = "generated_model.py"
        generate_python_code(model_data, output_script)

        print(f"Python-скрипт успешно сгенерирован: {output_script}")
    else:
        print("Ошибка: Основная модель не была загружена.")