请求开发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}')
使用说明
- 把你的BVH文件放在脚本同一目录下,或者修改
INPUT_BVH的路径 - 修改
OUTPUT_TARGET为你想要的目标文件名 - 运行脚本,即可得到符合要求的目标文本文件
注意事项
- 如果BVH文件的层级结构有特殊嵌套(比如END SITE),可以根据需要在
parse_bvh函数里添加处理逻辑 - Scale字段如果需要自定义,可以修改
generate_target_file里的Scale部分代码 - 所有平移值都会自动格式化为14位小数,旋转值严格按照你提供的逻辑转换
内容的提问来源于stack exchange,提问作者Lou




