Created
July 30, 2021 08:08
-
-
Save ABelliqueux/b66dcdf864f82d99f837f2b1fdc16d12 to your computer and use it in GitHub Desktop.
Lameguy64/-Blender-2.80-RSD-Plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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,80,0), | |
"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') | |
mesh = obj.to_mesh() | |
if not mesh.loop_triangles and mesh.polygons: | |
mesh.calc_loop_triangles() | |
# 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.loop_triangles)), len(mesh.loop_triangles))) | |
# 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.loop_triangles: | |
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.loop_triangles): | |
# 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.loop_triangles)) | |
# Get textures | |
mesh_uvs = mesh.uv_layers.active | |
if mesh_uvs is not None: | |
mesh_uvs = mesh_uvs.data | |
mesh_materials = obj.material_slots | |
# ~ mesh_textures = mesh_materials[].material.node_tree.nodes["Image Texture"] | |
# Scan through all faces for assigned textures | |
if mesh_uvs is not None: | |
tex_table = [] | |
tex_files = [] | |
for tex in mesh_materials: | |
if tex.material.node_tree.nodes["Image Texture"].image.name is not None: | |
addTex = True | |
texFileName = os.path.splitext(tex.material.node_tree.nodes["Image Texture"].image.name)[0] | |
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.uv_layers.active.data | |
else: | |
mesh_cols = None | |
for i,p in enumerate(mesh.loop_triangles): | |
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[14].uv, # 14 | |
mesh_uvs[12].uv, # 12 | |
mesh_uvs[15].uv, # 15 | |
) | |
elif (len(p.vertices) == 4): | |
uv = (mesh_uvs[13].uv4, # 13 | |
mesh_uvs[12].uv3, # 12 | |
mesh_uvs[14].uv1, # 14 | |
mesh_uvs[15].uv2 # 15 | |
) | |
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 make_annotations(cls): | |
"""Converts class fields to annotations if running with Blender 2.8""" | |
if bpy.app.version < (2, 80): | |
return cls | |
bl_props = {k: v for k, v in cls.__dict__.items() if isinstance(v, tuple)} | |
if bl_props: | |
if '__annotations__' not in cls.__dict__: | |
setattr(cls, '__annotations__', {}) | |
annotations = cls.__dict__['__annotations__'] | |
for k, v in bl_props.items(): | |
annotations[k] = v | |
delattr(cls, k) | |
return cls | |
def register(): | |
make_annotations(ExportRSD) # what is this? Read the section on annotations above! | |
bpy.utils.register_class(ExportRSD) | |
bpy.types.TOPBAR_MT_file_export.append(menu_func); | |
def unregister(): # note how unregistering is done in reverse | |
bpy.utils.unregister_class(ExportRSD) | |
bpy.types.TOPBAR_MT_file_export.remove(menu_func); | |
if __name__ == "__main__": | |
register() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment