--- a +++ b/app/resources/b3d/bvh_reader.py @@ -0,0 +1,211 @@ +class BVH(object): + + + def __init__(self): + super(BVH, self).__init__() + self.root = [] + self.channel_values = [] + self.channel_dict = {} + self.position_values = [] + self.position_dict = {} + + self.display_frame = 2000 + + + def load_from_file(self, bvh_file_path): + bvh_file = open(bvh_file_path, 'r') + bvh_str = bvh_file.read() + + self.tokens = self.tokenize(bvh_str) + if len(self.tokens) == 0: + return False + self.token_index = 0 + + if not self.parse_hierarchy(): + return False + if not self.parse_motion(): + return False + + bvh_file.close() + return True + + + def tokenize(self, source): + import re, os + # split source string with either white spaces, line separators, or tabulations + if (os.name == "posix"): + tokens = re.split(' |\n|\r\n|\t', source) #linux + else: + tokens = re.split(' |\n|\t', source) # windows + # filter the token list, remove all empty strings + return filter(None, tokens) + + + def parse_hierarchy(self): + if self.tokens[self.token_index] != 'HIERARCHY': + print('keyword HIERARCHY not found') + return False + self.token_index += 1 + self.joint_count = 0 + # parse all roots to support multiple hierarchy + while self.tokens[self.token_index] == 'ROOT': + joint = self.read_joint() + if joint: + self.root.append(joint) + return True + + + def parse_motion(self): + if self.tokens[self.token_index] != 'MOTION': + return False + self.token_index += 1 + if self.tokens[self.token_index] != 'Frames:': + print('keyword Frames: not found') + return False + self.token_index += 1 + try: + self.frame_count = int(self.tokens[self.token_index]) + except ValueError: + print('frame count invalid') + return False + self.token_index += 1 + # Frame Time: is treated as two tokens + if self.tokens[self.token_index] != 'Frame' or self.tokens[self.token_index + 1] != 'Time:': + print('keyword Frame Time: not found') + return False + self.token_index += 2 + try: + self.frame_time = float(self.tokens[self.token_index]) + except ValueError: + print('frame time invalid') + return False + self.token_index += 1 + for i in range(self.frame_count): + for j in range(len(self.channel_values)): + try: + self.channel_values[j].append(float(self.tokens[self.token_index])) + self.token_index += 1 + except ValueError: + print('frame data invalid', self.tokens[self.token_index]) + return False + return True + + + # before reading a joint, the token index will be pointing to the keyword ROOT, + # JOINT, or End + # + # after reading a joint, the token index will point to the token right after + # the closing brace of the joint which has been read + # + # in case of invalid joint format, function will ignore this joint and return None + # and the token index will point to the token that breaks joint format + def read_joint(self): + # 0 for ROOT or JOINT, 1 for End Site + joint_type = 0 + if self.tokens[self.token_index] == 'End': + joint_type = 1 + self.token_index += 1 + if joint_type == 0: + joint_name = self.tokens[self.token_index] + self.token_index += 1 + if self.tokens[self.token_index] != '{': + print('open brace not found') + return None + self.token_index += 1 + if self.tokens[self.token_index] != 'OFFSET': + print('keyword OFFSET not found') + return None + self.token_index += 1 + try: + joint_offset = [float(self.tokens[self.token_index]), float(self.tokens[self.token_index + 1]), float(self.tokens[self.token_index + 2])] + except ValueError: + print('offset value error') + return None + self.token_index += 3 + if joint_type == 0: + if self.tokens[self.token_index] != 'CHANNELS': + print('keyword CHANNELS not found') + return None + self.token_index += 1 + try: + joint_channel_count = int(self.tokens[self.token_index]) + except ValueError: + print('channel count value error') + return None + self.token_index += 1 + joint_channels = [] + self.channel_dict[joint_name] = {} + for i in range(joint_channel_count): + joint_channels.append(self.tokens[self.token_index]) + # channel data is stored in two data structures, a two-dimensional table which stores the channel values of each frame + # and a dictionary which stores the links between the joints and the indices in the value table + # in order to reference a channel value of a specifique joint at a specifique frame, one can do: + # + # self.channel_values[self.channel_dict[joint_name][channel_name]][frame] + # + # ==== NEED TO FIX LATER ==== + # + # channel data structure is created when the channel part of a joint is parsed, it could happen that there is invalid + # format in the rest of the joint definition + # since a joint with invalid definition will be ignored, the created channel data structure also becomes invalid + # to fix this later, we could perform a rollback when joint reading is failed + self.channel_dict[joint_name][self.tokens[self.token_index]] = len(self.channel_values) + self.channel_values.append([]) + self.token_index += 1 + joint_children = [] + while self.tokens[self.token_index] == 'JOINT' or self.tokens[self.token_index] == 'End': + child_joint = self.read_joint() + if child_joint: + joint_children.append(child_joint) + if self.tokens[self.token_index] != '}': + print('close brace not found : ', self.tokens[self.token_index]) + return None + if joint_type == 0: + joint = Joint(joint_name, joint_offset, joint_channels, joint_children) + self.joint_count += 1 + else: + joint = EndSite(joint_offset) + self.token_index += 1 + return joint + + +class Node(object): + + + def __init__(self, offset): + super(Node, self).__init__() + self.offset = offset + + + # def __str__(self): + # return '' + + +class Joint(Node): + + + def __init__(self, node_name, offset, channels, children): + super(Joint, self).__init__(offset) + self.name = node_name + self.channels = channels + self.children = children + + + # def __str__(self): + # res = self.name + # res += ' [' + # for joint in self.children: + # res += str(joint) + # res += '] ' + # return res + + +class EndSite(Node): + + + def __init__(self, offset): + super(EndSite, self).__init__(offset) + + + # def __str__(self): + # return 'EndSite : ' + str(self.offset)