ya2 · news · projects · code · about

50e88866f10eb86240cdaf218360bcc962b51356
[pmachines.git] / ya2 / build / models.py
1 from logging import info
2 from os import system, walk, makedirs
3 from os.path import exists, dirname, join, basename, splitext
4 from sys import executable
5 from functools import reduce
6 from multiprocessing import Pool
7 from hashlib import md5
8 from shutil import copyfile, rmtree
9 from ya2.build.build import to_be_built
10
11
12 class ModelsBuilder():
13
14 def __init__(self, blend_path, cores):
15 self.__blend_path = blend_path
16 self.__cores = cores
17 self.__manifest = ModelsManifest()
18
19 def build(self):
20 self.__compute_files_to_build()
21 return self.__run_build()
22
23 def __compute_files_to_build(self):
24 self.__blend_files = []
25 for root, _, file_names in walk(self.__blend_path):
26 b = [f for f in file_names if f.endswith('.blend')]
27 if '/prototypes/' in root:
28 b = []
29 b = [join(root, f)
30 for f in b
31 if to_be_built(join(root, f), [join(root, f)])]
32 self.__blend_files += b
33
34 def __run_build(self):
35 with Pool(self.__cores) as p:
36 just_built = p.map(self.blend2bam, self.__blend_files)
37 p.close()
38 p.join()
39 just_built = [assets for b in just_built for assets in b]
40 self.__manifest.update(just_built)
41 return just_built
42
43 def blend2bam(self, file_path):
44 built = []
45 if to_be_built(self.gltf_name(file_path), [file_path]):
46 self.__clean_gltf_dir(file_path)
47 built += self.__blend2gltf(file_path)
48 built += self.__gltf2bam(file_path)
49 return built
50
51 def __clean_gltf_dir(self, file_path):
52 gltf_dir = dirname(file_path)
53 gltf_dir = gltf_dir.replace('assets/models/blend/', 'assets/models/gltf/')
54 rmtree(gltf_dir, ignore_errors=True)
55
56 @staticmethod
57 def gltf_name(file_path):
58 gltf_name = file_path.replace('assets/models/blend/', 'assets/models/gltf/')
59 return gltf_name.replace('.blend', '.gltf')
60
61 def __blend2gltf(self, file_path):
62 b2g = Blend2Gltf(file_path)
63 return b2g.export()
64
65 def __gltf2bam(self, file_path):
66 g2b = Gltf2Bam(file_path)
67 return g2b.export()
68
69
70 class ModelsManifest:
71
72 def __init__(self):
73 self.__lines = []
74 self.__manifest = []
75 self.__file_name = 'models_manifest.txt'
76
77 def update(self, just_built):
78 self.__just_built = just_built
79 self.__load()
80 self.__add_just_built()
81 self.__discard_old_entries()
82 self.__rewrite()
83
84 def __load(self):
85 if exists(self.__file_name):
86 with open(self.__file_name) as f: self.__lines = f.readlines()
87 for l in self.__lines: # line's e.g. assets/path/to/gltf_or_png.ext 68ced1
88 self.__process_line(l)
89
90 def __process_line(self, l):
91 line_splitted = l.split()
92 hash_value = line_splitted[-1]
93 file_name = ' '.join(line_splitted[:-1])
94 if file_name not in self.__just_built:
95 self.__manifest += [(file_name, hash_value)]
96
97 def __add_just_built(self):
98 for f in self.__just_built:
99 self.__manifest += [(f, md5(open(f, 'rb').read()).hexdigest())]
100
101 def __discard_old_entries(self):
102 self.__manifest = self.__manifest[-2048:]
103
104 def __rewrite(self):
105 with open(self.__file_name, 'w') as f:
106 f.write('\n'.join([' '.join(l) for l in self.__manifest]))
107
108
109 class Blend2Gltf:
110
111 def __init__(self, file_path):
112 self.__file_path = file_path
113
114 def export(self):
115 gltf_dir, file_name = dirname(self.__file_path), basename(self.__file_path)
116 gltf_dir = gltf_dir.replace('assets/models/blend/', 'assets/models/gltf/')
117 makedirs(gltf_dir, exist_ok=True)
118 file_path = splitext(gltf_dir + '/' + file_name)[0]
119 command = f'blender {self.__file_path} --background --python ya2/build/blend2gltf.py -- {file_path}.gltf'
120 system(command)
121 return [self.__file_path, ModelsBuilder.gltf_name(self.__file_path)]
122
123
124 class Gltf2Bam:
125
126 def __init__(self, file_path):
127 self.__gltf_name = (file_path[:-5] + 'gltf').replace('/blend/', '/gltf/', 1)
128 self.__bam_name = (file_path[:-5] + 'bam').replace('/blend/', '/bam/', 1)
129
130 def export(self):
131 self.__build_dds_from_jpg_and_png()
132 self.__rewrite_gltf()
133 return self.__export_bam()
134
135 def __build_dds_from_jpg_and_png(self):
136 with open(self.__gltf_name) as f: self.__lines = f.readlines()
137 self.__jpg_png_files = []
138 for l in self.__lines:
139 if ('.png' in l or '.jpg' in l) and '"uri"' in l:
140 r = l[l.index('"uri"') + 9:].rstrip(',\n"')
141 t = f'{dirname(self.__gltf_name)}/{r}'
142 self.__jpg_png_files += [t.replace('/models/blend/', '/models/gltf/', 1)]
143 self.__create_dds_files()
144
145 def __create_dds_files(self):
146 for d in self.__jpg_png_files:
147 t = d.replace('/gltf/', '/bam/', 1)
148 t = t.replace('.png', '.dds').replace('.jpg', '.dds')
149 makedirs(dirname(t), exist_ok=True)
150 info(f'convert {d} {t}')
151 system(f'convert {d} {t}')
152
153 def __rewrite_gltf(self):
154 def r(lin):
155 e = [('.png', '.dds'), ('.jpg', '.dds'),
156 ('/png', '/dds'), ('/jpg', '/dds')]
157 return reduce(lambda s, p: s.replace(*p), e, lin)
158 with open(self.__gltf_name, 'w') as f:
159 f.write(''.join([r(line) for line in self.__lines]))
160
161 def __export_bam(self):
162 makedirs(dirname(self.__bam_name), exist_ok=True)
163 if to_be_built(self.__bam_name, self.__jpg_png_files):
164 venv_path = dirname(executable)
165 gltf2bam_path = join(venv_path, 'gltf2bam')
166 system(f'{gltf2bam_path} {self.__gltf_name} {self.__bam_name}')
167 return [self.__bam_name] + self.__jpg_png_files