From b3b6fd4475aed4ca38587ca83d34000f60b68a47 Mon Sep 17 00:00:00 2001 From: "John Wilbert M. Villamor" Date: Thu, 23 Nov 2017 09:24:10 +0800 Subject: [PATCH] Initial Github Release --- README.md | 15 ++- io_export_rsd.py | 302 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 io_export_rsd.py diff --git a/README.md b/README.md index c14cd1c..cd1958b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ # Blender-RSD-Plugin -A Blender Plug-in for Exporting Models in PlayStation SDK RSD Format +A Blender Plug-in for Exporting Models in PlayStation SDK RSD Format. + +This plug-in supports exporting flat shaded, gouraud shaded and texture mapped tris and quads in PlayStation SDK compatble RSD, PLY and MAT file formats. Semi-transparency attributes can also be set but per-polygon semi-transparency is currently not supported. + +This plug-in is an improvemnt over the original Blender RSD export plug-in found in the psxdev forums. This version fixes the reocurring bug where texture coordinates for quads are sometimes not exported correctly as well as performing a lot faster than the original plug-in. + +Should be compatible in Blender 2.69 and later (recently tested on 2.78c). + +## Installation +Simply install the plug-in by using the Install from File option in the Add-ons tab in Blender User Preferences. + +## Changelog +**Version 2.0.0** +* Initial github release. \ No newline at end of file diff --git a/io_export_rsd.py b/io_export_rsd.py new file mode 100644 index 0000000..15a306a --- /dev/null +++ b/io_export_rsd.py @@ -0,0 +1,302 @@ +""" +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)", + "blender": (2,6,9), + "version": (2,0,0), + "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, -v.co.z, v.co.y)) + + # 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])) + 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() \ No newline at end of file