You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

请求开发BVH文件帧数据转换至指定结构文本文件的工具

解决方案:BVH帧数据到目标文本文件的转换工具

我针对你的需求写了一个完整的Python工具,能自动解析BVH文件的层级和帧数据,按照规则转换平移、旋转值,并生成符合要求的目标文本文件。下面是详细的实现逻辑和代码:

1. 核心流程拆解

整个工具会完成三个关键步骤:

  • 解析BVH文件,提取关节层级每个关节的CHANNELS顺序帧间隔(Frame Time)所有帧的原始数值
  • 按照规则转换平移值(保留14位小数)和旋转值(使用你提供的转换逻辑)
  • 按照目标文件的结构格式,将转换后的数据写入新文件

2. 关键函数实现

旋转值转换函数

先把你提供的旋转转换逻辑封装成可复用的函数,方便批量处理帧数据:

def convert_rotation(bvh_rotation: float) -> int:
    if bvh_rotation == 0:
        return 16384
    elif bvh_rotation > 0:
        rotated = bvh_rotation * 45.50972222222222
        result = 16383.5 + rotated
    else:
        abs_rot = abs(bvh_rotation)
        rotated = abs_rot * 45.50972222222222
        result = 16383.5 - rotated
    return round(result)

BVH文件解析函数

这个函数负责读取BVH文件,提取我们需要的所有关键信息:

def parse_bvh(bvh_path: str) -> tuple:
    joints = []  # 存储每个关节的名称和对应的CHANNELS类型
    frames_data = []
    frame_time = 0.0
    in_motion = False

    with open(bvh_path, 'r') as f:
        lines = [line.strip() for line in f if line.strip()]
        i = 0
        while i < len(lines):
            line = lines[i]
            # 解析层级结构
            if line.startswith('ROOT') or line.startswith('JOINT'):
                joint_name = line.split()[1]
                i += 1
                # 找到CHANNELS行
                while not lines[i].startswith('CHANNELS'):
                    i += 1
                channels_part = lines[i].split()
                channel_count = int(channels_part[1])
                channel_types = channels_part[2:2+channel_count]
                joints.append({
                    'name': joint_name,
                    'channels': channel_types
                })
            # 解析MOTION部分
            elif line == 'MOTION':
                in_motion = True
                i += 1
                # 读取总帧数(这里暂时用不到,但可以保留)
                while not lines[i].startswith('Frame Time'):
                    i += 1
                frame_time = float(lines[i].split()[2])
                i += 1
                # 读取所有帧数据
                while i < len(lines):
                    frame_values = list(map(float, lines[i].split()))
                    frames_data.append(frame_values)
                    i += 1
            else:
                i += 1
    return joints, frame_time, frames_data

3. 生成目标文件

最后一步是把解析和转换后的数据,按照目标文件的格式写入:

def generate_target_file(joints: list, frame_time: float, frames_data: list, target_path: str):
    with open(target_path, 'w') as f:
        # 写入头部信息
        f.write('BCK Header\n')
        f.write(f'Loop Flags: 1\n')
        f.write(f'Angle Multiplier: 1.0\n')
        animation_length = len(frames_data) * frame_time
        f.write(f'Animation Length: {animation_length:.14f}\n\n')

        # 遍历每个关节生成配置
        value_idx = 0  # 跟踪当前帧数据的位置
        for joint in joints:
            f.write(f'Joint: {joint["name"]}\n')
            # Scale 默认设为[1,1,1],如果有需求可以修改
            f.write('Scale:\n')
            f.write('X: [0, 1.00000000000000, 0]\n')
            f.write('Y: [0, 1.00000000000000, 0]\n')
            f.write('Z: [0, 1.00000000000000, 0]\n')

            # 处理Rotation和Translation
            rot_x_values = []
            rot_y_values = []
            rot_z_values = []
            trans_x_values = []
            trans_y_values = []
            trans_z_values = []

            # 遍历所有帧,提取当前关节对应的通道值
            for frame_idx, frame in enumerate(frames_data):
                for channel in joint['channels']:
                    val = frame[value_idx]
                    if channel == 'Xposition':
                        formatted_val = f'{val:.14f}'
                        trans_x_values.append(f'[{frame_idx}, {formatted_val}, 0]')
                    elif channel == 'Yposition':
                        formatted_val = f'{val:.14f}'
                        trans_y_values.append(f'[{frame_idx}, {formatted_val}, 0]')
                    elif channel == 'Zposition':
                        formatted_val = f'{val:.14f}'
                        trans_z_values.append(f'[{frame_idx}, {formatted_val}, 0]')
                    elif channel == 'Xrotation':
                        converted_rot = convert_rotation(val)
                        rot_x_values.append(f'[{frame_idx}, {converted_rot}, 0]')
                    elif channel == 'Yrotation':
                        converted_rot = convert_rotation(val)
                        rot_y_values.append(f'[{frame_idx}, {converted_rot}, 0]')
                    elif channel == 'Zrotation':
                        converted_rot = convert_rotation(val)
                        rot_z_values.append(f'[{frame_idx}, {converted_rot}, 0]')
                    value_idx += 1

            # 写入Rotation
            f.write('Rotation:\n')
            f.write(f'X: {", ".join(rot_x_values)}\n')
            f.write(f'Y: {", ".join(rot_y_values)}\n')
            f.write(f'Z: {", ".join(rot_z_values)}\n')

            # 写入Translation
            f.write('Translation:\n')
            f.write(f'X: {", ".join(trans_x_values)}\n')
            f.write(f'Y: {", ".join(trans_y_values)}\n')
            f.write(f'Z: {", ".join(trans_z_values)}\n\n')

4. 主函数调用

把上面的函数整合起来,添加调用入口:

if __name__ == '__main__':
    # 修改为你的BVH文件路径和目标文件路径
    INPUT_BVH = 'input.bvh'
    OUTPUT_TARGET = 'output.bck'

    # 解析BVH文件
    joints, frame_time, frames_data = parse_bvh(INPUT_BVH)
    # 生成目标文件
    generate_target_file(joints, frame_time, frames_data, OUTPUT_TARGET)
    print(f'转换完成!目标文件已保存到 {OUTPUT_TARGET}')

使用说明

  1. 把你的BVH文件放在脚本同一目录下,或者修改INPUT_BVH的路径
  2. 修改OUTPUT_TARGET为你想要的目标文件名
  3. 运行脚本,即可得到符合要求的目标文本文件

注意事项

  • 如果BVH文件的层级结构有特殊嵌套(比如END SITE),可以根据需要在parse_bvh函数里添加处理逻辑
  • Scale字段如果需要自定义,可以修改generate_target_file里的Scale部分代码
  • 所有平移值都会自动格式化为14位小数,旋转值严格按照你提供的逻辑转换

内容的提问来源于stack exchange,提问作者Lou

火山引擎 最新活动