Spaces:
Running on Zero
Running on Zero
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| import re | |
| from typing import Optional | |
| import numpy as np | |
| class BvhNode: | |
| """Abstraction for bvh node.""" | |
| def __init__(self, value=[], parent=None): | |
| self.value = value | |
| self.children = [] | |
| self.parent = parent | |
| if self.parent: | |
| self.parent.add_child(self) | |
| def add_child(self, item): | |
| item.parent = self | |
| self.children.append(item) | |
| def filter(self, key): | |
| for child in self.children: | |
| if child.value[0] == key: | |
| yield child | |
| def __iter__(self): | |
| for child in self.children: | |
| yield child | |
| def __getitem__(self, key): | |
| for child in self.children: | |
| for index, item in enumerate(child.value): | |
| if item == key: | |
| if index + 1 >= len(child.value): | |
| return None | |
| else: | |
| return child.value[index + 1 :] | |
| raise IndexError("key {} not found".format(key)) | |
| def __repr__(self): | |
| return str(" ".join(self.value)) | |
| def name(self): | |
| return self.value[1] | |
| class Bvh: | |
| """Abstraction for bvh.""" | |
| def __init__(self, data: str, backend: Optional[str] = "graph"): | |
| """ | |
| Args: | |
| data (str): bvh file content | |
| """ | |
| self.data = data | |
| self.root = BvhNode() | |
| self.frames = [] | |
| self.backend = backend | |
| self.tokenize() | |
| if self.backend == "np": | |
| # cache important info for quick access later | |
| self.build_data_array() | |
| elif self.backend == "graph": | |
| pass | |
| else: | |
| raise ValueError(f"Unknown backend for BVH loading: {backend}") | |
| def build_data_array(self): | |
| joints = self.get_joints() | |
| self.joint2idx = dict() | |
| self.joint2channels = dict() | |
| cur_idx = 0 | |
| for joint in joints: | |
| self.joint2idx[joint.value[1]] = cur_idx | |
| cur_idx += int(joint["CHANNELS"][0]) | |
| self.joint2channels[joint.value[1]] = joint["CHANNELS"][1:] | |
| self.np_data_array = np.array(self.frames, dtype=np.float32) | |
| def tokenize(self): | |
| first_round = [] | |
| accumulator = "" | |
| for char in self.data: | |
| if char not in ("\n", "\r"): | |
| accumulator += char | |
| elif accumulator: | |
| first_round.append(re.split("\\s+", accumulator.strip())) | |
| accumulator = "" | |
| node_stack = [self.root] | |
| frame_time_found = False | |
| node = None | |
| for item in first_round: | |
| if frame_time_found: | |
| self.frames.append(item) | |
| continue | |
| key = item[0] | |
| if key == "{": | |
| node_stack.append(node) | |
| elif key == "}": | |
| node_stack.pop() | |
| else: | |
| node = BvhNode(item) | |
| # print("new node: ", node, "\nparent: ", node_stack[-1]) | |
| node_stack[-1].add_child(node) | |
| if item[0] == "Frame" and item[1] == "Time:": | |
| frame_time_found = True | |
| def search(self, *items): | |
| found_nodes = [] | |
| def check_children(node): | |
| if len(node.value) >= len(items): | |
| failed = False | |
| for index, item in enumerate(items): | |
| if node.value[index] != item: | |
| failed = True | |
| break | |
| if not failed: | |
| found_nodes.append(node) | |
| for child in node: | |
| check_children(child) | |
| check_children(self.root) | |
| return found_nodes | |
| def get_joints(self): | |
| joints = [] | |
| def iterate_joints(joint): | |
| joints.append(joint) | |
| for child in joint.filter("JOINT"): | |
| iterate_joints(child) | |
| iterate_joints(next(self.root.filter("ROOT"))) | |
| return joints | |
| def get_joints_names(self): | |
| joints = [] | |
| def iterate_joints(joint): | |
| joints.append(joint.value[1]) | |
| for child in joint.filter("JOINT"): | |
| iterate_joints(child) | |
| iterate_joints(next(self.root.filter("ROOT"))) | |
| return joints | |
| def joint_direct_children(self, name): | |
| joint = self.get_joint(name) | |
| return [child for child in joint.filter("JOINT")] | |
| def get_joint_index(self, name): | |
| return self.get_joints().index(self.get_joint(name)) | |
| def get_joint(self, name): | |
| found = self.search("ROOT", name) | |
| if not found: | |
| found = self.search("JOINT", name) | |
| if found: | |
| return found[0] | |
| raise LookupError("joint not found") | |
| def joint_offset(self, name, idx=[0, 1, 2]): | |
| joint = self.get_joint(name) | |
| offset = joint["OFFSET"] | |
| if len(offset) < max(idx): | |
| return None | |
| return (float(offset[idx[0]]), float(offset[idx[1]]), float(offset[idx[2]])) | |
| def joint_offset_rot(self, name): | |
| return self.joint_offset(name, idx=[3, 4, 5]) | |
| def joint_channels(self, name): | |
| if self.backend == "np": | |
| return self.joint2channels[name] | |
| else: | |
| joint = self.get_joint(name) | |
| return joint["CHANNELS"][1:] | |
| def get_joint_channels_index(self, joint_name): | |
| if self.backend == "np": | |
| return self.joint2idx[joint_name] | |
| else: | |
| index = 0 | |
| for joint in self.get_joints(): | |
| if joint.value[1] == joint_name: | |
| return index | |
| index += int(joint["CHANNELS"][0]) | |
| raise LookupError("joint not found") | |
| def get_joint_channel_index(self, joint, channel): | |
| channels = self.joint_channels(joint) | |
| if channel in channels: | |
| channel_index = channels.index(channel) | |
| else: | |
| raise ValueError(f"Channel {channel} not found in {channels}") | |
| return channel_index | |
| def frame_joint_channel(self, frame_index, joint, channel, value=None): | |
| """Get single frame data for on specific joint and one specific channel (e.g. Xrotation).""" | |
| joint_index = self.get_joint_channels_index(joint) | |
| channel_index = self.get_joint_channel_index(joint, channel) | |
| if channel_index == -1 and value is not None: | |
| return value | |
| if self.backend == "np": | |
| return self.np_data_array[frame_index, joint_index + channel_index] | |
| else: | |
| return float(self.frames[frame_index][joint_index + channel_index]) | |
| def frame_joint_channels(self, frame_index, joint, channels, value=None): | |
| """Get single frame data for on specific joint from multiple specific channels (e.g. | |
| Xrotation, Yrotation, Zrotation).""" | |
| values = [] | |
| joint_index = self.get_joint_channels_index(joint) | |
| if self.backend == "np": | |
| channel_idx = [self.get_joint_channel_index(joint, channel) for channel in channels] | |
| channel_idx = np.array(channel_idx) + joint_index | |
| values = self.np_data_array[frame_index, channel_idx] | |
| else: | |
| for channel in channels: | |
| channel_index = self.get_joint_channel_index(joint, channel) | |
| if channel_index == -1 and value is not None: | |
| values.append(value) | |
| else: | |
| values.append(float(self.frames[frame_index][joint_index + channel_index])) | |
| return values | |
| def frames_joint_channels(self, joint, channels, value=None): | |
| """Get all frame data for one joint from multiple channels (e.g. Xrotation, Yrotation, | |
| Zrotation).""" | |
| joint_index = self.get_joint_channels_index(joint) | |
| if self.backend == "np": | |
| channel_idx = [self.get_joint_channel_index(joint, channel) for channel in channels] | |
| channel_idx = np.array(channel_idx) + joint_index | |
| all_frames = self.np_data_array[:, channel_idx] | |
| else: | |
| all_frames = [] | |
| for frame in self.frames: | |
| values = [] | |
| for channel in channels: | |
| channel_index = self.get_joint_channel_index(joint, channel) | |
| if channel_index == -1 and value is not None: | |
| values.append(value) | |
| else: | |
| values.append(float(frame[joint_index + channel_index])) | |
| all_frames.append(values) | |
| return all_frames | |
| def frames_joints_channels(self, joint_names, channels): | |
| """Get all frames for all specified joints with one specified set of channels.""" | |
| if self.backend != "np": | |
| raise NotImplementedError("Only np backend is supported for this function") | |
| joint_indices = [(joint_name, self.joint2idx[joint_name]) for joint_name in joint_names] | |
| data_indices = [] | |
| for joint_name, joint_idx in joint_indices: | |
| channel_indices = [self.get_joint_channel_index(joint_name, channel) for channel in channels] | |
| data_indices.extend([joint_idx + channel_idx for channel_idx in channel_indices]) | |
| all_frames = self.np_data_array[:, data_indices] | |
| all_frames = all_frames.reshape(-1, len(joint_names), len(channels)) | |
| return all_frames | |
| def joint_parent(self, name): | |
| joint = self.get_joint(name) | |
| if joint.parent == self.root: | |
| return None | |
| return joint.parent | |
| def joint_parent_index(self, name): | |
| joint = self.get_joint(name) | |
| if joint.parent == self.root: | |
| return -1 | |
| return self.get_joints().index(joint.parent) | |
| def nframes(self): | |
| try: | |
| return int(next(self.root.filter("Frames:")).value[1]) | |
| except StopIteration: | |
| raise LookupError("number of frames not found") | |
| def frame_time(self): | |
| try: | |
| return float(next(self.root.filter("Frame")).value[2]) | |
| except StopIteration: | |
| raise LookupError("frame time not found") | |