Merge pull request #438 from s4Ys369/revert-434-patch-1
[sm64pc.git] / c2obj.py
blob508a51ec994a15f64c96a03a6aaff555d414ac49
1 """
2 This module attempts to parse the ``model.inc.c`` files and extract the
3 3D models within as standard Wavefront OBJ files.
5 Example:
6 Specify the path to the ``.inc.c`` file and a directory where to save
7 the extracted ``.obj`` files.
9 $ python c2obj.py ./actors/mario/model.inc.c ./actors/mario/obj/
11 This is a work in progress and it currently has some serious limitations:
12 * It only extracts geometry information, so no textures or any other info
13 * It makes assumptions about the layout of the code in the C source
14 * It hasn't been properly tested.
16 """
18 def parse(filename, output_directory):
19 from os import path, mkdir
21 if not path.isdir(output_directory):
22 try:
23 mkdir(output_directory)
24 except OSError:
25 print(f'Could not use output directory {output_directory}.')
27 vtx_def = 'static const Vtx '
28 vtx_data = {}
29 reading_vtx = False
30 current_vtx_name = ''
31 current_vtx_data = []
32 current_vtx_vertices = 0
34 gfx_def = 'const Gfx '
35 reading_gfx = False
36 current_gfx_vertices = 0
37 current_gfx_faces = 0
38 insert_vert_call = 'gsSPVertex('
39 insert_1tri_call = 'gsSP1Triangle('
40 insert_2tri_call = 'gsSP2Triangles('
41 gfx_count = 0
43 end_of_block = '};'
45 with open(filename, 'r') as f:
46 for line in f:
47 line = line.strip()
49 if line.startswith(vtx_def):
50 vtx_name = line.split(' ')[3][:-2]
51 current_vtx_name = vtx_name
52 current_vtx_data = []
53 reading_vtx = True
54 continue
56 if line.startswith(gfx_def):
57 from datetime import datetime
59 current_gfx_name = line.split(' ')[2][:-2]
60 current_gfx_file = open(path.join(output_directory, current_gfx_name + '.obj'), 'w')
61 current_gfx_file.write("# Armando Arredondo's SM64 Wavefront OBJ Geometry Converter\n")
62 current_gfx_file.write('# File Created: {}\n\n'.format(datetime.now()))
63 reading_gfx = True
64 continue
66 if line == end_of_block:
67 if reading_vtx:
68 vtx_data[current_vtx_name] = current_vtx_data
69 reading_vtx = False
71 elif reading_gfx:
72 current_gfx_file.write(f'# {current_gfx_faces} faces\n\n')
73 current_gfx_file.close()
74 current_gfx_vertices = 0
75 reading_gfx = False
76 gfx_count += 1
78 continue
80 if reading_vtx:
81 line = line.replace('{', '[').replace('}', ']')
82 tri = eval(line[:-1])[0]
83 current_vtx_data.append(tri)
84 continue
86 if reading_gfx:
87 if line.startswith(insert_vert_call):
88 args = line[len(insert_vert_call):].split(',')
89 current_vtx_name = args[0]
91 if current_gfx_vertices > 0:
92 current_gfx_file.write(f'# {current_gfx_faces} faces\n\n')
94 current_gfx_faces = 0
95 current_vtx_vertices = len(vtx_data[current_vtx_name])
96 current_gfx_vertices += current_vtx_vertices
98 current_gfx_file.write(f'#\n# object {current_vtx_name}\n#\n\n')
99 current_vtx_data = vtx_data[current_vtx_name]
100 for tri in current_vtx_data:
101 v = tri[0]
102 current_gfx_file.write('v {:.3f} {:.3f} {:.3f}\n'.format(*v))
103 current_gfx_file.write(f'# {current_vtx_vertices} vertices\n\n')
105 for tri in current_vtx_data:
106 n = [_decode_normal(u) for u in tri[3][:3]]
107 current_gfx_file.write('vn {:.3f} {:.3f} {:.3f}\n'.format(*n))
108 current_gfx_file.write(f'# {current_vtx_vertices} vertex normals\n\n')
110 current_gfx_file.write(f'g {current_vtx_name}\n\n')
112 elif line.startswith(insert_2tri_call):
113 args = line[len(insert_2tri_call):].split(',')
114 correction = current_gfx_vertices - current_vtx_vertices + 1
115 indexes = [eval(args[i]) + correction for i in [0, 1, 2, 4, 5, 6]]
116 current_gfx_file.write('f {0}//{0} {1}//{1} {2}//{2}\n'.format(*indexes[:3]))
117 current_gfx_file.write('f {0}//{0} {1}//{1} {2}//{2}\n'.format(*indexes[3:]))
118 current_gfx_faces += 2
120 elif line.startswith(insert_1tri_call):
121 args = line[len(insert_1tri_call):].split(',')
122 correction = current_gfx_vertices - current_vtx_vertices + 1
123 indexes = [eval(args[i]) + correction for i in [0, 1, 2]]
124 current_gfx_file.write('f {0}//{0} {1}//{1} {2}//{2}\n'.format(*indexes))
125 current_gfx_faces += 1
127 continue
129 print(f'{gfx_count} models extracted.')
131 def _decode_normal(x):
132 y = x if x <= 127 else x - 255
133 return y / 127
135 if __name__ == "__main__":
136 import argparse
137 parser = argparse.ArgumentParser()
138 parser.add_argument('filename', help = 'filename of the .inc.c source file')
139 parser.add_argument('output_directory', help = 'directory where to put the extracted .obj files')
140 args = parser.parse_args()
141 parse(args.filename, args.output_directory)