fix [#33615] bl_info (2,6,5,0) vs. (2,65,0) ?
[blender-addons-contrib.git] / io_scene_cod / export_xmodel.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 """
22 Blender-CoD: Blender Add-On for Call of Duty modding
23 Version: alpha 3
24
25 Copyright (c) 2011 CoDEmanX, Flybynyt -- blender-cod@online.de
26
27 http://code.google.com/p/blender-cod/
28
29 """
30
31 import bpy
32 import os
33 from datetime import datetime
34
35 def save(self, context, filepath="",
36          use_version='6',
37          use_selection=False,
38          use_apply_modifiers=True,
39          use_armature=True,
40          use_vertex_colors=True,
41          use_vertex_colors_alpha=False,
42          use_vertex_cleanup=False,
43          use_armature_pose=False,
44          use_frame_start=1,
45          use_frame_end=250,
46          use_weight_min=False,
47          use_weight_min_threshold=0.010097):
48
49     # There's no context object right after object deletion, need to set one
50     if context.object:
51         last_mode = context.object.mode
52     else:
53         last_mode = 'OBJECT'
54
55         for ob in bpy.data.objects:
56             if ob.type == 'MESH':
57                 context.scene.objects.active = ob
58                 break
59         else:
60             return "No mesh to export."
61
62     # HACK: Force an update, so that bone tree is properly sorted for hierarchy table export
63     bpy.ops.object.mode_set(mode='EDIT', toggle=False)
64     bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
65
66     # Remember frame to set it back after export
67     last_frame_current = context.scene.frame_current
68
69     # Disable Armature for Pose animation export, bone.tail_local not available for PoseBones
70     if use_armature and use_armature_pose:
71         use_armature = False
72
73     # Don't iterate for a single frame
74     if not use_armature_pose or (use_armature_pose and use_frame_start == use_frame_end):
75         context.scene.frame_set(use_frame_start)
76
77         result = _write(self, context, filepath,
78                         use_version,
79                         use_selection,
80                         use_apply_modifiers,
81                         use_armature,
82                         use_vertex_colors,
83                         use_vertex_colors_alpha,
84                         use_vertex_cleanup,
85                         use_armature_pose,
86                         use_weight_min,
87                         use_weight_min_threshold)
88     else:
89
90         if use_frame_start < use_frame_end:
91             frame_order = 1
92             frame_min = use_frame_start
93             frame_max = use_frame_end
94         else:
95             frame_order = -1
96             frame_min = use_frame_end
97             frame_max = use_frame_start
98
99         # String length of highest frame number for filename padding
100         frame_strlen = len(str(frame_max))
101
102         filepath_split = os.path.splitext(filepath)
103
104         for i_frame, frame in enumerate(range(use_frame_start,
105                                               use_frame_end + frame_order,
106                                               frame_order
107                                               ),
108                                         frame_min):
109
110             # Set frame for export
111             # Don't do it directly to frame_current, as to_mesh() won't use updated frame!
112             context.scene.frame_set(frame)
113
114             # Generate filename including padded frame number
115             filepath_frame = "%s_%.*i%s" % (filepath_split[0], frame_strlen, i_frame, filepath_split[1])
116
117             result = _write(self, context, filepath_frame,
118                             use_version,
119                             use_selection,
120                             use_apply_modifiers,
121                             use_armature,
122                             use_vertex_colors,
123                             use_vertex_colors_alpha,
124                             use_vertex_cleanup,
125                             use_armature_pose,
126                             use_weight_min,
127                             use_weight_min_threshold
128                             )
129
130             # Quit iteration on error
131             if result:
132                 context.scene.frame_set(last_frame_current)
133                 return result
134
135     # Set frame back
136     context.scene.frame_set(last_frame_current)
137
138     # Set mode back
139     bpy.ops.object.mode_set(mode=last_mode, toggle=False)
140
141     # Handle possible error result of single frame export
142     return result
143
144 def _write(self, context, filepath,
145            use_version,
146            use_selection,
147            use_apply_modifiers,
148            use_armature,
149            use_vertex_colors,
150            use_vertex_colors_alpha,
151            use_vertex_cleanup,
152            use_armature_pose,
153            use_weight_min,
154            use_weight_min_threshold):
155
156     num_verts = 0
157     num_verts_unique = 0
158     verts_unique = []
159     num_faces = 0
160     meshes = []
161     meshes_matrix = []
162     meshes_vgroup = []
163     objects = []
164     armature = None
165     bone_mapping = {}
166     materials = []
167
168     ob_count = 0
169     v_count = 0
170
171     # Check input objects, count them and convert mesh objects
172     for ob in bpy.data.objects:
173
174         # Take the first armature
175         if ob.type == 'ARMATURE' and use_armature and armature is None and len(ob.data.bones) > 0:
176             armature = ob
177             continue
178
179         if ob.type != 'MESH':
180             continue
181
182         # Skip meshes, which are unselected
183         if use_selection and not ob.select:
184             continue
185
186         # Set up modifiers whether to apply deformation or not
187         mod_states = []
188         for mod in ob.modifiers:
189             mod_states.append(mod.show_viewport)
190             if mod.type == 'ARMATURE':
191                 mod.show_viewport = mod.show_viewport and use_armature_pose
192             else:
193                 mod.show_viewport = mod.show_viewport and use_apply_modifiers
194
195         # to_mesh() applies enabled modifiers only
196         mesh = ob.to_mesh(scene=context.scene, apply_modifiers=True, settings='PREVIEW')
197
198         # Restore modifier settings
199         for i, mod in enumerate(ob.modifiers):
200             mod.show_viewport = mod_states[i]
201
202         # Skip invalid meshes
203         if len(mesh.vertices) < 3:
204             _skip_notice(ob.name, mesh.name, "Less than 3 vertices")
205             continue
206         if len(mesh.tessfaces) < 1:
207             _skip_notice(ob.name, mesh.name, "No faces")
208             continue
209         if len(ob.material_slots) < 1:
210             _skip_notice(ob.name, mesh.name, "No material")
211             continue
212         if not mesh.tessface_uv_textures:
213             _skip_notice(ob.name, mesh.name, "No UV texture, not unwrapped?")
214             continue
215
216         meshes.append(mesh)
217         meshes_matrix.append(ob.matrix_world)
218
219         if ob.vertex_groups:
220             meshes_vgroup.append(ob.vertex_groups)
221         else:
222             meshes_vgroup.append(None)
223
224         if use_vertex_cleanup:
225
226             # Retrieve verts which belong to a face
227             verts = []
228             for f in mesh.tessfaces:
229                 for v in f.vertices:
230                     verts.append(v)
231
232             # Uniquify & sort
233             keys = {}
234             for e in verts:
235                 keys[e] = 1
236             verts = list(keys.keys())
237
238         else:
239             verts = [v.index for v in mesh.vertices]
240
241         # Store vert sets, aligned to mesh objects
242         verts_unique.append(verts)
243
244         # As len(mesh.vertices) doesn't take unused verts into account, already count here
245         num_verts_unique += len(verts)
246
247         # Take quads into account!
248         for f in mesh.tessfaces:
249             if len(f.vertices) == 3:
250                 num_faces += 1
251             else:
252                 num_faces += 2
253
254         objects.append(ob.name)
255
256     if (num_verts or num_faces or len(objects)) == 0:
257         return "Nothing to export.\n" \
258                "Meshes must have at least:\n" \
259                "    3 vertices\n" \
260                "    1 face\n" \
261                "    1 material\n" \
262                "    UV mapping"
263
264     # There's valid data for export, create output file
265     try:
266         file = open(filepath, "w")
267     except IOError:
268         return "Could not open file for writing:\n%s" % filepath
269
270     # Write header
271     file.write("// XMODEL_EXPORT file in CoD model v%i format created with Blender v%s\n" \
272                % (int(use_version), bpy.app.version_string))
273
274     file.write("// Source file: %s\n" % bpy.data.filepath)
275     file.write("// Export time: %s\n\n" % datetime.now().strftime("%d-%b-%Y %H:%M:%S"))
276
277     if use_armature_pose:
278         file.write("// Posed model of frame %i\n\n" % bpy.context.scene.frame_current)
279
280     if use_weight_min:
281         file.write("// Minimum bone weight: %f\n\n" % use_weight_min_threshold)
282
283     file.write("MODEL\n")
284     file.write("VERSION %i\n" % int(use_version))
285
286     # Write armature data
287     if armature is None:
288
289         # Default rig
290         file.write("\nNUMBONES 1\n")
291         file.write("BONE 0 -1 \"tag_origin\"\n")
292
293         file.write("\nBONE 0\n")
294
295         if use_version == '5':
296             file.write("OFFSET 0.000000 0.000000 0.000000\n")
297             file.write("SCALE 1.000000 1.000000 1.000000\n")
298             file.write("X 1.000000 0.000000 0.000000\n")
299             file.write("Y 0.000000 1.000000 0.000000\n")
300             file.write("Z 0.000000 0.000000 1.000000\n")
301         else:
302             # Model format v6 has commas
303             file.write("OFFSET 0.000000, 0.000000, 0.000000\n")
304             file.write("SCALE 1.000000, 1.000000, 1.000000\n")
305             file.write("X 1.000000, 0.000000, 0.000000\n")
306             file.write("Y 0.000000, 1.000000, 0.000000\n")
307             file.write("Z 0.000000, 0.000000, 1.000000\n")
308
309     else:
310
311         # Either use posed armature bones for animation to model sequence export
312         if use_armature_pose:
313             bones = armature.pose.bones
314         # Or armature bones in rest pose for regular rigged models
315         else:
316             bones = armature.data.bones
317
318         file.write("\nNUMBONES %i\n" % len(bones))
319
320         # Get the armature object's orientation
321         a_matrix = armature.matrix_world
322
323         # Check for multiple roots, armature should have exactly one
324         roots = 0
325         for bone in bones:
326             if not bone.parent:
327                 roots += 1
328         if roots != 1:
329             warning_string = "Warning: %i root bones found in armature object '%s'\n" \
330                              % (roots, armature.name)
331             print(warning_string)
332             file.write("// %s" % warning_string)
333
334         # Look up table for bone indices
335         bone_table = [b.name for b in bones]
336
337         # Write bone hierarchy table and create bone_mapping array for later use (vertex weights)
338         for i, bone in enumerate(bones):
339
340             if bone.parent:
341                 try:
342                     bone_parent_index = bone_table.index(bone.parent.name)
343                 except (ValueError):
344                     bone_parent_index = 0
345                     file.write("// Warning: \"%s\" not found in bone table, binding to root...\n"
346                                % bone.parent.name)
347             else:
348                 bone_parent_index = -1
349
350             file.write("BONE %i %i \"%s\"\n" % (i, bone_parent_index, bone.name))
351             bone_mapping[bone.name] = i
352
353         # Write bone orientations
354         for i, bone in enumerate(bones):
355             file.write("\nBONE %i\n" % i)
356
357             # Using local tail for proper coordinates
358             b_tail = a_matrix * bone.tail_local
359
360             # TODO: Fix calculation/error: pose animation will use posebones, but they don't have tail_local!
361
362             # TODO: Fix rotation matrix calculation, calculation seems to be wrong...
363             #b_matrix = bone.matrix_local * a_matrix
364             #b_matrix = bone.matrix * a_matrix * bones[0].matrix.inverted()
365             #from mathutils import Matrix
366
367             # Is this the way to go? Or will it fix the root only, but mess up all other roll angles?
368             if i == 0:
369                 b_matrix = ((1,0,0),(0,1,0),(0,0,1))
370             else:
371                 b_matrix = a_matrix * bone.matrix_local
372                 #from mathutils import Matrix
373                 #b_matrix = bone.matrix_local * a_matrix * Matrix(((1,-0,0),(0,0,-1),(-0,1,0)))
374                 
375             if use_version == '5':
376                 file.write("OFFSET %.6f %.6f %.6f\n" % (b_tail[0], b_tail[1], b_tail[2]))
377                 file.write("SCALE 1.000000 1.000000 1.000000\n") # Is this even supported by CoD?
378                 file.write("X %.6f %.6f %.6f\n" % (b_matrix[0][0], b_matrix[1][0], b_matrix[2][0]))
379                 file.write("Y %.6f %.6f %.6f\n" % (b_matrix[0][1], b_matrix[1][1], b_matrix[2][1]))
380                 file.write("Z %.6f %.6f %.6f\n" % (b_matrix[0][2], b_matrix[1][2], b_matrix[2][2]))
381             else:
382                 file.write("OFFSET %.6f, %.6f, %.6f\n" % (b_tail[0], b_tail[1], b_tail[2]))
383                 file.write("SCALE 1.000000, 1.000000, 1.000000\n")
384                 file.write("X %.6f, %.6f, %.6f\n" % (b_matrix[0][0], b_matrix[1][0], b_matrix[2][0]))
385                 file.write("Y %.6f, %.6f, %.6f\n" % (b_matrix[0][1], b_matrix[1][1], b_matrix[2][1]))
386                 file.write("Z %.6f, %.6f, %.6f\n" % (b_matrix[0][2], b_matrix[1][2], b_matrix[2][2]))
387
388     # Write vertex data
389     file.write("\nNUMVERTS %i\n" % num_verts_unique)
390
391     for i, me in enumerate(meshes):
392
393         # Get the right object matrix for mesh
394         mesh_matrix = meshes_matrix[i]
395
396         # Get bone influences per vertex
397         if armature is not None and meshes_vgroup[i] is not None:
398
399             groupNames, vWeightList = meshNormalizedWeights(meshes_vgroup[i],
400                                                             me,
401                                                             use_weight_min,
402                                                             use_weight_min_threshold
403                                                             )
404             # Get bones by vertex_group names, bind to root if can't find one 
405             groupIndices = [bone_mapping.get(g, -1) for g in groupNames]
406
407             weight_group_list = []
408             for weights in vWeightList:
409                 weight_group_list.append(sorted(zip(weights, groupIndices), reverse=True))
410
411         # Use uniquified vert sets and count the verts
412         for i_vert, vert in enumerate(verts_unique[i]):
413             v = me.vertices[vert]
414
415             # Calculate global coords
416             x = mesh_matrix[0][0] * v.co[0] + \
417                 mesh_matrix[0][1] * v.co[1] + \
418                 mesh_matrix[0][2] * v.co[2] + \
419                 mesh_matrix[0][3]
420
421             y = mesh_matrix[1][0] * v.co[0] + \
422                 mesh_matrix[1][1] * v.co[1] + \
423                 mesh_matrix[1][2] * v.co[2] + \
424                 mesh_matrix[1][3]
425
426             z = mesh_matrix[2][0] * v.co[0] + \
427                 mesh_matrix[2][1] * v.co[1] + \
428                 mesh_matrix[2][2] * v.co[2] + \
429                 mesh_matrix[2][3]
430                 
431             #print("%.6f %.6f %.6f single calced xyz\n%.6f %.6f %.6f mat mult" % (x, y, z, ))
432
433             file.write("VERT %i\n" % (i_vert + v_count))
434
435             if use_version == '5':
436                 file.write("OFFSET %.6f %.6f %.6f\n" % (x, y, z))
437             else:
438                 file.write("OFFSET %.6f, %.6f, %.6f\n" % (x, y, z))
439
440             # Write bone influences
441             if armature is None or meshes_vgroup[i] is None:
442                 file.write("BONES 1\n")
443                 file.write("BONE 0 1.000000\n\n")
444             else:
445                 cache = ""
446                 c_bones = 0
447
448                 for weight, bone_index in weight_group_list[v.index]:
449                     if (use_weight_min and round(weight, 6) < use_weight_min_threshold) or \
450                        (not use_weight_min and round(weight, 6) == 0):
451                         # No (more) bones with enough weight, totalweight of 0 would lead to error
452                         break
453                     cache += "BONE %i %.6f\n" % (bone_index, weight)
454                     c_bones += 1
455
456                 if c_bones == 0:
457                     warning_string = "Warning: No bone influence found for vertex %i, binding to bone %i\n" \
458                                      % (v.index, bone_index)
459                     print(warning_string)
460                     file.write("// %s" % warning_string)
461                     file.write("BONES 1\n")
462                     file.write("BONE %i 0.000001\n\n" % bone_index) # HACK: Is a minimum weight a good idea?
463                 else:
464                     file.write("BONES %i\n%s\n" % (c_bones, cache))
465
466         v_count += len(verts_unique[i]);
467
468     # TODO: Find a better way to keep track of the vertex index?
469     v_count = 0
470
471     # Prepare material array
472     for me in meshes:
473         for f in me.tessfaces:
474             try:
475                 mat = me.materials[f.material_index]
476             except (IndexError):
477                 # Mesh has no material with this index
478                 # Note: material_index is never None (will be 0 instead)
479                 continue
480             else:
481                 if mat not in materials:
482                     materials.append(mat)
483
484     # Write face data
485     file.write("\nNUMFACES %i\n" % num_faces)
486
487     for i_me, me in enumerate(meshes):
488
489         #file.write("// Verts:\n%s\n" % list(enumerate(verts_unique[i_me])))
490
491         for f in me.tessfaces:
492
493             try:
494                 mat = me.materials[f.material_index]
495
496             except (IndexError):
497                 mat_index = 0
498
499                 warning_string = "Warning: Assigned material with index %i not found, falling back to first\n" \
500                                   % f.material_index
501                 print(warning_string)
502                 file.write("// %s" % warning_string)
503
504             else:
505                 try:
506                     mat_index = materials.index(mat)
507
508                 except (ValueError):
509                     mat_index = 0
510
511                     warning_string = "Warning: Material \"%s\" not mapped, falling back to first\n" \
512                                       % mat.name
513                     print(warning_string)
514                     file.write("// %s" % warning_string)
515
516             # Support for vertex colors
517             if me.tessface_vertex_colors:
518                 col = me.tessface_vertex_colors.active.data[f.index]
519
520             # Automatic triangulation support
521             f_v_orig = [v for v in enumerate(f.vertices)]
522
523             if len(f_v_orig) == 3:
524                 f_v_iter = (f_v_orig[2], f_v_orig[1], f_v_orig[0]), # HACK: trailing comma to force a tuple
525             else:
526                 f_v_iter = (f_v_orig[2], f_v_orig[1], f_v_orig[0]), (f_v_orig[3], f_v_orig[2], f_v_orig[0])
527
528             for iter in f_v_iter:
529
530                 # TODO: Test material# export (v5 correct?)
531                 if use_version == '5':
532                     file.write("TRI %i %i 0 1\n" % (ob_count, mat_index))
533                 else:
534                     file.write("TRI %i %i 0 0\n" % (ob_count, mat_index))
535
536                 for vi, v in iter:
537
538                     no = me.vertices[v].normal # Invert? Orientation seems to have no effect...
539
540                     uv = me.tessface_uv_textures.active
541                     uv1 = uv.data[f.index].uv[vi][0]
542                     uv2 = 1 - uv.data[f.index].uv[vi][1] # Flip!
543
544                     #if 0 > uv1 > 1 
545                     # TODO: Warn if accidentally tiling ( uv <0 or >1 )
546
547                     # Remap vert indices used by face
548                     if use_vertex_cleanup:
549                         vert_new = verts_unique[i_me].index(v) + v_count
550                         #file.write("// %i (%i) --> %i\n" % (v+v_count, v, vert_new))
551                     else:
552                         vert_new = v + v_count
553
554                     if use_version == '5':
555                         file.write("VERT %i %.6f %.6f %.6f %.6f %.6f\n" \
556                                    % (vert_new, uv1, uv2, no[0], no[1], no[2]))
557                     else:
558                         file.write("VERT %i\n" % vert_new)
559                         file.write("NORMAL %.6f %.6f %.6f\n" % (no[0], no[1], no[2]))
560
561                         if me.tessface_vertex_colors and use_vertex_colors:
562
563                             if vi == 0:
564                                 c = col.color1
565                             elif vi == 1:
566                                 c = col.color2
567                             elif vi == 2:
568                                 c = col.color3
569                             else:
570                                 c = col.color4
571
572                             if use_vertex_colors_alpha:
573
574                                 # Turn RGB into grayscale (luminance conversion)
575                                 c_lum = c[0] * 0.3 + c[1] * 0.59 + c[2] * 0.11
576                                 file.write("COLOR 1.000000 1.000000 1.000000 %.6f\n" % c_lum)
577                             else:
578                                 file.write("COLOR %.6f %.6f %.6f 1.000000\n" % (c[0], c[1], c[2]))
579
580                         else:
581                             file.write("COLOR 1.000000 1.000000 1.000000 1.000000\n")
582
583                         file.write("UV 1 %.6f %.6f\n" % (uv1, uv2))
584
585         # Note: Face types (tris/quads) have nothing to do with vert indices!
586         if use_vertex_cleanup:
587             v_count += len(verts_unique[i_me])
588         else:
589             v_count += len(me.vertices)
590
591         ob_count += 1
592
593     # Write object data
594     file.write("\nNUMOBJECTS %i\n" % len(objects))
595
596     for i_ob, ob in enumerate(objects):
597         file.write("OBJECT %i \"%s\"\n" % (i_ob, ob))
598
599     # Static material string
600     material_string = ("COLOR 0.000000 0.000000 0.000000 1.000000\n"
601                        "TRANSPARENCY 0.000000 0.000000 0.000000 1.000000\n"
602                        "AMBIENTCOLOR 0.000000 0.000000 0.000000 1.000000\n"
603                        "INCANDESCENCE 0.000000 0.000000 0.000000 1.000000\n"
604                        "COEFFS 0.800000 0.000000\n"
605                        "GLOW 0.000000 0\n"
606                        "REFRACTIVE 6 1.000000\n"
607                        "SPECULARCOLOR -1.000000 -1.000000 -1.000000 1.000000\n"
608                        "REFLECTIVECOLOR -1.000000 -1.000000 -1.000000 1.000000\n"
609                        "REFLECTIVE -1 -1.000000\n"
610                        "BLINN -1.000000 -1.000000\n"
611                        "PHONG -1.000000\n\n"
612                        )
613
614     if len(materials) > 0:
615         file.write("\nNUMMATERIALS %i\n" % len(materials))
616
617         for i_mat, mat in enumerate(materials):
618
619             try:
620                 for i_ts, ts in enumerate(mat.texture_slots):
621
622                     # Skip empty slots and disabled textures
623                     if not ts or not mat.use_textures[i_ts]:
624                         continue
625
626                     # Image type and Color map? If yes, add to material array and index
627                     if ts.texture.type == 'IMAGE' and ts.use_map_color_diffuse:
628
629                         # Pick filename of the first color map
630                         imagepath = ts.texture.image.filepath
631                         imagename = os.path.split(imagepath)[1]
632                         if len(imagename) == 0:
633                             imagename = "untitled"
634                         break
635                 else:
636                     raise(ValueError)
637
638             except:
639                 imagename = "no color diffuse map found"
640
641             # Material can be assigned and None
642             if mat:
643                 mat_name = mat.name
644                 mat_shader = mat.diffuse_shader.capitalize()
645             else:
646                 mat_name = "None"
647                 mat_shader = "Lambert"
648
649             if use_version == '5':
650                 file.write("MATERIAL %i \"%s\"\n" % (i_mat, imagename))
651                 # or is it mat.name@filename?
652             else:
653                 file.write("MATERIAL %i \"%s\" \"%s\" \"%s\"\n" % (
654                            i_mat,
655                            mat_name,
656                            mat_shader,
657                            imagename
658                            ))
659                 file.write(material_string)
660     else:
661         # Write a default material
662         # Should never happen, nothing to export / mesh without material exceptions already caught
663         file.write("\nNUMMATERIALS 1\n")
664         if use_version == '5':
665             file.write("MATERIAL 0 \"default.tga\"\n")
666         else:
667             file.write("MATERIAL 0 \"$default\" \"Lambert\" \"untitled\"\n")
668             file.write(material_string)
669
670     # Close to flush buffers!
671     file.close()
672
673     # Remove meshes, which were made by to_mesh()
674     for mesh in meshes:
675         mesh.user_clear()
676         bpy.data.meshes.remove(mesh)    
677
678     # Quit with no errors
679     return
680
681 # Taken from export_fbx.py by Campbell Barton
682 # Modified to accept vertex_groups directly instead of mesh object
683 def BPyMesh_meshWeight2List(vgroup, me):
684
685     """ Takes a mesh and return its group names and a list of lists, one list per vertex.
686     aligning the each vert list with the group names, each list contains float value for the weight.
687     These 2 lists can be modified and then used with list2MeshWeight to apply the changes.
688     """
689
690     # Clear the vert group.
691     groupNames = [g.name for g in vgroup]
692     len_groupNames = len(groupNames)
693
694     if not len_groupNames:
695         # no verts? return a vert aligned empty list
696         #return [[] for i in range(len(me.vertices))], []
697         return [], []
698
699     else:
700         vWeightList = [[0.0] * len_groupNames for i in range(len(me.vertices))]
701
702     for i, v in enumerate(me.vertices):
703         for g in v.groups:
704             # possible weights are out of range
705             index = g.group
706             if index < len_groupNames:
707                 vWeightList[i][index] = g.weight
708
709     return groupNames, vWeightList
710
711 def meshNormalizedWeights(vgroup, me, weight_min, weight_min_threshold):
712
713     groupNames, vWeightList = BPyMesh_meshWeight2List(vgroup, me)
714
715     if not groupNames:
716         return [], []
717
718     for vWeights in vWeightList:
719         tot = 0.0
720         for w in vWeights:
721             if weight_min and w < weight_min_threshold:
722                 w = 0.0
723             tot += w
724
725         if tot:
726             for j, w in enumerate(vWeights):
727                 vWeights[j] = w / tot
728
729     return groupNames, vWeightList
730
731 def _skip_notice(ob_name, mesh_name, notice):
732     print("\nSkipped object \"%s\" (mesh \"%s\"): %s" % (ob_name, mesh_name, notice))