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 21import os 22 23import bpy 24from mathutils import Matrix, Vector, Color 25from bpy_extras import io_utils, node_shader_utils 26 27from bpy_extras.wm_utils.progress_report import ( 28 ProgressReport, 29 ProgressReportSubstep, 30) 31 32 33def name_compat(name): 34 if name is None: 35 return 'None' 36 else: 37 return name.replace(' ', '_') 38 39 40def mesh_triangulate(me): 41 import bmesh 42 bm = bmesh.new() 43 bm.from_mesh(me) 44 bmesh.ops.triangulate(bm, faces=bm.faces) 45 bm.to_mesh(me) 46 bm.free() 47 48 49def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict): 50 source_dir = os.path.dirname(bpy.data.filepath) 51 dest_dir = os.path.dirname(filepath) 52 53 with open(filepath, "w", encoding="utf8", newline="\n") as f: 54 fw = f.write 55 56 fw('# Blender MTL File: %r\n' % (os.path.basename(bpy.data.filepath) or "None")) 57 fw('# Material Count: %i\n' % len(mtl_dict)) 58 59 mtl_dict_values = list(mtl_dict.values()) 60 mtl_dict_values.sort(key=lambda m: m[0]) 61 62 # Write material/image combinations we have used. 63 # Using mtl_dict.values() directly gives un-predictable order. 64 for mtl_mat_name, mat in mtl_dict_values: 65 # Get the Blender data for the material and the image. 66 # Having an image named None will make a bug, dont do it :) 67 68 fw('\nnewmtl %s\n' % mtl_mat_name) # Define a new material: matname_imgname 69 70 mat_wrap = node_shader_utils.PrincipledBSDFWrapper(mat) if mat else None 71 72 if mat_wrap: 73 use_mirror = mat_wrap.metallic != 0.0 74 use_transparency = mat_wrap.alpha != 1.0 75 76 # XXX Totally empirical conversion, trying to adapt it 77 # (from 1.0 - 0.0 Principled BSDF range to 0.0 - 900.0 OBJ specular exponent range)... 78 spec = (1.0 - mat_wrap.roughness) * 30 79 spec *= spec 80 fw('Ns %.6f\n' % spec) 81 82 # Ambient 83 if use_mirror: 84 fw('Ka %.6f %.6f %.6f\n' % (mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic)) 85 else: 86 fw('Ka %.6f %.6f %.6f\n' % (1.0, 1.0, 1.0)) 87 fw('Kd %.6f %.6f %.6f\n' % mat_wrap.base_color[:3]) # Diffuse 88 # XXX TODO Find a way to handle tint and diffuse color, in a consistent way with import... 89 fw('Ks %.6f %.6f %.6f\n' % (mat_wrap.specular, mat_wrap.specular, mat_wrap.specular)) # Specular 90 # Emission, not in original MTL standard but seems pretty common, see T45766. 91 emission_strength = mat_wrap.emission_strength 92 emission = [emission_strength * c for c in mat_wrap.emission_color[:3]] 93 fw('Ke %.6f %.6f %.6f\n' % tuple(emission)) 94 fw('Ni %.6f\n' % mat_wrap.ior) # Refraction index 95 fw('d %.6f\n' % mat_wrap.alpha) # Alpha (obj uses 'd' for dissolve) 96 97 # See http://en.wikipedia.org/wiki/Wavefront_.obj_file for whole list of values... 98 # Note that mapping is rather fuzzy sometimes, trying to do our best here. 99 if mat_wrap.specular == 0: 100 fw('illum 1\n') # no specular. 101 elif use_mirror: 102 if use_transparency: 103 fw('illum 6\n') # Reflection, Transparency, Ray trace 104 else: 105 fw('illum 3\n') # Reflection and Ray trace 106 elif use_transparency: 107 fw('illum 9\n') # 'Glass' transparency and no Ray trace reflection... fuzzy matching, but... 108 else: 109 fw('illum 2\n') # light normally 110 111 #### And now, the image textures... 112 image_map = { 113 "map_Kd": "base_color_texture", 114 "map_Ka": None, # ambient... 115 "map_Ks": "specular_texture", 116 "map_Ns": "roughness_texture", 117 "map_d": "alpha_texture", 118 "map_Tr": None, # transmission roughness? 119 "map_Bump": "normalmap_texture", 120 "disp": None, # displacement... 121 "refl": "metallic_texture", 122 "map_Ke": "emission_color_texture" if emission_strength != 0.0 else None, 123 } 124 125 for key, mat_wrap_key in sorted(image_map.items()): 126 if mat_wrap_key is None: 127 continue 128 tex_wrap = getattr(mat_wrap, mat_wrap_key, None) 129 if tex_wrap is None: 130 continue 131 image = tex_wrap.image 132 if image is None: 133 continue 134 135 filepath = io_utils.path_reference(image.filepath, source_dir, dest_dir, 136 path_mode, "", copy_set, image.library) 137 options = [] 138 if key == "map_Bump": 139 if mat_wrap.normalmap_strength != 1.0: 140 options.append('-bm %.6f' % mat_wrap.normalmap_strength) 141 if tex_wrap.translation != Vector((0.0, 0.0, 0.0)): 142 options.append('-o %.6f %.6f %.6f' % tex_wrap.translation[:]) 143 if tex_wrap.scale != Vector((1.0, 1.0, 1.0)): 144 options.append('-s %.6f %.6f %.6f' % tex_wrap.scale[:]) 145 if options: 146 fw('%s %s %s\n' % (key, " ".join(options), repr(filepath)[1:-1])) 147 else: 148 fw('%s %s\n' % (key, repr(filepath)[1:-1])) 149 150 else: 151 # Write a dummy material here? 152 fw('Ns 500\n') 153 fw('Ka 0.8 0.8 0.8\n') 154 fw('Kd 0.8 0.8 0.8\n') 155 fw('Ks 0.8 0.8 0.8\n') 156 fw('d 1\n') # No alpha 157 fw('illum 2\n') # light normally 158 159 160def test_nurbs_compat(ob): 161 if ob.type != 'CURVE': 162 return False 163 164 for nu in ob.data.splines: 165 if nu.point_count_v == 1 and nu.type != 'BEZIER': # not a surface and not bezier 166 return True 167 168 return False 169 170 171def write_nurb(fw, ob, ob_mat): 172 tot_verts = 0 173 cu = ob.data 174 175 # use negative indices 176 for nu in cu.splines: 177 if nu.type == 'POLY': 178 DEG_ORDER_U = 1 179 else: 180 DEG_ORDER_U = nu.order_u - 1 # odd but tested to be correct 181 182 if nu.type == 'BEZIER': 183 print("\tWarning, bezier curve:", ob.name, "only poly and nurbs curves supported") 184 continue 185 186 if nu.point_count_v > 1: 187 print("\tWarning, surface:", ob.name, "only poly and nurbs curves supported") 188 continue 189 190 if len(nu.points) <= DEG_ORDER_U: 191 print("\tWarning, order_u is lower then vert count, skipping:", ob.name) 192 continue 193 194 pt_num = 0 195 do_closed = nu.use_cyclic_u 196 do_endpoints = (do_closed == 0) and nu.use_endpoint_u 197 198 for pt in nu.points: 199 fw('v %.6f %.6f %.6f\n' % (ob_mat @ pt.co.to_3d())[:]) 200 pt_num += 1 201 tot_verts += pt_num 202 203 fw('g %s\n' % (name_compat(ob.name))) # name_compat(ob.getData(1)) could use the data name too 204 fw('cstype bspline\n') # not ideal, hard coded 205 fw('deg %d\n' % DEG_ORDER_U) # not used for curves but most files have it still 206 207 curve_ls = [-(i + 1) for i in range(pt_num)] 208 209 # 'curv' keyword 210 if do_closed: 211 if DEG_ORDER_U == 1: 212 pt_num += 1 213 curve_ls.append(-1) 214 else: 215 pt_num += DEG_ORDER_U 216 curve_ls = curve_ls + curve_ls[0:DEG_ORDER_U] 217 218 fw('curv 0.0 1.0 %s\n' % (" ".join([str(i) for i in curve_ls]))) # Blender has no U and V values for the curve 219 220 # 'parm' keyword 221 tot_parm = (DEG_ORDER_U + 1) + pt_num 222 tot_parm_div = float(tot_parm - 1) 223 parm_ls = [(i / tot_parm_div) for i in range(tot_parm)] 224 225 if do_endpoints: # end points, force param 226 for i in range(DEG_ORDER_U + 1): 227 parm_ls[i] = 0.0 228 parm_ls[-(1 + i)] = 1.0 229 230 fw("parm u %s\n" % " ".join(["%.6f" % i for i in parm_ls])) 231 232 fw('end\n') 233 234 return tot_verts 235 236 237def write_file(filepath, objects, depsgraph, scene, 238 EXPORT_TRI=False, 239 EXPORT_EDGES=False, 240 EXPORT_SMOOTH_GROUPS=False, 241 EXPORT_SMOOTH_GROUPS_BITFLAGS=False, 242 EXPORT_NORMALS=False, 243 EXPORT_UV=True, 244 EXPORT_MTL=True, 245 EXPORT_APPLY_MODIFIERS=True, 246 EXPORT_APPLY_MODIFIERS_RENDER=False, 247 EXPORT_BLEN_OBS=True, 248 EXPORT_GROUP_BY_OB=False, 249 EXPORT_GROUP_BY_MAT=False, 250 EXPORT_KEEP_VERT_ORDER=False, 251 EXPORT_POLYGROUPS=False, 252 EXPORT_CURVE_AS_NURBS=True, 253 EXPORT_GLOBAL_MATRIX=None, 254 EXPORT_PATH_MODE='AUTO', 255 progress=ProgressReport(), 256 ): 257 """ 258 Basic write function. The context and options must be already set 259 This can be accessed externaly 260 eg. 261 write( 'c:\\test\\foobar.obj', Blender.Object.GetSelected() ) # Using default options. 262 """ 263 if EXPORT_GLOBAL_MATRIX is None: 264 EXPORT_GLOBAL_MATRIX = Matrix() 265 266 def veckey3d(v): 267 return round(v.x, 4), round(v.y, 4), round(v.z, 4) 268 269 def veckey2d(v): 270 return round(v[0], 4), round(v[1], 4) 271 272 def findVertexGroupName(face, vWeightMap): 273 """ 274 Searches the vertexDict to see what groups is assigned to a given face. 275 We use a frequency system in order to sort out the name because a given vertex can 276 belong to two or more groups at the same time. To find the right name for the face 277 we list all the possible vertex group names with their frequency and then sort by 278 frequency in descend order. The top element is the one shared by the highest number 279 of vertices is the face's group 280 """ 281 weightDict = {} 282 for vert_index in face.vertices: 283 vWeights = vWeightMap[vert_index] 284 for vGroupName, weight in vWeights: 285 weightDict[vGroupName] = weightDict.get(vGroupName, 0.0) + weight 286 287 if weightDict: 288 return max((weight, vGroupName) for vGroupName, weight in weightDict.items())[1] 289 else: 290 return '(null)' 291 292 with ProgressReportSubstep(progress, 2, "OBJ Export path: %r" % filepath, "OBJ Export Finished") as subprogress1: 293 with open(filepath, "w", encoding="utf8", newline="\n") as f: 294 fw = f.write 295 296 # Write Header 297 fw('# Blender v%s OBJ File: %r\n' % (bpy.app.version_string, os.path.basename(bpy.data.filepath))) 298 fw('# www.blender.org\n') 299 300 # Tell the obj file what material file to use. 301 if EXPORT_MTL: 302 mtlfilepath = os.path.splitext(filepath)[0] + ".mtl" 303 # filepath can contain non utf8 chars, use repr 304 fw('mtllib %s\n' % repr(os.path.basename(mtlfilepath))[1:-1]) 305 306 # Initialize totals, these are updated each object 307 totverts = totuvco = totno = 1 308 309 face_vert_index = 1 310 311 # A Dict of Materials 312 # (material.name, image.name):matname_imagename # matname_imagename has gaps removed. 313 mtl_dict = {} 314 # Used to reduce the usage of matname_texname materials, which can become annoying in case of 315 # repeated exports/imports, yet keeping unique mat names per keys! 316 # mtl_name: (material.name, image.name) 317 mtl_rev_dict = {} 318 319 copy_set = set() 320 321 # Get all meshes 322 subprogress1.enter_substeps(len(objects)) 323 for i, ob_main in enumerate(objects): 324 # ignore dupli children 325 if ob_main.parent and ob_main.parent.instance_type in {'VERTS', 'FACES'}: 326 subprogress1.step("Ignoring %s, dupli child..." % ob_main.name) 327 continue 328 329 obs = [(ob_main, ob_main.matrix_world)] 330 if ob_main.is_instancer: 331 obs += [(dup.instance_object.original, dup.matrix_world.copy()) 332 for dup in depsgraph.object_instances 333 if dup.parent and dup.parent.original == ob_main] 334 # ~ print(ob_main.name, 'has', len(obs) - 1, 'dupli children') 335 336 subprogress1.enter_substeps(len(obs)) 337 for ob, ob_mat in obs: 338 with ProgressReportSubstep(subprogress1, 6) as subprogress2: 339 uv_unique_count = no_unique_count = 0 340 341 # Nurbs curve support 342 if EXPORT_CURVE_AS_NURBS and test_nurbs_compat(ob): 343 ob_mat = EXPORT_GLOBAL_MATRIX @ ob_mat 344 totverts += write_nurb(fw, ob, ob_mat) 345 continue 346 # END NURBS 347 348 ob_for_convert = ob.evaluated_get(depsgraph) if EXPORT_APPLY_MODIFIERS else ob.original 349 350 try: 351 me = ob_for_convert.to_mesh() 352 except RuntimeError: 353 me = None 354 355 if me is None: 356 continue 357 358 # _must_ do this before applying transformation, else tessellation may differ 359 if EXPORT_TRI: 360 # _must_ do this first since it re-allocs arrays 361 mesh_triangulate(me) 362 363 me.transform(EXPORT_GLOBAL_MATRIX @ ob_mat) 364 # If negative scaling, we have to invert the normals... 365 if ob_mat.determinant() < 0.0: 366 me.flip_normals() 367 368 if EXPORT_UV: 369 faceuv = len(me.uv_layers) > 0 370 if faceuv: 371 uv_layer = me.uv_layers.active.data[:] 372 else: 373 faceuv = False 374 375 me_verts = me.vertices[:] 376 377 # Make our own list so it can be sorted to reduce context switching 378 face_index_pairs = [(face, index) for index, face in enumerate(me.polygons)] 379 380 if EXPORT_EDGES: 381 edges = me.edges 382 else: 383 edges = [] 384 385 if not (len(face_index_pairs) + len(edges) + len(me.vertices)): # Make sure there is something to write 386 # clean up 387 ob_for_convert.to_mesh_clear() 388 continue # dont bother with this mesh. 389 390 if EXPORT_NORMALS and face_index_pairs: 391 me.calc_normals_split() 392 # No need to call me.free_normals_split later, as this mesh is deleted anyway! 393 394 loops = me.loops 395 396 if (EXPORT_SMOOTH_GROUPS or EXPORT_SMOOTH_GROUPS_BITFLAGS) and face_index_pairs: 397 smooth_groups, smooth_groups_tot = me.calc_smooth_groups(use_bitflags=EXPORT_SMOOTH_GROUPS_BITFLAGS) 398 if smooth_groups_tot <= 1: 399 smooth_groups, smooth_groups_tot = (), 0 400 else: 401 smooth_groups, smooth_groups_tot = (), 0 402 403 materials = me.materials[:] 404 material_names = [m.name if m else None for m in materials] 405 406 # avoid bad index errors 407 if not materials: 408 materials = [None] 409 material_names = [name_compat(None)] 410 411 # Sort by Material, then images 412 # so we dont over context switch in the obj file. 413 if EXPORT_KEEP_VERT_ORDER: 414 pass 415 else: 416 if len(materials) > 1: 417 if smooth_groups: 418 sort_func = lambda a: (a[0].material_index, 419 smooth_groups[a[1]] if a[0].use_smooth else False) 420 else: 421 sort_func = lambda a: (a[0].material_index, 422 a[0].use_smooth) 423 else: 424 # no materials 425 if smooth_groups: 426 sort_func = lambda a: smooth_groups[a[1] if a[0].use_smooth else False] 427 else: 428 sort_func = lambda a: a[0].use_smooth 429 430 face_index_pairs.sort(key=sort_func) 431 432 del sort_func 433 434 # Set the default mat to no material and no image. 435 contextMat = 0, 0 # Can never be this, so we will label a new material the first chance we get. 436 contextSmooth = None # Will either be true or false, set bad to force initialization switch. 437 438 if EXPORT_BLEN_OBS or EXPORT_GROUP_BY_OB: 439 name1 = ob.name 440 name2 = ob.data.name 441 if name1 == name2: 442 obnamestring = name_compat(name1) 443 else: 444 obnamestring = '%s_%s' % (name_compat(name1), name_compat(name2)) 445 446 if EXPORT_BLEN_OBS: 447 fw('o %s\n' % obnamestring) # Write Object name 448 else: # if EXPORT_GROUP_BY_OB: 449 fw('g %s\n' % obnamestring) 450 451 subprogress2.step() 452 453 # Vert 454 for v in me_verts: 455 fw('v %.6f %.6f %.6f\n' % v.co[:]) 456 457 subprogress2.step() 458 459 # UV 460 if faceuv: 461 # in case removing some of these dont get defined. 462 uv = f_index = uv_index = uv_key = uv_val = uv_ls = None 463 464 uv_face_mapping = [None] * len(face_index_pairs) 465 466 uv_dict = {} 467 uv_get = uv_dict.get 468 for f, f_index in face_index_pairs: 469 uv_ls = uv_face_mapping[f_index] = [] 470 for uv_index, l_index in enumerate(f.loop_indices): 471 uv = uv_layer[l_index].uv 472 # include the vertex index in the key so we don't share UV's between vertices, 473 # allowed by the OBJ spec but can cause issues for other importers, see: T47010. 474 475 # this works too, shared UV's for all verts 476 #~ uv_key = veckey2d(uv) 477 uv_key = loops[l_index].vertex_index, veckey2d(uv) 478 479 uv_val = uv_get(uv_key) 480 if uv_val is None: 481 uv_val = uv_dict[uv_key] = uv_unique_count 482 fw('vt %.6f %.6f\n' % uv[:]) 483 uv_unique_count += 1 484 uv_ls.append(uv_val) 485 486 del uv_dict, uv, f_index, uv_index, uv_ls, uv_get, uv_key, uv_val 487 # Only need uv_unique_count and uv_face_mapping 488 489 subprogress2.step() 490 491 # NORMAL, Smooth/Non smoothed. 492 if EXPORT_NORMALS: 493 no_key = no_val = None 494 normals_to_idx = {} 495 no_get = normals_to_idx.get 496 loops_to_normals = [0] * len(loops) 497 for f, f_index in face_index_pairs: 498 for l_idx in f.loop_indices: 499 no_key = veckey3d(loops[l_idx].normal) 500 no_val = no_get(no_key) 501 if no_val is None: 502 no_val = normals_to_idx[no_key] = no_unique_count 503 fw('vn %.4f %.4f %.4f\n' % no_key) 504 no_unique_count += 1 505 loops_to_normals[l_idx] = no_val 506 del normals_to_idx, no_get, no_key, no_val 507 else: 508 loops_to_normals = [] 509 510 subprogress2.step() 511 512 # XXX 513 if EXPORT_POLYGROUPS: 514 # Retrieve the list of vertex groups 515 vertGroupNames = ob.vertex_groups.keys() 516 if vertGroupNames: 517 currentVGroup = '' 518 # Create a dictionary keyed by face id and listing, for each vertex, the vertex groups it belongs to 519 vgroupsMap = [[] for _i in range(len(me_verts))] 520 for v_idx, v_ls in enumerate(vgroupsMap): 521 v_ls[:] = [(vertGroupNames[g.group], g.weight) for g in me_verts[v_idx].groups] 522 523 for f, f_index in face_index_pairs: 524 f_smooth = f.use_smooth 525 if f_smooth and smooth_groups: 526 f_smooth = smooth_groups[f_index] 527 f_mat = min(f.material_index, len(materials) - 1) 528 529 # MAKE KEY 530 key = material_names[f_mat], None # No image, use None instead. 531 532 # Write the vertex group 533 if EXPORT_POLYGROUPS: 534 if vertGroupNames: 535 # find what vertext group the face belongs to 536 vgroup_of_face = findVertexGroupName(f, vgroupsMap) 537 if vgroup_of_face != currentVGroup: 538 currentVGroup = vgroup_of_face 539 fw('g %s\n' % vgroup_of_face) 540 541 # CHECK FOR CONTEXT SWITCH 542 if key == contextMat: 543 pass # Context already switched, dont do anything 544 else: 545 if key[0] is None and key[1] is None: 546 # Write a null material, since we know the context has changed. 547 if EXPORT_GROUP_BY_MAT: 548 # can be mat_image or (null) 549 fw("g %s_%s\n" % (name_compat(ob.name), name_compat(ob.data.name))) 550 if EXPORT_MTL: 551 fw("usemtl (null)\n") # mat, image 552 553 else: 554 mat_data = mtl_dict.get(key) 555 if not mat_data: 556 # First add to global dict so we can export to mtl 557 # Then write mtl 558 559 # Make a new names from the mat and image name, 560 # converting any spaces to underscores with name_compat. 561 562 # If none image dont bother adding it to the name 563 # Try to avoid as much as possible adding texname (or other things) 564 # to the mtl name (see [#32102])... 565 mtl_name = "%s" % name_compat(key[0]) 566 if mtl_rev_dict.get(mtl_name, None) not in {key, None}: 567 if key[1] is None: 568 tmp_ext = "_NONE" 569 else: 570 tmp_ext = "_%s" % name_compat(key[1]) 571 i = 0 572 while mtl_rev_dict.get(mtl_name + tmp_ext, None) not in {key, None}: 573 i += 1 574 tmp_ext = "_%3d" % i 575 mtl_name += tmp_ext 576 mat_data = mtl_dict[key] = mtl_name, materials[f_mat] 577 mtl_rev_dict[mtl_name] = key 578 579 if EXPORT_GROUP_BY_MAT: 580 # can be mat_image or (null) 581 fw("g %s_%s_%s\n" % (name_compat(ob.name), name_compat(ob.data.name), mat_data[0])) 582 if EXPORT_MTL: 583 fw("usemtl %s\n" % mat_data[0]) # can be mat_image or (null) 584 585 contextMat = key 586 if f_smooth != contextSmooth: 587 if f_smooth: # on now off 588 if smooth_groups: 589 f_smooth = smooth_groups[f_index] 590 fw('s %d\n' % f_smooth) 591 else: 592 fw('s 1\n') 593 else: # was off now on 594 fw('s off\n') 595 contextSmooth = f_smooth 596 597 f_v = [(vi, me_verts[v_idx], l_idx) 598 for vi, (v_idx, l_idx) in enumerate(zip(f.vertices, f.loop_indices))] 599 600 fw('f') 601 if faceuv: 602 if EXPORT_NORMALS: 603 for vi, v, li in f_v: 604 fw(" %d/%d/%d" % (totverts + v.index, 605 totuvco + uv_face_mapping[f_index][vi], 606 totno + loops_to_normals[li], 607 )) # vert, uv, normal 608 else: # No Normals 609 for vi, v, li in f_v: 610 fw(" %d/%d" % (totverts + v.index, 611 totuvco + uv_face_mapping[f_index][vi], 612 )) # vert, uv 613 614 face_vert_index += len(f_v) 615 616 else: # No UV's 617 if EXPORT_NORMALS: 618 for vi, v, li in f_v: 619 fw(" %d//%d" % (totverts + v.index, totno + loops_to_normals[li])) 620 else: # No Normals 621 for vi, v, li in f_v: 622 fw(" %d" % (totverts + v.index)) 623 624 fw('\n') 625 626 subprogress2.step() 627 628 # Write edges. 629 if EXPORT_EDGES: 630 for ed in edges: 631 if ed.is_loose: 632 fw('l %d %d\n' % (totverts + ed.vertices[0], totverts + ed.vertices[1])) 633 634 # Make the indices global rather then per mesh 635 totverts += len(me_verts) 636 totuvco += uv_unique_count 637 totno += no_unique_count 638 639 # clean up 640 ob_for_convert.to_mesh_clear() 641 642 subprogress1.leave_substeps("Finished writing geometry of '%s'." % ob_main.name) 643 subprogress1.leave_substeps() 644 645 subprogress1.step("Finished exporting geometry, now exporting materials") 646 647 # Now we have all our materials, save them 648 if EXPORT_MTL: 649 write_mtl(scene, mtlfilepath, EXPORT_PATH_MODE, copy_set, mtl_dict) 650 651 # copy all collected files. 652 io_utils.path_reference_copy(copy_set) 653 654 655def _write(context, filepath, 656 EXPORT_TRI, # ok 657 EXPORT_EDGES, 658 EXPORT_SMOOTH_GROUPS, 659 EXPORT_SMOOTH_GROUPS_BITFLAGS, 660 EXPORT_NORMALS, # ok 661 EXPORT_UV, # ok 662 EXPORT_MTL, 663 EXPORT_APPLY_MODIFIERS, # ok 664 EXPORT_APPLY_MODIFIERS_RENDER, # ok 665 EXPORT_BLEN_OBS, 666 EXPORT_GROUP_BY_OB, 667 EXPORT_GROUP_BY_MAT, 668 EXPORT_KEEP_VERT_ORDER, 669 EXPORT_POLYGROUPS, 670 EXPORT_CURVE_AS_NURBS, 671 EXPORT_SEL_ONLY, # ok 672 EXPORT_ANIMATION, 673 EXPORT_GLOBAL_MATRIX, 674 EXPORT_PATH_MODE, # Not used 675 ): 676 677 with ProgressReport(context.window_manager) as progress: 678 base_name, ext = os.path.splitext(filepath) 679 context_name = [base_name, '', '', ext] # Base name, scene name, frame number, extension 680 681 depsgraph = context.evaluated_depsgraph_get() 682 scene = context.scene 683 684 # Exit edit mode before exporting, so current object states are exported properly. 685 if bpy.ops.object.mode_set.poll(): 686 bpy.ops.object.mode_set(mode='OBJECT') 687 688 orig_frame = scene.frame_current 689 690 # Export an animation? 691 if EXPORT_ANIMATION: 692 scene_frames = range(scene.frame_start, scene.frame_end + 1) # Up to and including the end frame. 693 else: 694 scene_frames = [orig_frame] # Dont export an animation. 695 696 # Loop through all frames in the scene and export. 697 progress.enter_substeps(len(scene_frames)) 698 for frame in scene_frames: 699 if EXPORT_ANIMATION: # Add frame to the filepath. 700 context_name[2] = '_%.6d' % frame 701 702 scene.frame_set(frame, subframe=0.0) 703 if EXPORT_SEL_ONLY: 704 objects = context.selected_objects 705 else: 706 objects = scene.objects 707 708 full_path = ''.join(context_name) 709 710 # erm... bit of a problem here, this can overwrite files when exporting frames. not too bad. 711 # EXPORT THE FILE. 712 progress.enter_substeps(1) 713 write_file(full_path, objects, depsgraph, scene, 714 EXPORT_TRI, 715 EXPORT_EDGES, 716 EXPORT_SMOOTH_GROUPS, 717 EXPORT_SMOOTH_GROUPS_BITFLAGS, 718 EXPORT_NORMALS, 719 EXPORT_UV, 720 EXPORT_MTL, 721 EXPORT_APPLY_MODIFIERS, 722 EXPORT_APPLY_MODIFIERS_RENDER, 723 EXPORT_BLEN_OBS, 724 EXPORT_GROUP_BY_OB, 725 EXPORT_GROUP_BY_MAT, 726 EXPORT_KEEP_VERT_ORDER, 727 EXPORT_POLYGROUPS, 728 EXPORT_CURVE_AS_NURBS, 729 EXPORT_GLOBAL_MATRIX, 730 EXPORT_PATH_MODE, 731 progress, 732 ) 733 progress.leave_substeps() 734 735 scene.frame_set(orig_frame, subframe=0.0) 736 progress.leave_substeps() 737 738 739""" 740Currently the exporter lacks these features: 741* multiple scene export (only active scene is written) 742* particles 743""" 744 745 746def save(context, 747 filepath, 748 *, 749 use_triangles=False, 750 use_edges=True, 751 use_normals=False, 752 use_smooth_groups=False, 753 use_smooth_groups_bitflags=False, 754 use_uvs=True, 755 use_materials=True, 756 use_mesh_modifiers=True, 757 use_mesh_modifiers_render=False, 758 use_blen_objects=True, 759 group_by_object=False, 760 group_by_material=False, 761 keep_vertex_order=False, 762 use_vertex_groups=False, 763 use_nurbs=True, 764 use_selection=True, 765 use_animation=False, 766 global_matrix=None, 767 path_mode='AUTO' 768 ): 769 770 _write(context, filepath, 771 EXPORT_TRI=use_triangles, 772 EXPORT_EDGES=use_edges, 773 EXPORT_SMOOTH_GROUPS=use_smooth_groups, 774 EXPORT_SMOOTH_GROUPS_BITFLAGS=use_smooth_groups_bitflags, 775 EXPORT_NORMALS=use_normals, 776 EXPORT_UV=use_uvs, 777 EXPORT_MTL=use_materials, 778 EXPORT_APPLY_MODIFIERS=use_mesh_modifiers, 779 EXPORT_APPLY_MODIFIERS_RENDER=use_mesh_modifiers_render, 780 EXPORT_BLEN_OBS=use_blen_objects, 781 EXPORT_GROUP_BY_OB=group_by_object, 782 EXPORT_GROUP_BY_MAT=group_by_material, 783 EXPORT_KEEP_VERT_ORDER=keep_vertex_order, 784 EXPORT_POLYGROUPS=use_vertex_groups, 785 EXPORT_CURVE_AS_NURBS=use_nurbs, 786 EXPORT_SEL_ONLY=use_selection, 787 EXPORT_ANIMATION=use_animation, 788 EXPORT_GLOBAL_MATRIX=global_matrix, 789 EXPORT_PATH_MODE=path_mode, 790 ) 791 792 return {'FINISHED'} 793