211 lines
5.0 KiB
Python
211 lines
5.0 KiB
Python
from dataclasses import dataclass
|
|
import sys
|
|
import os
|
|
|
|
@dataclass(frozen=True)
|
|
class FixedPoint:
|
|
s: int # sign
|
|
w: int # whole
|
|
f: int # fraction
|
|
b: int # base
|
|
|
|
def to_string(self):
|
|
s_s = "-" if self.s < 0 else ""
|
|
w_s = f"{self.w}"
|
|
f_s = f"{self.f}".rjust(self.b, '0')
|
|
return f"{s_s}{w_s}.{f_s}"
|
|
|
|
@dataclass(frozen=True)
|
|
class VertexPosition:
|
|
x: FixedPoint
|
|
y: FixedPoint
|
|
z: FixedPoint
|
|
|
|
@dataclass(frozen=True)
|
|
class VertexNormal:
|
|
x: FixedPoint
|
|
y: FixedPoint
|
|
z: FixedPoint
|
|
|
|
@dataclass(frozen=True)
|
|
class VertexTexture:
|
|
x: FixedPoint
|
|
y: FixedPoint
|
|
|
|
@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 Line:
|
|
a: int
|
|
b: int
|
|
|
|
@dataclass
|
|
class Material:
|
|
name: str
|
|
|
|
@dataclass
|
|
class Object:
|
|
name: str
|
|
faces: list[Face]
|
|
lines: list[Line]
|
|
material: Material
|
|
|
|
def __init__(self, name, faces=None, lines=None, material=None):
|
|
self.name = name
|
|
self.faces = [] if faces is None else faces
|
|
self.lines = [] if lines is None else lines
|
|
self.material = material
|
|
|
|
@dataclass
|
|
class ObjFile:
|
|
position: list[VertexPosition]
|
|
normal: list[VertexNormal]
|
|
texture: list[VertexTexture]
|
|
objects: list[Object]
|
|
|
|
def __init__(self, position=None, normal=None, texture=None, objects=None):
|
|
self.position = [] if position is None else position
|
|
self.normal = [] if normal is None else normal
|
|
self.texture = [] if texture is None else texture
|
|
self.objects = [] if objects is None else 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)
|
|
|
|
return FixedPoint(sign, i, f, f_digits)
|
|
|
|
def parse_vertex(line, n, type):
|
|
assert len(line) == n
|
|
vs = [parse_float(line[i]) for i in range(n)]
|
|
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
|
|
name = name.replace(".", "_").replace("-", "_")
|
|
return ObjectEvent(name)
|
|
|
|
def parse_usemtl(line):
|
|
assert len(line) == 1
|
|
name, = line
|
|
return Material(name)
|
|
|
|
def parse_polyline(line):
|
|
assert len(line) == 2
|
|
a, b = line
|
|
return Line(int(a, 10) - 1,
|
|
int(b, 10) - 1)
|
|
|
|
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
|
|
if t == 'l':
|
|
# polyline
|
|
return parse_polyline(line)
|
|
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
|
|
elif type(x) is Line:
|
|
object.lines.append(x)
|
|
else:
|
|
assert False, x
|
|
if object.faces or object.lines:
|
|
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)
|