121 lines
4.4 KiB
Python
121 lines
4.4 KiB
Python
|
|
from mtl_file_collection import MtlFileCollection
|
|
import os
|
|
|
|
class ObjFile(object):
|
|
def __init__(self, file_path) -> None:
|
|
self.logged_unsupported = set()
|
|
self.mtl_files = MtlFileCollection(os.path.dirname(file_path))
|
|
|
|
self.crt_object = ''
|
|
self.crt_mtl = ''
|
|
|
|
self.vertices = []
|
|
self.vertex_colors = []
|
|
self.faces = []
|
|
|
|
# Constructed in the post-process phase
|
|
self.objects = {}
|
|
|
|
with open(file_path, 'r') as f:
|
|
while True:
|
|
line = f.readline()
|
|
if not line:
|
|
break
|
|
if not line.strip() or line.startswith('#'):
|
|
continue
|
|
parts = line.strip().split(' ')
|
|
if parts[0] == 'v':
|
|
self.on_v(parts[1:])
|
|
elif parts[0] == 'l':
|
|
self.on_l(parts[1:])
|
|
elif parts[0] == 'f':
|
|
self.on_f(parts[1:])
|
|
elif parts[0] == 'mtllib':
|
|
self.mtl_files.mtllib(' '.join(parts[1:]))
|
|
elif parts[0] == 'usemtl':
|
|
self.crt_mtl = ' '.join(parts[1:])
|
|
elif parts[0] == 'o':
|
|
self.crt_object = parts[1]
|
|
else:
|
|
if parts[0] not in self.logged_unsupported:
|
|
print('Unsupported OBJ directive: ' + parts[0])
|
|
self.logged_unsupported.add(parts[0])
|
|
self.post_process()
|
|
|
|
def flatten_vertices(self):
|
|
return [coord for point in self.vertices for coord in point]
|
|
|
|
def on_v(self, params):
|
|
r, g, b = None, None, None
|
|
w = 1.0
|
|
if len(params) == 3:
|
|
x, y, z = [float(param) for param in params]
|
|
if len(params) == 4:
|
|
x, y, z, w = [float(param) for param in params]
|
|
if len(params) == 6:
|
|
x, y, z, r, g, b = [float(param) for param in params]
|
|
self.vertices.append((x, z, y))
|
|
if r is None or g is None or b is None:
|
|
self.vertex_colors.append(None)
|
|
else:
|
|
self.vertex_colors.append((r, g, b))
|
|
|
|
def on_l(self, params):
|
|
# TODO: handle lines
|
|
pass
|
|
|
|
def on_f(self, params):
|
|
indices = []
|
|
for param in params:
|
|
# TODO: use texture coordinate index / use vertex normal index?
|
|
v_index = int(param.split('/')[0])
|
|
# If an index is positive then it refers to the offset in that vertex list, starting at 1.
|
|
# If an index is negative then it relatively refers to the end of the vertex list, -1 referring to the last element.
|
|
if v_index > 0:
|
|
v_index -= 1
|
|
indices.append(v_index)
|
|
|
|
self.faces.append({
|
|
'indices': indices,
|
|
'object': self.crt_object,
|
|
'mtl': self.crt_mtl
|
|
})
|
|
|
|
def post_process(self):
|
|
# Step 1: group into object_id/material_id/[faces_with_global_indices]
|
|
objects = {}
|
|
for face in self.faces:
|
|
if face['object'] not in objects:
|
|
objects[face['object']] = {}
|
|
obj = objects[face['object']]
|
|
if face['mtl'] not in obj:
|
|
obj[face['mtl']] = []
|
|
obj[face['mtl']].append(face['indices'])
|
|
|
|
# Step 2: construct final structure: object_id / [{material, local_vertices, vertex_colors, faces_with_local_indices}]
|
|
for object in objects:
|
|
self.objects[object] = []
|
|
for mtl in objects[object].keys():
|
|
material = self.mtl_files.get_material(mtl)
|
|
vertices = []
|
|
vertex_colors = []
|
|
faces = []
|
|
|
|
v_global2local_id = {}
|
|
for face in objects[object][mtl]:
|
|
for global_v in face:
|
|
if global_v not in v_global2local_id:
|
|
v_global2local_id[global_v] = len(vertices)
|
|
vertices.append(self.vertices[global_v])
|
|
vertex_colors.append(self.vertex_colors[global_v])
|
|
|
|
faces.append([v_global2local_id[global_id] for global_id in face])
|
|
|
|
self.objects[object].append({
|
|
'material': material,
|
|
'vertices': vertices,
|
|
'vertex_colors': vertex_colors,
|
|
'faces': faces
|
|
})
|