""" This script exports PlayStation SDK compatible RSD,PLY,MAT files from Blender. Supports normals, colors and texture mapped triangles. Only one mesh can be exported at a time. """ import os import bpy from bpy.props import (CollectionProperty, StringProperty, BoolProperty, EnumProperty, FloatProperty, ) from bpy_extras.io_utils import (ImportHelper, ExportHelper, axis_conversion, ) bl_info = { "name": "Export: Playstation RSD,PLY,MAT Model Format", "author": "Jobert 'Lameguy' Villamor (Lameguy64), ABelliqueux", "blender": (2,6,9), "version": (2,0,1), "location": "File > Export", "description": "Export mesh to PlayStation SDK compatible RSD,PLY,MAT format", "support": "COMMUNITY", "category": "Import-Export" } class ExportRSD(bpy.types.Operator, ExportHelper): bl_idname = "export_mesh.rsd"; bl_label = "Export RSD,PLY,MAT"; filename_ext = ".rsd"; filter_glob = StringProperty(default="*.rsd;*.ply;*.mat", options={'HIDDEN'}) # Export options exp_applyModifiers = BoolProperty( name="Apply Modifiers", description="Apply modifiers to the exported mesh.", default=True, ) exp_coloredTexPolys = BoolProperty( name="Colored Textured Polygons", description="Export all textured faces as vertex colored, " "light source calculation on such faces will be " "disabled however due to libgs limitations.", default=False, ) exp_scaleFactor = FloatProperty( name="Scale Factor", description="Scale factor of exported mesh.", min=0.01, max=1000.0, default=1.0, ) def execute(self, context): bl_idname = "export_mesh.rsd"; bl_label = "Export RSD"; # Trivia: bpy.path.ensure_ext got borked in newer versions of Blender due to a 'fix' introduced in # Thu Sep 3 13:09:16 2015 +0200 so I had to use this messy trick to get extensions to work properly # # Said 'bugfix' addresses the issue in bpy.path.ensure_ext with extensions with double periods (such as .tar.gz) # but it also breaks its intended function of adding or replacing an existing file extension with the specified # extension. This 'bugfix' still persists in 2.76b and no one but I (lameguy64) seems to notice this problem # probably because I'm the only one making export plugins that output more than one file in different extensions # so I doubt this will ever get fixed. filepath = self.filepath filepath = filepath.replace(self.filename_ext, "") # Quick fix to get around the aforementioned 'bugfix' rsd_filepath = bpy.path.ensure_ext(filepath, self.filename_ext) ply_filepath = bpy.path.ensure_ext(filepath, '.ply') mat_filepath = bpy.path.ensure_ext(filepath, '.mat') # Get object context obj = context.object # Get mesh mesh = obj.to_mesh(context.scene, self.exp_applyModifiers, 'PREVIEW') if not mesh.tessfaces and mesh.polygons: mesh.calc_tessface() # Write PLY file with open(ply_filepath, "w") as f: f.write("@PLY940102\n") f.write("%d %d %d\n" % (len(mesh.vertices), (len(mesh.vertices)+len(mesh.polygons)), len(mesh.tessfaces))) # Write vertices f.write("# Vertices\n") for v in mesh.vertices: f.write("%E %E %E\n" % (v.co.x * self.exp_scaleFactor, -v.co.z * self.exp_scaleFactor, v.co.y * self.exp_scaleFactor)) # Write normals f.write("# Normals\n") f.write("# Smooth normals begin here\n") for v in mesh.vertices: f.write("%E %E %E\n" % (v.normal.x, -v.normal.z, v.normal.y)) f.write("# Flat normals begin here\n") flatnorms_start = len(mesh.vertices) for p in mesh.tessfaces: f.write("%E %E %E\n" % (p.normal.x, -p.normal.z, p.normal.y)) # Write polygons f.write("# Polygon\n") for i,p in enumerate(mesh.tessfaces): # Write vertex indices if (len(p.vertices) == 3): f.write("0 %d %d %d 0 " % (p.vertices[0], p.vertices[2], p.vertices[1])) elif (len(p.vertices) == 4): f.write("1 %d %d %d %d " % (p.vertices[3], p.vertices[2], p.vertices[0], p.vertices[1])) # Write normal indices and shading mode if p.use_smooth: if (len(p.vertices) == 3): f.write("%d %d %d 0" % (p.vertices[0], p.vertices[2], p.vertices[1])) elif (len(p.vertices) == 4): f.write("%d %d %d %d" % (p.vertices[3], p.vertices[2], p.vertices[0], p.vertices[1])) else: n = flatnorms_start+i if (len(p.vertices) == 3): f.write("%d %d %d 0" % (n, n, n)) elif (len(p.vertices) == 4): f.write("%d %d %d %d" % (n, n, n, n)) f.write("\n") # Write MAT file with open(mat_filepath, "w") as f: f.write("@MAT940801\n") f.write("%d\n" % len(mesh.tessfaces)) # Get textures mesh_uvs = mesh.tessface_uv_textures.active if mesh_uvs is not None: mesh_uvs = mesh_uvs.data # Scan through all faces for assigned textures if mesh_uvs is not None: tex_table = [] tex_files = [] for uv in mesh_uvs: if uv.image is not None: addTex = True texFileName = bpy.path.display_name_from_filepath(uv.image.filepath) if len(tex_files)>0: for c,t in enumerate(tex_files): if t == texFileName: tex_table.append(c+1) addTex = False break if addTex: tex_files.append(texFileName) tex_table.append(len(tex_files)) else: tex_table.append(0) else: tex_table = None tex_files = None if mesh.vertex_colors: mesh_cols = mesh.tessface_vertex_colors.active.data else: mesh_cols = None for i,p in enumerate(mesh.tessfaces): f.write("%d\t 0 " % i) # Set flat or gouraud if p.use_smooth: f.write("G ") else: f.write("F ") # So that vertex colors will be correct for textured polys if tex_table is not None: if (tex_table[i] > 0): color_mul = 128.0 pol_textured = True else: color_mul = 255.0 pol_textured = False else: color_mul = 255.0 pol_textured = False # Check if polygon is flat or gouraud shaded if mesh_cols is not None: col = mesh_cols[i] col = col.color1[:], col.color2[:], col.color3[:], col.color4[:] # Check if polygon is flat shaded if (col[0] == col[1]) and (col[1] == col[2]) and (col[2] == col[0]): # is flat... pol_gouraud = False else: # is gouraud... pol_gouraud = True else: pol_gouraud = False # Write texture coordinates if pol_textured: if self.exp_coloredTexPolys: if pol_gouraud: f.write("H ") else: f.write("D ") else: f.write("T ") f.write("%d " % (tex_table[i]-1)); if (len(p.vertices) == 3): uv = (mesh_uvs[i].uv1, mesh_uvs[i].uv3, mesh_uvs[i].uv2, ) elif (len(p.vertices) == 4): uv = (mesh_uvs[i].uv4, mesh_uvs[i].uv3, mesh_uvs[i].uv1, mesh_uvs[i].uv2 ) tex_w = mesh_uvs[i].image.size[0]-0.85 tex_h = mesh_uvs[i].image.size[1]-0.85 for j,c in enumerate(p.vertices): f.write("%d %d " % (round(tex_w*uv[j].x), round(tex_h-(tex_h*uv[j].y)))) if (len(p.vertices) == 3): f.write("0 0 ") else: if pol_gouraud: f.write("G ") else: f.write("C ") # Write vertex colors if mesh_cols is not None: if (self.exp_coloredTexPolys) or (pol_textured == False): if (pol_gouraud): if (len(p.vertices) == 4): index_tab = [ 3, 2, 0, 1 ] else: index_tab = [ 0, 2, 1 ] for j,c in enumerate(p.vertices): color = col[index_tab[j]] color = (int(color[0]*color_mul), int(color[1]*color_mul), int(color[2]*color_mul)) f.write("%d %d %d " % (color[0], color[1], color[2])) # according to filefrmt.pdf, section 2-10, figure 2-15, "(4th vertex is 0,0,0 for triangles)" if (len(p.vertices) == 3): f.write("0 0 0") else: color = col[0] color = (int(color[0]*color_mul), int(color[1]*color_mul), int(color[2]*color_mul), ) f.write("%d %d %d " % color[:]) else: f.write("%d %d %d " % (color_mul, color_mul, color_mul)) f.write("\n") # Write RSD file with open(rsd_filepath, "w") as f: f.write("@RSD940102\n") f.write("PLY=%s\n" % bpy.path.basename(ply_filepath)) f.write("MAT=%s\n" % bpy.path.basename(mat_filepath)) # Write texture files if tex_files is not None: f.write("NTEX=%d\n" % len(tex_files)) for i,n in enumerate(tex_files): f.write("TEX[%d]=%s\n" % (i, bpy.path.ensure_ext(n, '.tim'))) else: f.write("NTEX=0\n") f.close() return {'FINISHED'}; # For registering to Blender menus def menu_func(self, context): self.layout.operator(ExportRSD.bl_idname, text="PlayStation RSD (.rsd,.ply,.mat)"); def register(): bpy.utils.register_module(__name__); bpy.types.INFO_MT_file_export.append(menu_func); def unregister(): bpy.utils.unregister_module(__name__); bpy.types.INFO_MT_file_export.remove(menu_func); if __name__ == "__main__": register()