| | import re |
| | import numpy as np |
| |
|
| | channelmap = { |
| | 'Xrotation': 'x', |
| | 'Yrotation': 'y', |
| | 'Zrotation': 'z' |
| | } |
| |
|
| | channelmap_inv = { |
| | 'x': 'Xrotation', |
| | 'y': 'Yrotation', |
| | 'z': 'Zrotation', |
| | } |
| |
|
| | ordermap = { |
| | 'x': 0, |
| | 'y': 1, |
| | 'z': 2, |
| | } |
| |
|
| | def load(filename:str, order:str=None) -> dict: |
| | """Loads a BVH file. |
| | |
| | Args: |
| | filename (str): Path to the BVH file. |
| | order (str): The order of the rotation channels. (i.e."xyz") |
| | |
| | Returns: |
| | dict: A dictionary containing the following keys: |
| | * names (list)(jnum): The names of the joints. |
| | * parents (list)(jnum): The parent indices. |
| | * offsets (np.ndarray)(jnum, 3): The offsets of the joints. |
| | * rotations (np.ndarray)(fnum, jnum, 3) : The local coordinates of rotations of the joints. |
| | * positions (np.ndarray)(fnum, jnum, 3) : The positions of the joints. |
| | * order (str): The order of the channels. |
| | * frametime (float): The time between two frames. |
| | """ |
| |
|
| | f = open(filename, "r") |
| |
|
| | i = 0 |
| | active = -1 |
| | end_site = False |
| |
|
| | |
| | names = [] |
| | offsets = np.array([]).reshape((0, 3)) |
| | parents = np.array([], dtype=int) |
| |
|
| | |
| | for line in f: |
| |
|
| | if "HIERARCHY" in line: continue |
| | if "MOTION" in line: continue |
| |
|
| | rmatch = re.match(r"ROOT (\w+)", line) |
| | if rmatch: |
| | names.append(rmatch.group(1)) |
| | offsets = np.append(offsets, np.array([[0, 0, 0]]), axis=0) |
| | parents = np.append(parents, active) |
| | active = (len(parents) - 1) |
| | continue |
| |
|
| | if "{" in line: continue |
| |
|
| | if "}" in line: |
| | if end_site: |
| | end_site = False |
| | else: |
| | active = parents[active] |
| | continue |
| |
|
| | offmatch = re.match(r"\s*OFFSET\s+([\-\d\.e]+)\s+([\-\d\.e]+)\s+([\-\d\.e]+)", line) |
| | if offmatch: |
| | if not end_site: |
| | offsets[active] = np.array([list(map(float, offmatch.groups()))]) |
| | continue |
| |
|
| | chanmatch = re.match(r"\s*CHANNELS\s+(\d+)", line) |
| | if chanmatch: |
| | channels = int(chanmatch.group(1)) |
| | if order is None: |
| | channelis = 0 if channels == 3 else 3 |
| | channelie = 3 if channels == 3 else 6 |
| | parts = line.split()[2 + channelis:2 + channelie] |
| | if any([p not in channelmap for p in parts]): |
| | continue |
| | order = "".join([channelmap[p] for p in parts]) |
| | continue |
| |
|
| | jmatch = re.match("\s*JOINT\s+(\w+)", line) |
| | if jmatch: |
| | names.append(jmatch.group(1)) |
| | offsets = np.append(offsets, np.array([[0, 0, 0]]), axis=0) |
| | parents = np.append(parents, active) |
| | active = (len(parents) - 1) |
| | continue |
| |
|
| | if "End Site" in line: |
| | end_site = True |
| | continue |
| |
|
| | fmatch = re.match("\s*Frames:\s+(\d+)", line) |
| | if fmatch: |
| | fnum = int(fmatch.group(1)) |
| | positions = offsets[None].repeat(fnum, axis=0) |
| | rotations = np.zeros((fnum, len(offsets), 3)) |
| | continue |
| |
|
| | fmatch = re.match("\s*Frame Time:\s+([\d\.]+)", line) |
| | if fmatch: |
| | frametime = float(fmatch.group(1)) |
| | continue |
| |
|
| | dmatch = line.strip().split(' ') |
| | if dmatch: |
| | data_block = np.array(list(map(float, dmatch))) |
| | N = len(parents) |
| | fi = i |
| | if channels == 3: |
| | positions[fi, 0:1] = data_block[0:3] |
| | rotations[fi, :] = data_block[3:].reshape(N, 3) |
| | elif channels == 6: |
| | data_block = data_block.reshape(N, 6) |
| | positions[fi, :] = data_block[:, 0:3] |
| | rotations[fi, :] = data_block[:, 3:6] |
| | elif channels == 9: |
| | positions[fi, 0] = data_block[0:3] |
| | data_block = data_block[3:].reshape(N - 1, 9) |
| | rotations[fi, 1:] = data_block[:, 3:6] |
| | positions[fi, 1:] += data_block[:, 0:3] * data_block[:, 6:9] |
| | else: |
| | raise Exception("Too many channels! %i" % channels) |
| |
|
| | i += 1 |
| |
|
| | f.close() |
| |
|
| | return { |
| | 'rotations': rotations, |
| | 'positions': positions, |
| | 'offsets': offsets, |
| | 'parents': parents, |
| | 'names': names, |
| | 'order': order, |
| | 'frametime': frametime |
| | } |
| | |
| | |
| | def save_joint(f, data, t, i, save_order, order='zyx', save_positions=False): |
| | |
| | save_order.append(i) |
| | |
| | f.write("%sJOINT %s\n" % (t, data['names'][i])) |
| | f.write("%s{\n" % t) |
| | t += '\t' |
| |
|
| | f.write("%sOFFSET %f %f %f\n" % (t, data['offsets'][i,0], data['offsets'][i,1], data['offsets'][i,2])) |
| | |
| | if save_positions: |
| | f.write("%sCHANNELS 6 Xposition Yposition Zposition %s %s %s \n" % (t, |
| | channelmap_inv[order[0]], channelmap_inv[order[1]], channelmap_inv[order[2]])) |
| | else: |
| | f.write("%sCHANNELS 3 %s %s %s\n" % (t, |
| | channelmap_inv[order[0]], channelmap_inv[order[1]], channelmap_inv[order[2]])) |
| | |
| | end_site = True |
| | |
| | for j in range(len(data['parents'])): |
| | if data['parents'][j] == i: |
| | t = save_joint(f, data, t, j, save_order, order=order, save_positions=save_positions) |
| | end_site = False |
| | |
| | if end_site: |
| | f.write("%sEnd Site\n" % t) |
| | f.write("%s{\n" % t) |
| | t += '\t' |
| | f.write("%sOFFSET %f %f %f\n" % (t, 0.0, 0.0, 0.0)) |
| | t = t[:-1] |
| | f.write("%s}\n" % t) |
| |
|
| | t = t[:-1] |
| | f.write("%s}\n" % t) |
| | |
| | return t |
| | |
| |
|
| | def save(filename, data, save_positions=False): |
| | """ Save a joint hierarchy to a file. |
| | |
| | Args: |
| | filename (str): The output will save on the bvh file. |
| | data (dict): The data to save.(rotations, positions, offsets, parents, names, order, frametime) |
| | save_positions (bool): Whether to save all of joint positions on MOTION. (False is recommended.) |
| | """ |
| | |
| | order = data['order'] |
| | frametime = data['frametime'] |
| | |
| | with open(filename, 'w') as f: |
| |
|
| | t = "" |
| | f.write("%sHIERARCHY\n" % t) |
| | f.write("%sROOT %s\n" % (t, data['names'][0])) |
| | f.write("%s{\n" % t) |
| | t += '\t' |
| |
|
| | f.write("%sOFFSET %f %f %f\n" % (t, data['offsets'][0,0], data['offsets'][0,1], data['offsets'][0,2]) ) |
| | f.write("%sCHANNELS 6 Xposition Yposition Zposition %s %s %s \n" % |
| | (t, channelmap_inv[order[0]], channelmap_inv[order[1]], channelmap_inv[order[2]])) |
| |
|
| | save_order = [0] |
| | |
| | for i in range(len(data['parents'])): |
| | if data['parents'][i] == 0: |
| | t = save_joint(f, data, t, i, save_order, order=order, save_positions=save_positions) |
| | |
| | t = t[:-1] |
| | f.write("%s}\n" % t) |
| |
|
| | rots, poss = data['rotations'], data['positions'] |
| |
|
| | f.write("MOTION\n") |
| | f.write("Frames: %i\n" % len(rots)); |
| | f.write("Frame Time: %f\n" % frametime); |
| | |
| | for i in range(rots.shape[0]): |
| | for j in save_order: |
| | |
| | if save_positions or j == 0: |
| | |
| | f.write("%f %f %f %f %f %f " % ( |
| | poss[i,j,0], poss[i,j,1], poss[i,j,2], |
| | rots[i,j,0], rots[i,j,1], rots[i,j,2])) |
| | |
| | else: |
| | |
| | f.write("%f %f %f " % ( |
| | rots[i,j,0], rots[i,j,1], rots[i,j,2])) |
| |
|
| | f.write("\n") |