2025-04-02 11:49:32 -05:00

189 lines
4.2 KiB
Python

from dataclasses import dataclass
import sys
import os
@dataclass(frozen=True)
class VertexPosition:
x: float
y: float
z: float
@dataclass(frozen=True)
class VertexNormal:
x: float
y: float
z: float
@dataclass(frozen=True)
class VertexTexture:
x: float
y: float
z: float
@dataclass(frozen=True)
class ObjectEvent:
name: str
@dataclass(frozen=True)
class FacePTN:
position: int
texture: int
normal: int
@dataclass
class Face:
ptn: list[FacePTN]
@dataclass
class Material:
name: str
@dataclass
class Object:
name: str
faces: list[Face]
material: Material
def __init__(self, name):
self.name = name
self.faces = []
self.material = None
@dataclass
class ObjFile:
position: list[VertexPosition]
normal: list[VertexNormal]
texture: list[VertexTexture]
objects: list[Object]
def __init__(self):
self.position = []
self.normal = []
self.texture = []
self.objects = []
def parse_float(s):
sign = -1 if s.startswith("-") else 1
s = s.removeprefix("-")
if '.' not in s:
return sign * int(s, 10)
i, f = s.split('.')
f_digits = len(f)
i = int(i, 10)
f = int(f, 10)
if 'ROUND_PRECISION' in os.environ:
max_digits = 4
if f_digits > max_digits:
f = int(f / (f_digits - max_digits))
f_digits = max_digits
f_10 = (f / 10 ** f_digits)
return sign * (i + f_10)
def parse_vertex(line, n, type):
assert len(line) == n
vs = [parse_float(line[i]) for i in range(n)]
if len(vs) < 3:
vs.append(0)
return type(*vs)
def parse_vertex_position(line):
return parse_vertex(line, 3, VertexPosition)
def parse_vertex_normal(line):
return parse_vertex(line, 3, VertexNormal)
def parse_vertex_texture(line):
return parse_vertex(line, 2, VertexTexture)
def parse_face_indices(indices):
assert "/" in indices
indices = indices.split("/")
assert len(indices) == 3, indices
def face_ix(s):
i = int(s, 10)
assert i >= 1
return i - 1
return FacePTN(*(face_ix(i) for i in indices))
def parse_face(line):
return Face([parse_face_indices(indices) for indices in line])
def parse_object_event(line):
assert len(line) == 1
name, = line
return ObjectEvent(name)
def parse_usemtl(line):
assert len(line) == 1
name, = line
return Material(name)
def parse_line(line):
t, *line = line.split(' ')
if t == '#':
return None
if t == 'usemtl':
return parse_usemtl(line)
if t == 'mtllib':
return None
if t == 'o':
return parse_object_event(line)
if t == 'v':
return parse_vertex_position(line)
if t == 'vn':
return parse_vertex_normal(line)
if t == 'vt':
return parse_vertex_texture(line)
if t == 'f':
return parse_face(line)
if t == 's':
# smooth shading
return None
assert False, (t, line)
def parse_obj_lines(lines):
file = ObjFile()
object = Object(None)
for line in lines:
x = parse_line(line)
if x is None:
continue
elif type(x) is VertexPosition:
file.position.append(x)
elif type(x) is VertexNormal:
file.normal.append(x)
elif type(x) is VertexTexture:
file.texture.append(x)
elif type(x) is ObjectEvent:
if object.faces:
assert object.name != None
file.objects.append(object)
object = Object(x.name.replace('-', '_').replace('.', '_'))
elif type(x) is Face:
object.faces.append(x)
elif type(x) is Material:
if object.material != None:
file.objects.append(object)
object = Object(object.name + "_mtl_" + x.name)
object.material = x
else:
assert False, x
if object.faces:
assert object.name != None
file.objects.append(object)
return file
def parse_obj_file(filename):
with open(filename, "r") as f:
lines = f.read().strip().split("\n")
file = parse_obj_lines(lines)
return file
if __name__ == "__main__":
file = parse_obj_file(sys.argv[1])
from pprint import pprint
pprint(file)