Spaces:
Runtime error
Runtime error
| from io import TextIOWrapper | |
| from typing import Dict, Any, Union, Iterable | |
| import numpy as np | |
| from pathlib import Path | |
| __all__ = [ | |
| 'read_obj', | |
| 'write_obj', | |
| 'simple_write_obj' | |
| ] | |
| def read_obj( | |
| file : Union[str, Path, TextIOWrapper], | |
| encoding: Union[str, None] = None, | |
| ignore_unknown: bool = False | |
| ): | |
| """ | |
| Read wavefront .obj file, without preprocessing. | |
| Why bothering having this read_obj() while we already have other libraries like `trimesh`? | |
| This function read the raw format from .obj file and keeps the order of vertices and faces, | |
| while trimesh which involves modification like merge/split vertices, which could break the orders of vertices and faces, | |
| Those libraries are commonly aiming at geometry processing and rendering supporting various formats. | |
| If you want mesh geometry processing, you may turn to `trimesh` for more features. | |
| ### Parameters | |
| `file` (str, Path, TextIOWrapper): filepath or file object | |
| encoding (str, optional): | |
| ### Returns | |
| obj (dict): A dict containing .obj components | |
| { | |
| 'mtllib': [], | |
| 'v': [[0,1, 0.2, 1.0], [1.2, 0.0, 0.0], ...], | |
| 'vt': [[0.5, 0.5], ...], | |
| 'vn': [[0., 0.7, 0.7], [0., -0.7, 0.7], ...], | |
| 'f': [[0, 1, 2], [2, 3, 4],...], | |
| 'usemtl': [{'name': 'mtl1', 'f': 7}] | |
| } | |
| """ | |
| if hasattr(file,'read'): | |
| lines = file.read().splitlines() | |
| else: | |
| with open(file, 'r', encoding=encoding) as fp: | |
| lines = fp.read().splitlines() | |
| mtllib = [] | |
| v, vt, vn, vp = [], [], [], [] # Vertex coordinates, Vertex texture coordinate, Vertex normal, Vertex parameter | |
| f, ft, fn = [], [], [] # Face indices, Face texture indices, Face normal indices | |
| o = [] | |
| s = [] | |
| usemtl = [] | |
| def pad(l: list, n: Any): | |
| return l + [n] * (3 - len(l)) | |
| for i, line in enumerate(lines): | |
| sq = line.strip().split() | |
| if len(sq) == 0: | |
| continue | |
| if sq[0] == 'v': | |
| assert 4 <= len(sq) <= 5, f'Invalid format of line {i}: {line}' | |
| v.append([float(e) for e in sq[1:]][:3]) | |
| elif sq[0] == 'vt': | |
| assert 3 <= len(sq) <= 4, f'Invalid format of line {i}: {line}' | |
| vt.append([float(e) for e in sq[1:]][:2]) | |
| elif sq[0] == 'vn': | |
| assert len(sq) == 4, f'Invalid format of line {i}: {line}' | |
| vn.append([float(e) for e in sq[1:]]) | |
| elif sq[0] == 'vp': | |
| assert 2 <= len(sq) <= 4, f'Invalid format of line {i}: {line}' | |
| vp.append(pad([float(e) for e in sq[1:]], 0)) | |
| elif sq[0] == 'f': | |
| spliting = [pad([int(j) - 1 for j in e.split('/')], -1) for e in sq[1:]] | |
| f.append([e[0] for e in spliting]) | |
| ft.append([e[1] for e in spliting]) | |
| fn.append([e[2] for e in spliting]) | |
| elif sq[0] == 'usemtl': | |
| assert len(sq) == 2 | |
| usemtl.append((sq[1], len(f))) | |
| elif sq[0] == 'o': | |
| assert len(sq) == 2 | |
| o.append((sq[1], len(f))) | |
| elif sq[0] == 's': | |
| s.append((sq[1], len(f))) | |
| elif sq[0] == 'mtllib': | |
| assert len(sq) == 2 | |
| mtllib.append(sq[1]) | |
| elif sq[0][0] == '#': | |
| continue | |
| else: | |
| if not ignore_unknown: | |
| raise Exception(f'Unknown keyword {sq[0]}') | |
| min_poly_vertices = min(len(f) for f in f) | |
| max_poly_vertices = max(len(f) for f in f) | |
| return { | |
| 'mtllib': mtllib, | |
| 'v': np.array(v, dtype=np.float32), | |
| 'vt': np.array(vt, dtype=np.float32), | |
| 'vn': np.array(vn, dtype=np.float32), | |
| 'vp': np.array(vp, dtype=np.float32), | |
| 'f': np.array(f, dtype=np.int32) if min_poly_vertices == max_poly_vertices else f, | |
| 'ft': np.array(ft, dtype=np.int32) if min_poly_vertices == max_poly_vertices else ft, | |
| 'fn': np.array(fn, dtype=np.int32) if min_poly_vertices == max_poly_vertices else fn, | |
| 'o': o, | |
| 's': s, | |
| 'usemtl': usemtl, | |
| } | |
| def write_obj( | |
| file: Union[str, Path], | |
| obj: Dict[str, Any], | |
| encoding: Union[str, None] = None | |
| ): | |
| with open(file, 'w', encoding=encoding) as fp: | |
| for k in ['v', 'vt', 'vn', 'vp']: | |
| if k not in obj: | |
| continue | |
| for v in obj[k]: | |
| print(k, *map(float, v), file=fp) | |
| for f in obj['f']: | |
| print('f', *((str('/').join(map(int, i)) if isinstance(int(i), Iterable) else i) for i in f), file=fp) | |
| def simple_write_obj( | |
| file: Union[str, Path], | |
| vertices: np.ndarray, | |
| faces: np.ndarray, | |
| encoding: Union[str, None] = None | |
| ): | |
| """ | |
| Write wavefront .obj file, without preprocessing. | |
| Args: | |
| vertices (np.ndarray): [N, 3] | |
| faces (np.ndarray): [T, 3] | |
| file (Any): filepath | |
| encoding (str, optional): | |
| """ | |
| with open(file, 'w', encoding=encoding) as fp: | |
| for v in vertices: | |
| print('v', *map(float, v), file=fp) | |
| for f in faces: | |
| print('f', *map(int, f + 1), file=fp) | |