]> git.tdb.fi Git - libs/gl.git/blob - blender/io_mspgl/export_mesh.py
Add a hidden option to disable progress meter
[libs/gl.git] / blender / io_mspgl / export_mesh.py
1 import itertools
2 import bpy
3
4 class VertexCache:
5         def __init__(self, size):
6                 self.size = size
7                 self.slots = [-1]*self.size
8
9         def fetch(self, v):
10                 hit = v.index in self.slots
11                 if hit:
12                         self.slots.remove(v.index)
13                 self.slots.append(v.index)
14                 if not hit:
15                         del self.slots[0]
16                 return hit
17
18         def fetch_strip(self, strip):
19                 hits = 0
20                 for v in strip:
21                         if self.fetch(v):
22                                 hits += 1
23                 return hits
24
25         def test_strip(self, strip):
26                 hits = 0
27                 for i in range(len(strip)):
28                         if i>=self.size:
29                                 break
30                         if strip[i].index in self.slots[i:]:
31                                 hits += 1
32                 return hits
33
34
35 class MeshExporter:
36         def __init__(self):
37                 self.show_progress = True
38                 self.use_strips = True
39                 self.use_degen_tris = False
40                 self.max_strip_len = 1024
41                 self.optimize_cache = True
42                 self.cache_size = 64
43                 self.export_lines = False
44                 self.export_uv = "UNIT0"
45                 self.tbn_vecs = False
46                 self.tbn_uvtex = ""
47                 self.compound = False
48                 self.material_tex = False
49                 self.smoothing = "MSPGL"
50                 self.export_groups = False
51                 self.max_groups = 2
52
53         def stripify(self, mesh, progress = None):
54                 for f in mesh.faces:
55                         f.flag = False
56
57                 faces_done = 0
58                 strips = []
59                 loose = []
60
61                 cache = None
62                 if self.optimize_cache:
63                         cache = VertexCache(self.cache_size)
64
65                 island = []
66                 island_strips = []
67                 while 1:
68                         if not island:
69                                 # No current island; find any unused face to start from
70                                 queue = []
71                                 for f in mesh.faces:
72                                         if not f.flag:
73                                                 f.flag = True
74                                                 queue.append(f)
75                                                 break
76
77                                 if not queue:
78                                         break
79
80                                 # Find all faces connected to the first one
81                                 while queue:
82                                         face = queue.pop(0)
83                                         island.append(face)
84
85                                         for n in f.get_neighbors():
86                                                 if not n.flag:
87                                                         n.flag = True
88                                                         queue.append(n)
89
90                                 # Unflag the island for the next phase
91                                 for f in island:
92                                         f.flag = False
93
94                         # Find an unused face with as few unused neighbors as possible, but
95                         # at least one.  This heuristic gives a preference to faces in corners
96                         # or along borders of a non-closed island.
97                         best = 5
98                         face = None
99                         for f in island:
100                                 if f.flag:
101                                         continue
102
103                                 score = sum(not n.flag for n in f.get_neighbors())
104                                 if score>0 and score<best:
105                                         face = f
106                                         best = score
107
108                         if face:
109                                 # Create a strip starting from the face.  This will flag the faces.
110                                 strip = mesh.create_strip(face, self.max_strip_len)
111                                 if strip:
112                                         island_strips.append(strip)
113                                 else:
114                                         face.flag = True
115                         else:
116                                 # Couldn't find a candidate face for starting a strip, so we're
117                                 # done with this island
118                                 while island_strips:
119                                         best = 0
120                                         if cache:
121                                                 # Find the strip that benefits the most from the current
122                                                 # contents of the vertex cache
123                                                 best_hits = 0
124                                                 for i in range(len(island_strips)):
125                                                         hits = cache.test_strip(island_strips[i])
126                                                         if hits>best_hits:
127                                                                 best = i
128                                                                 best_hits = hits
129
130                                         strip = island_strips.pop(best)
131                                         strips.append(strip)
132
133                                         if cache:
134                                                 cache.fetch_strip(strip)
135
136                                 faces_done += len(island)
137                                 if progress:
138                                         progress.set_progress(float(faces_done)/len(mesh.faces))
139
140                                 # Collect any faces that weren't used in strips
141                                 loose += [f for f in island if not f.flag]
142                                 for f in island:
143                                         f.flag = True
144
145                                 island = []
146                                 island_strips = []
147
148                 if cache:
149                         cache = VertexCache(self.cache_size)
150                         total_hits = 0
151
152                 if self.use_degen_tris and strips:
153                         big_strip = []
154
155                         for s in strips:
156                                 if big_strip:
157                                         # Generate glue elements, ensuring that the next strip begins at
158                                         # an even position
159                                         glue = [big_strip[-1], s[0]]
160                                         if len(big_strip)%2:
161                                                 glue += [s[0]]
162
163                                         big_strip += glue
164                                         if cache:
165                                                 total_hits += cache.fetch_strip(glue)
166
167                                 big_strip += s
168                                 if cache:
169                                         total_hits += cache.fetch_strip(s)
170
171                         for f in loose:
172                                 # Add loose faces to the end.  This wastes space, using five
173                                 # elements per triangle and six elements per quad.
174                                 if len(big_strip)%2:
175                                         order = (-1, -2, 0, 1)
176                                 else:
177                                         order = (0, 1, -1, -2)
178                                 vertices = [f.vertices[i] for i in order[:len(f.vertices)]]
179
180                                 if big_strip:
181                                         glue = [big_strip[-1], vertices[0]]
182                                         big_strip += glue
183                                         if cache:
184                                                 total_hits += cache.fetch_strip(glue)
185
186                                 big_strip += vertices
187                                 if cache:
188                                         total_hits += cache.fetch_strip(vertices)
189
190                         strips = [big_strip]
191                         loose = []
192
193                 return strips, loose
194
195         def export(self, context, out_file, objs=None, progress=None):
196                 if self.compound:
197                         if objs is None:
198                                 objs = context.selected_objects
199                         check = objs
200                         while check:
201                                 children = []
202                                 for o in check:
203                                         for c in o.children:
204                                                 if c.compound:
205                                                         children.append(c)
206                                 objs += children
207                                 check = children
208                 elif objs is None:
209                         objs = [context.active_object]
210
211                 if not objs:
212                         raise Exception("Nothing to export")
213                 for o in objs:
214                         if o.type!="MESH":
215                                 raise Exception("Can only export Mesh data")
216
217                 from .mesh import Mesh
218                 from .util import Progress
219
220                 if self.show_progress:
221                         if not progress:
222                                 progress = Progress(context)
223                         progress.set_task("Preparing", 0.0, 0.0)
224                 else:
225                         progress = None
226
227                 mesh = None
228                 bmeshes = []
229                 for o in objs:
230                         bmesh = o.to_mesh(context.scene, True, "PREVIEW")
231                         bmeshes.append(bmesh)
232                         if not mesh:
233                                 mesh = Mesh(bmesh)
234                         else:
235                                 mesh.splice(Mesh(bmesh))
236
237                 if progress:
238                         progress.set_task("Smoothing", 0.05, 0.35)
239                 if self.smoothing=="NONE":
240                         mesh.flatten_faces()
241                 mesh.split_smooth(progress)
242
243                 if self.smoothing!="BLENDER":
244                         mesh.compute_normals()
245
246                 if self.export_groups:
247                         mesh.sort_vertex_groups(self.max_groups)
248
249                         # Create a mapping from vertex group indices to bone indices
250                         group_index_map = dict((i, i) for i in range(len(objs[0].vertex_groups)))
251                         if objs[0].parent and objs[0].parent.type=="ARMATURE":
252                                 armature = objs[0].parent.data
253                                 bone_indices = dict((armature.bones[i].name, i) for i in range(len(armature.bones)))
254                                 for g in objs[0].vertex_groups:
255                                         if g.name in bone_indices:
256                                                 group_index_map[g.index] = bone_indices[g.name]
257
258                 if self.material_tex and mesh.materials:
259                         mesh.generate_material_uv()
260
261                 texunits = []
262                 force_unit0 = False
263                 if mesh.uv_layers and (self.export_uv!="NONE" or self.material_tex):
264                         # Figure out which UV layers to export
265                         if self.export_uv=="ALL":
266                                 texunits = range(len(mesh.uv_layers))
267                         elif self.material_tex:
268                                 # The material UV layer is always the last one
269                                 texunits = [len(mesh.uv_layers)-1]
270                                 force_unit0 = True
271                         else:
272                                 for i, u in enumerate(mesh.uv_layers):
273                                         if u.unit==0:
274                                                 texunits = [i]
275                                                 break
276                         texunits = [(i, mesh.uv_layers[i]) for i in texunits]
277                         texunits = [u for u in texunits if not u[1].hidden]
278
279                         if self.tbn_vecs:
280                                 # TBN coordinates must be generated before vertices are split by any other layer
281                                 uv_names = [u.name for i, u in texunits]
282                                 if self.tbn_uvtex in uv_names:
283                                         tbn_index = uv_names.index(self.tbn_uvtex)
284                                         unit = texunits[tbn_index]
285                                         del texunits[tbn_index]
286                                         texunits.insert(0, unit)
287
288                         for i, u in texunits:
289                                 if progress:
290                                         progress.set_task("Splitting UVs", 0.35+0.3*i/len(texunits), 0.35+0.3*(i+1)/len(texunits))
291                                 mesh.split_uv(i, progress)
292                                 if self.tbn_vecs and u.name==self.tbn_uvtex:
293                                         mesh.compute_uv()
294                                         mesh.compute_tbn(i)
295
296                         mesh.compute_uv()
297
298                 strips = []
299                 loose = mesh.faces
300                 if self.use_strips:
301                         if progress:
302                                 progress.set_task("Creating strips", 0.65, 0.95)
303                         strips, loose = self.stripify(mesh, progress)
304
305                 if progress:
306                         progress.set_task("Writing file", 0.95, 1.0)
307
308                 from .outfile import open_output
309                 out_file = open_output(out_file)
310
311                 fmt = ["NORMAL3"]
312                 if texunits:
313                         for i, u in texunits:
314                                 if u.unit==0 or force_unit0:
315                                         fmt.append("TEXCOORD2")
316                                 else:
317                                         fmt.append("TEXCOORD2_%d"%u.unit)
318                         if self.tbn_vecs:
319                                 fmt += ["TANGENT3", "BINORMAL3"]
320                 if self.export_groups:
321                         fmt.append("ATTRIB%d_5"%(self.max_groups*2))
322                 fmt.append("VERTEX3")
323                 out_file.begin("vertices", *fmt)
324                 normal = None
325                 uvs = {}
326                 tan = None
327                 bino = None
328                 group = None
329                 for v in mesh.vertices:
330                         if v.normal!=normal:
331                                 out_file.write("normal3", *v.normal)
332                                 normal = v.normal
333                         for i, u in texunits:
334                                 if v.uvs[i]!=uvs.get(i):
335                                         if u.unit==0 or force_unit0:
336                                                 out_file.write("texcoord2", *v.uvs[i])
337                                         else:
338                                                 out_file.write("multitexcoord2", u.unit, *v.uvs[i])
339                                         uvs[i] = v.uvs[i]
340                         if self.tbn_vecs:
341                                 if v.tan!=tan:
342                                         out_file.write("tangent3", *v.tan)
343                                         tan = v.tan
344                                 if v.bino!=bino:
345                                         out_file.write("binormal3", *v.bino)
346                                         bino = v.bino
347                         if self.export_groups:
348                                 group_attr = [(group_index_map[g.group], g.weight*v.group_weight_scale) for g in v.groups[:self.max_groups]]
349                                 while len(group_attr)<self.max_groups:
350                                         group_attr.append((0, 0.0))
351                                 group_attr = list(itertools.chain(*group_attr))
352                                 if group_attr!=group:
353                                         out_file.write("attrib%d"%len(group_attr), 5, *group_attr)
354                                         group = group_attr
355                         out_file.write("vertex3", *v.co)
356                 out_file.end()
357                 for s in strips:
358                         out_file.begin("batch", "TRIANGLE_STRIP")
359                         indices = []
360                         n = 0
361                         for v in s:
362                                 indices.append(v.index)
363                                 if len(indices)>=32:
364                                         out_file.write("indices", *indices)
365                                         indices = []
366                         if indices:
367                                 out_file.write("indices", *indices)
368                         out_file.end()
369
370                 if loose:
371                         out_file.begin("batch", "TRIANGLES")
372                         for f in loose:
373                                 for i in range(2, len(f.vertices)):
374                                         out_file.write("indices", f.vertices[0].index, f.vertices[i-1].index, f.vertices[i].index)
375                         out_file.end()
376
377                 if self.export_lines and mesh.lines:
378                         out_file.begin("batch", "LINES")
379                         for l in mesh.lines:
380                                 out_file.write("indices", l.vertices[0].index, l.vertices[1].index)
381                         out_file.end()
382
383                 if progress:
384                         progress.set_task("Done", 1.0, 1.0)
385
386                 for m in bmeshes:
387                         bpy.data.meshes.remove(m)
388
389                 return mesh