| | from dataclasses import dataclass, field |
| | from typing import BinaryIO, Dict, Optional, Union |
| |
|
| | import numpy as np |
| |
|
| | from .ply_util import write_ply |
| |
|
| |
|
| | @dataclass |
| | class TriMesh: |
| | """ |
| | A 3D triangle mesh with optional data at the vertices and faces. |
| | """ |
| |
|
| | |
| | verts: np.ndarray |
| |
|
| | |
| | faces: np.ndarray |
| |
|
| | |
| | normals: Optional[np.ndarray] = None |
| |
|
| | |
| | vertex_channels: Optional[Dict[str, np.ndarray]] = field(default_factory=dict) |
| | face_channels: Optional[Dict[str, np.ndarray]] = field(default_factory=dict) |
| |
|
| | @classmethod |
| | def load(cls, f: Union[str, BinaryIO]) -> "TriMesh": |
| | """ |
| | Load the mesh from a .npz file. |
| | """ |
| | if isinstance(f, str): |
| | with open(f, "rb") as reader: |
| | return cls.load(reader) |
| | else: |
| | obj = np.load(f) |
| | keys = list(obj.keys()) |
| | verts = obj["verts"] |
| | faces = obj["faces"] |
| | normals = obj["normals"] if "normals" in keys else None |
| | vertex_channels = {} |
| | face_channels = {} |
| | for key in keys: |
| | if key.startswith("v_"): |
| | vertex_channels[key[2:]] = obj[key] |
| | elif key.startswith("f_"): |
| | face_channels[key[2:]] = obj[key] |
| | return cls( |
| | verts=verts, |
| | faces=faces, |
| | normals=normals, |
| | vertex_channels=vertex_channels, |
| | face_channels=face_channels, |
| | ) |
| |
|
| | def save(self, f: Union[str, BinaryIO]): |
| | """ |
| | Save the mesh to a .npz file. |
| | """ |
| | if isinstance(f, str): |
| | with open(f, "wb") as writer: |
| | self.save(writer) |
| | else: |
| | obj_dict = dict(verts=self.verts, faces=self.faces) |
| | if self.normals is not None: |
| | obj_dict["normals"] = self.normals |
| | for k, v in self.vertex_channels.items(): |
| | obj_dict[f"v_{k}"] = v |
| | for k, v in self.face_channels.items(): |
| | obj_dict[f"f_{k}"] = v |
| | np.savez(f, **obj_dict) |
| |
|
| | def has_vertex_colors(self) -> bool: |
| | return self.vertex_channels is not None and all(x in self.vertex_channels for x in "RGB") |
| |
|
| | def write_ply(self, raw_f: BinaryIO): |
| | write_ply( |
| | raw_f, |
| | coords=self.verts, |
| | rgb=( |
| | np.stack([self.vertex_channels[x] for x in "RGB"], axis=1) |
| | if self.has_vertex_colors() |
| | else None |
| | ), |
| | faces=self.faces, |
| | ) |
| |
|