import struct import numpy as np import collections def _write_ply_point(fp, x,y,z, color=None, normal=None, binary=False): args = [x,y,z] if color is not None: args += [int(color[0]), int(color[1]), int(color[2])] if normal is not None: args += [normal[0],normal[1],normal[2]] if binary: fmt = ' 1: c = color[vidx] else: c = color[0] else: c = None if normals is None: n = None else: n = normals[vidx] _write_ply_point(fp, v[0],v[1],v[2], c, n, binary) if trias is not None: for t in trias: _write_ply_triangle(fp, t[0],t[1],t[2], binary) def faces_to_triangles(faces): new_faces = [] for f in faces: if f[0] == 3: new_faces.append([f[1], f[2], f[3]]) elif f[0] == 4: new_faces.append([f[1], f[2], f[3]]) new_faces.append([f[3], f[4], f[1]]) else: raise Exception('unknown face count %d', f[0]) return new_faces def read_ply(path): with open(path, 'rb') as f: # parse header line = f.readline().decode().strip() if line != 'ply': raise Exception('Header error') n_verts = 0 n_faces = 0 vert_types = {} vert_bin_format = [] vert_bin_len = 0 vert_bin_cols = 0 line = f.readline().decode() parse_vertex_prop = False while line.strip() != 'end_header': if 'format' in line: if 'ascii' in line: binary = False elif 'binary_little_endian' in line: binary = True else: raise Exception('invalid ply format') if 'element face' in line: splits = line.strip().split(' ') n_faces = int(splits[-1]) parse_vertex_prop = False if 'element camera' in line: parse_vertex_prop = False if 'element vertex' in line: splits = line.strip().split(' ') n_verts = int(splits[-1]) parse_vertex_prop = True if parse_vertex_prop and 'property' in line: prop = line.strip().split() if prop[1] == 'float': vert_bin_format.append('f4') vert_bin_len += 4 vert_bin_cols += 1 elif prop[1] == 'uchar': vert_bin_format.append('B') vert_bin_len += 1 vert_bin_cols += 1 else: raise Exception('invalid property') vert_types[prop[2]] = len(vert_types) line = f.readline().decode() # parse content if binary: sz = n_verts * vert_bin_len fmt = ','.join(vert_bin_format) verts = np.ndarray(shape=(1, n_verts), dtype=np.dtype(fmt), buffer=f.read(sz)) verts = verts[0].astype(vert_bin_cols*'f4,').view(dtype='f4').reshape((n_verts,-1)) faces = [] for idx in range(n_faces): fmt = '= 2 and len(parts[1]) > 0: tidx = int(parts[1]) - 1 else: tidx = -1 if len(parts) >= 3 and len(parts[2]) > 0: nidx = int(parts[2]) - 1 else: nidx = -1 return vidx, tidx, nidx def read_obj(path): with open(path, 'r') as fp: lines = fp.readlines() verts = [] colors = [] fnorms = [] fnorm_map = collections.defaultdict(list) faces = [] for line in lines: line = line.strip() if line.startswith('#') or len(line) == 0: continue parts = line.split() if line.startswith('v '): parts = parts[1:] x,y,z = float(parts[0]), float(parts[1]), float(parts[2]) if len(parts) == 4 or len(parts) == 7: w = float(parts[3]) x,y,z = x/w, y/w, z/w verts.append((x,y,z)) if len(parts) >= 6: r,g,b = float(parts[-3]), float(parts[-2]), float(parts[-1]) rgb.append((r,g,b)) elif line.startswith('vn '): parts = parts[1:] x,y,z = float(parts[0]), float(parts[1]), float(parts[2]) fnorms.append((x,y,z)) elif line.startswith('f '): parts = parts[1:] if len(parts) != 3: raise Exception('only triangle meshes supported atm') vidx0, tidx0, nidx0 = _read_obj_split_f(parts[0]) vidx1, tidx1, nidx1 = _read_obj_split_f(parts[1]) vidx2, tidx2, nidx2 = _read_obj_split_f(parts[2]) faces.append((vidx0, vidx1, vidx2)) if nidx0 >= 0: fnorm_map[vidx0].append( nidx0 ) if nidx1 >= 0: fnorm_map[vidx1].append( nidx1 ) if nidx2 >= 0: fnorm_map[vidx2].append( nidx2 ) verts = np.array(verts) colors = np.array(colors) fnorms = np.array(fnorms) faces = np.array(faces) # face normals to vertex normals norms = np.zeros_like(verts) for vidx in fnorm_map.keys(): ind = fnorm_map[vidx] norms[vidx] = fnorms[ind].sum(axis=0) N = np.linalg.norm(norms, axis=1, keepdims=True) np.divide(norms, N, out=norms, where=N != 0) return verts, faces, colors, norms