| from dataclasses import dataclass |
| import numpy as np |
| from numpy import ndarray |
|
|
| import os |
| from typing import Union, List, Tuple |
|
|
| from .exporter import Exporter |
|
|
| from ..tokenizer.spec import DetokenizeOutput |
| from .order import Order |
|
|
| @dataclass(frozen=True) |
| class RawData(Exporter): |
| ''' |
| Dataclass to handle data from processed model files. |
| ''' |
| |
| |
| vertices: Union[ndarray, None] |
| |
| |
| vertex_normals: Union[ndarray, None] |
| |
| |
| faces: Union[ndarray, None] |
| |
| |
| face_normals: Union[ndarray, None] |
| |
| |
| joints: Union[ndarray, None] |
| |
| |
| tails: Union[ndarray, None] |
| |
| |
| skin: Union[ndarray, None] |
| |
| |
| no_skin: Union[ndarray, None] |
| |
| |
| |
| parents: Union[List[Union[int, None]], None] |
| |
| |
| names: Union[List[str], None] |
| |
| |
| matrix_local: Union[ndarray, None] |
| |
| |
| path: Union[str, None]=None |
| |
| |
| cls: Union[str, None]=None |
| |
| @staticmethod |
| def load(path: str) -> 'RawData': |
| data = np.load(path, allow_pickle=True) |
| d = {name: data[name][()] for name in data} |
| d['path'] = path |
| return RawData(**d) |
| |
| def save(self, path: str): |
| os.makedirs(os.path.dirname(path), exist_ok=True) |
| np.savez(file=path, **self.__dict__) |
| |
| @property |
| def N(self): |
| ''' |
| number of vertices |
| ''' |
| return self.vertices.shape[0] |
| |
| @property |
| def F(self): |
| ''' |
| number of faces |
| ''' |
| return self.faces.shape[0] |
| |
| @property |
| def J(self): |
| ''' |
| number of joints |
| ''' |
| return self.joints.shape[0] |
| |
| def check(self): |
| if self.names is not None and self.joints is not None: |
| assert len(self.names) == self.J |
| if self.names is not None and self.parents is not None: |
| assert len(self.names) == len(self.parents) |
| if self.parents is not None: |
| for (i, pid) in enumerate(self.parents): |
| if i==0: |
| assert pid is None |
| else: |
| assert pid is not None |
| assert pid < i |
| |
| def export_pc(self, path: str, with_normal: bool=True, normal_size=0.01): |
| ''' |
| export point cloud |
| ''' |
| if with_normal: |
| self._export_pc(vertices=self.vertices, path=path, vertex_normals=self.vertex_normals, normal_size=normal_size) |
| else: |
| self._export_pc(vertices=self.vertices, path=path, vertex_normals=None, normal_size=normal_size) |
| |
| def export_mesh(self, path: str): |
| ''' |
| export mesh |
| ''' |
| self._export_mesh(vertices=self.vertices, faces=self.faces, path=path) |
| |
| def export_skeleton(self, path: str): |
| ''' |
| export spring |
| ''' |
| self._export_skeleton(joints=self.joints, parents=self.parents, path=path) |
| |
| def export_skeleton_sequence(self, path: str): |
| ''' |
| export spring |
| ''' |
| self._export_skeleton_sequence(joints=self.joints, parents=self.parents, path=path) |
| |
| def export_fbx( |
| self, |
| path: str, |
| extrude_size: float=0.03, |
| group_per_vertex: int=-1, |
| add_root: bool=False, |
| do_not_normalize: bool=False, |
| use_extrude_bone: bool=True, |
| use_connect_unique_child: bool=True, |
| extrude_from_parent: bool=True, |
| use_tail: bool=False, |
| custom_vertex_group: Union[ndarray, None]=None, |
| ): |
| ''' |
| export the whole model with skining |
| ''' |
| self._export_fbx( |
| path=path, |
| vertices=self.vertices, |
| joints=self.joints, |
| skin=self.skin if custom_vertex_group is None else custom_vertex_group, |
| parents=self.parents, |
| names=self.names, |
| faces=self.faces, |
| extrude_size=extrude_size, |
| group_per_vertex=group_per_vertex, |
| add_root=add_root, |
| do_not_normalize=do_not_normalize, |
| use_extrude_bone=use_extrude_bone, |
| use_connect_unique_child=use_connect_unique_child, |
| extrude_from_parent=extrude_from_parent, |
| tails=self.tails if use_tail else None, |
| ) |
| |
| def export_render(self, path: str, resolution: Tuple[int, int]=[256, 256]): |
| self._export_render( |
| path=path, |
| vertices=self.vertices, |
| faces=self.faces, |
| bones=np.concatenate([self.joints, self.tails], axis=-1), |
| resolution=resolution, |
| ) |
|
|
| @dataclass(frozen=True) |
| class RawSkeleton(Exporter): |
| ''' |
| Dataclass to handle skeleton from AR. |
| ''' |
| |
| joints: Union[ndarray, None] |
| |
| |
| tails: Union[ndarray, None] |
| |
| |
| no_skin: Union[ndarray, None] |
| |
| |
| |
| parents: Union[List[Union[int, None]], None] |
| |
| |
| names: Union[List[str], None] |
| |
| @staticmethod |
| def load(path: str) -> 'RawSkeleton': |
| data = np.load(path, allow_pickle=True) |
| return RawSkeleton(**{name: data[name][()] for name in data}) |
| |
| def save(self, path: str): |
| os.makedirs(os.path.dirname(path), exist_ok=True) |
| np.savez(file=path, **self.__dict__) |
| |
| @staticmethod |
| def from_detokenize_output(res: DetokenizeOutput, order: Union[Order, None]) -> 'RawSkeleton': |
| J = len(res.bones) |
| names = order.make_names(cls=res.cls, parts=res.parts, num_bones=J) |
| joints = res.joints |
| p_joints = res.p_joints |
| parents = [] |
| for (i, joint) in enumerate(joints): |
| if i == 0: |
| parents.append(None) |
| continue |
| p_joint = p_joints[i] |
| dis = 999999 |
| pid = None |
| for j in reversed(range(i)): |
| n_dis = ((joints[j] - p_joint)**2).sum() |
| if n_dis < dis: |
| pid = j |
| dis = n_dis |
| parents.append(pid) |
| return RawSkeleton( |
| joints=joints, |
| tails=res.tails, |
| no_skin=res.no_skin, |
| parents=parents, |
| names=names, |
| ) |
| |
| def export_skeleton(self, path: str): |
| ''' |
| export spring |
| ''' |
| self._export_skeleton(joints=self.joints, parents=self.parents, path=path) |
| |
| def export_skeleton_sequence(self, path: str): |
| ''' |
| export spring |
| ''' |
| self._export_skeleton_sequence(joints=self.joints, parents=self.parents, path=path) |
| |
| def export_fbx( |
| self, |
| path: str, |
| extrude_size: float=0.03, |
| group_per_vertex: int=-1, |
| add_root: bool=False, |
| do_not_normalize: bool=False, |
| use_extrude_bone: bool=True, |
| use_connect_unique_child: bool=True, |
| extrude_from_parent: bool=True, |
| use_tail: bool=False, |
| ): |
| ''' |
| export the whole model with skining |
| ''' |
| self._export_fbx( |
| path=path, |
| vertices=None, |
| joints=self.joints, |
| skin=None, |
| parents=self.parents, |
| names=self.names, |
| faces=None, |
| extrude_size=extrude_size, |
| group_per_vertex=group_per_vertex, |
| add_root=add_root, |
| do_not_normalize=do_not_normalize, |
| use_extrude_bone=use_extrude_bone, |
| use_connect_unique_child=use_connect_unique_child, |
| extrude_from_parent=extrude_from_parent, |
| tails=self.tails if use_tail else None, |
| ) |
| |
| def export_render(self, path: str, resolution: Tuple[int, int]=[256, 256]): |
| self._export_render( |
| path=path, |
| vertices=None, |
| faces=None, |
| bones=np.concatenate([self.joints, self.tails], axis=-1), |
| resolution=resolution, |
| ) |
|
|
| @dataclass |
| class RawSkin(Exporter): |
| ''' |
| Dataclass to handle skeleton from AR. |
| ''' |
| |
| skin: ndarray |
| |
| |
| vertices: Union[ndarray, None]=None |
| |
| |
| joints: Union[ndarray, None]=None |
| |
| @staticmethod |
| def load(path: str) -> 'RawSkin': |
| data = np.load(path, allow_pickle=True) |
| return RawSkin(**{name: data[name][()] for name in data}) |
| |
| def save(self, path: str): |
| os.makedirs(os.path.dirname(path), exist_ok=True) |
| np.savez(file=path, **self.__dict__) |