finish cleanup

This commit is contained in:
ABelliqueux 2021-03-12 13:06:21 +01:00
parent 55af8e8406
commit c0ba209f87

View File

@ -1,7 +1,5 @@
# bpy. app. debug = True # bpy. app. debug = True
bl_info = { bl_info = {
"name": "PSX TMesh exporter", "name": "PSX TMesh exporter",
"author": "Schnappy, TheDukeOfZill", "author": "Schnappy, TheDukeOfZill",
@ -13,12 +11,19 @@ bl_info = {
} }
import os import os
import bpy import bpy
import bmesh import bmesh
import unicodedata import unicodedata
from math import radians, degrees, floor, cos, sin from math import radians, degrees, floor, cos, sin
from mathutils import Vector from mathutils import Vector
from collections import defaultdict
from bpy.props import (CollectionProperty, from bpy.props import (CollectionProperty,
StringProperty, StringProperty,
BoolProperty, BoolProperty,
@ -31,122 +36,282 @@ from bpy_extras.io_utils import (ExportHelper,
) )
class ExportMyFormat(bpy.types.Operator, ExportHelper): class ExportMyFormat(bpy.types.Operator, ExportHelper):
bl_idname = "export_psx.c"; bl_idname = "export_psx.c";
bl_label = "PSX compatible scene exporter"; bl_label = "PSX compatible scene exporter";
bl_options = {'PRESET'}; bl_options = {'PRESET'};
filename_ext = ".c"; filename_ext = ".c";
exp_Triangulate = BoolProperty( exp_Triangulate = BoolProperty(
name="Triangulate meshes ( Destructive ! )", name="Triangulate meshes ( Destructive ! )",
description="Triangulate meshes (destructive ! Do not use your original file)", description="Triangulate meshes (destructive ! Do not use your original file)",
default=False, default=False,
) )
exp_Scale = FloatProperty( exp_Scale = FloatProperty(
name="Scale", name="Scale",
description="Scale of exported mesh.", description="Scale of exported mesh.",
min=1, max=1000, min=1, max=1000,
default=65, default=65,
) )
exp_Precalc = BoolProperty( exp_Precalc = BoolProperty(
name="Use precalculated BGs", name="Use precalculated BGs",
description="Set the BGs UV to black", description="Set the BGs UV to black",
default=False, default=False,
) )
def execute(self, context): def execute(self, context):
def triangulate_object(obj): # Stolen from here : https://blender.stackexchange.com/questions/45698/triangulate-mesh-in-python/45722#45722 def triangulate_object(obj):
# Triangulate an object's mesh
# Source : https://blender.stackexchange.com/questions/45698/triangulate-mesh-in-python/45722#45722
me = obj.data me = obj.data
# Get a BMesh representation # Get a BMesh representation
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(me) bm.from_mesh(me)
bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method=0, ngon_method=0) bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method=0, ngon_method=0)
# Finish up, write the bmesh back to the mesh # Finish up, write the bmesh back to the mesh
bm.to_mesh(me) bm.to_mesh(me)
bm.free() bm.free()
# ~ return bm
def CleanName(strName): def CleanName(strName):
# Removes specials characters, dots ans space from string
name = strName.replace(' ','_') name = strName.replace(' ','_')
name = name.replace('.','_') name = name.replace('.','_')
name = unicodedata.normalize('NFKD',name).encode('ASCII', 'ignore').decode() name = unicodedata.normalize('NFKD',name).encode('ASCII', 'ignore').decode()
return name return name
def isInPlane(plane, obj):
# Checks if 'obj' has its coordinates contained between the plane's coordinate.
# If 'obj' is partly contained, returns which side (S, W, N, E) it's overlapping.
# If 'obj' is not contained in 'plane', returns 0.
if (
(LvlPlanes[plane]['x1'] < LvlObjects[obj]['x1'] and LvlPlanes[plane]['x2'] > LvlObjects[obj]['x2']) and
(LvlPlanes[plane]['y1'] < LvlObjects[obj]['y1'] and LvlPlanes[plane]['y2'] > LvlObjects[obj]['y2'])
):
return 1
# Overlap on the West side of the plane
if (
( LvlPlanes[plane]['x1'] > LvlObjects[obj]['x1'] and LvlPlanes[plane]['x1'] < LvlObjects[obj]['x2'] ) and
( LvlPlanes[plane]['y1'] < LvlObjects[obj]['y1'] and LvlPlanes[plane]['y2'] > LvlObjects[obj]['y2'] )
):
return 4
# Overlap on the East side of the plane
if (
( LvlPlanes[plane]['x2'] < LvlObjects[obj]['x2'] and LvlPlanes[plane]['x2'] > LvlObjects[obj]['x1'] ) and
( LvlPlanes[plane]['y1'] < LvlObjects[obj]['y1'] and LvlPlanes[plane]['y2'] > LvlObjects[obj]['y2'] )
):
return 6
# Overlap on the North side of the plane
if (
( LvlPlanes[plane]['y2'] < LvlObjects[obj]['y2'] and LvlPlanes[plane]['y2'] > LvlObjects[obj]['y1'] ) and
( LvlPlanes[plane]['x1'] < LvlObjects[obj]['x1'] and LvlPlanes[plane]['x2'] > LvlObjects[obj]['x2'] )
):
return 8
# Overlap on the South side of the plane
if (
( LvlPlanes[plane]['y1'] > LvlObjects[obj]['y1'] and LvlPlanes[plane]['y1'] < LvlObjects[obj]['y2'] ) and
( LvlPlanes[plane]['x1'] < LvlObjects[obj]['x1'] and LvlPlanes[plane]['x2'] > LvlObjects[obj]['x2'] )
):
return 2
else:
return 0
def getSepLine(plane, side):
# Construct the line used for BSP generation from 'plane' 's coordinates, on specified side (S, W, N, E)
# Returns an array of 3 values
if side == 'S':
return [ LvlPlanes[plane]['x1'], LvlPlanes[plane]['y1'], LvlPlanes[plane]['x2'], LvlPlanes[plane]['y1'] ]
if side == 'N':
return [ LvlPlanes[plane]['x1'], LvlPlanes[plane]['y2'], LvlPlanes[plane]['x2'], LvlPlanes[plane]['y2'] ]
if side == 'W':
return [ LvlPlanes[plane]['x1'], LvlPlanes[plane]['y1'], LvlPlanes[plane]['x1'], LvlPlanes[plane]['y2'] ]
if side == 'E':
return [ LvlPlanes[plane]['x2'], LvlPlanes[plane]['y1'], LvlPlanes[plane]['x2'], LvlPlanes[plane]['y2'] ]
def checkLine(lineX1, lineY1 ,lineX2 ,lineY2, objX1, objY1, objX2, objY2):
# Returns wether object spanning from objXY1 to objXY2 is Back, Front, Same or Intersecting the line
# defined by points (lineXY1, lineXY2)
val1 = ( objX1 - lineX1 ) * ( lineY2-lineY1 ) - ( objY1 - lineY1 ) * ( lineX2 - lineX1 )
# rounding to avoid false positives
val1 = round(val1, 4)
val2 = ( objX2 - lineX1 ) * ( lineY2-lineY1 ) - ( objY2 - lineY1 ) * ( lineX2 - lineX1 )
val2 = round(val2, 4)
if ( (val1 > 0) and (val2 > 0) ):
return "front"
elif ( (val1 < 0) and (val2 < 0) ):
return "back"
elif ( (val1 == 0) and (val2 == 0) ):
return "same"
elif (
( (val1>0) and (val2==0) ) or
( (val1==0) and (val2>0) )
):
return "front"
elif (
( (val1<0) and (val2==0) ) or
( (val1==0) and (val2<0) )
):
return "back"
elif (
( (val1<0) and (val2>0) ) or
( (val1>0) and (val2<0) )
):
return "intersect"
# Leave edit mode to avoid errors # Leave edit mode to avoid errors
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
# triangulate objects of type mesh # If set, triangulate objects of type mesh
if self.exp_Triangulate: if self.exp_Triangulate:
for o in range(len(bpy.data.objects)): for o in range(len(bpy.data.objects)):
if bpy.data.objects[o].type == 'MESH': if bpy.data.objects[o].type == 'MESH':
triangulate_object(bpy.data.objects[o]) triangulate_object(bpy.data.objects[o])
# ~ obj = bpy.data.objects[bpy.data.objects[o].name] # Set Scale
# ~ tm = obj.to_mesh(bpy.context.scene, False, 'PREVIEW')
# ~ work_meshes.append(tm)
scale = self.exp_Scale scale = self.exp_Scale
# get working directory path # Get working directory path
filepath = bpy.data.filepath filepath = bpy.data.filepath
folder = os.path.dirname(bpy.path.abspath(filepath)) folder = os.path.dirname(bpy.path.abspath(filepath))
dirpath = os.path.join(folder, "TIM") dirpath = os.path.join(folder, "TIM")
### Export pre-calculated backgrounds
camAngles = [] camAngles = []
defaultCam = 'NULL' defaultCam = 'NULL'
# if using precalculated BG, render and export them to ./TIM/ # If using precalculated BG, render and export them to ./TIM/
if self.exp_Precalc: if self.exp_Precalc:
# create folder if !exist # Create folder if it doesn't exist
os.makedirs(dirpath, exist_ok = 1) os.makedirs(dirpath, exist_ok = 1)
# file format config # Set file format config
bpy.context.scene.render.image_settings.file_format = 'PNG' bpy.context.scene.render.image_settings.file_format = 'PNG'
bpy.context.scene.render.image_settings.quality = 100 bpy.context.scene.render.image_settings.quality = 100
bpy.context.scene.render.image_settings.compression = 0 bpy.context.scene.render.image_settings.compression = 0
bpy.context.scene.render.image_settings.color_depth = '8' bpy.context.scene.render.image_settings.color_depth = '8'
# get current cam # Get active cam
cam = bpy.context.scene.camera cam = bpy.context.scene.camera
# store cam location and rot for restoration later # Find default cam, and cameras in camPath
# ~ originLoc = cam.location
# ~ originRot = cam.rotation_euler
for o in bpy.data.objects: for o in bpy.data.objects:
if o.type == 'CAMERA' and o.data.get('isDefault'): if o.type == 'CAMERA' and o.data.get('isDefault'):
defaultCam = o.name defaultCam = o.name
if o.type == 'CAMERA' and o.name.startswith("camPath"): if o.type == 'CAMERA' and o.name.startswith("camPath"):
# set cam as active - could be useful if multiple cam are present # Set camera as active
bpy.context.scene.camera = o bpy.context.scene.camera = o
# set cam Rot/Loc to empty rot/loc # Render and save image
# ~ cam.location = o.location
# ~ cam.rotation_euler = o.rotation_euler
# apply 90degrees rotation on local X axis, as EMPTYs are pointing to -Z (bottom of the screen) by default
# ~ cam.rotation_euler.rotate_axis('X', radians(90))
# render and save image
bpy.ops.render.render() bpy.ops.render.render()
bpy.data.images["Render Result"].save_render(folder + os.sep + "TIM" + os.sep + "bg_" + CleanName(o.name) + "." + str(bpy.context.scene.render.image_settings.file_format).lower()) bpy.data.images["Render Result"].save_render(folder + os.sep + "TIM" + os.sep + "bg_" + CleanName(o.name) + "." + str(bpy.context.scene.render.image_settings.file_format).lower())
# Add camera object to camAngles
camAngles.append(o) camAngles.append(o)
# set cam back to original pos/rot ### Start writing output file
# ~ cam.location = originLoc
# ~ cam.rotation_euler = originRot # Open file
f = open(os.path.normpath(self.filepath),"w+") f = open(os.path.normpath(self.filepath),"w+")
# write BODY struct def ## Add C structures definitions
# BODY
f.write("typedef struct {\n" + f.write("typedef struct {\n" +
"\tVECTOR gForce;\n" + "\tVECTOR gForce;\n" +
"\tVECTOR position;\n" + "\tVECTOR position;\n" +
@ -158,7 +323,8 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
"\tint restitution; \n" + "\tint restitution; \n" +
"\t} BODY;\n\n") "\t} BODY;\n\n")
# VERTEX ANIM struct # VANIM
f.write("typedef struct { \n" + f.write("typedef struct { \n" +
"\tint nframes; // number of frames e.g 20\n" + "\tint nframes; // number of frames e.g 20\n" +
"\tint nvert; // number of vertices e.g 21\n" + "\tint nvert; // number of vertices e.g 21\n" +
@ -170,13 +336,15 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
# ~ "\tSVECTOR normals[]; // vertex pos as SVECTORs e.g 20 * 21 SVECTORS\n" + # ~ "\tSVECTOR normals[]; // vertex pos as SVECTORs e.g 20 * 21 SVECTORS\n" +
"\t} VANIM;\n\n") "\t} VANIM;\n\n")
# PRIM struc # PRIM
f.write("typedef struct {\n" + f.write("typedef struct {\n" +
"\tVECTOR order;\n" + "\tVECTOR order;\n" +
"\tint code; // Same as POL3/POL4 codes : Code (F3 = 1, FT3 = 2, G3 = 3, GT3 = 4) Code (F4 = 5, FT4 = 6, G4 = 7, GT4 = 8)\n" + "\tint code; // Same as POL3/POL4 codes : Code (F3 = 1, FT3 = 2, G3 = 3, GT3 = 4) Code (F4 = 5, FT4 = 6, G4 = 7, GT4 = 8)\n" +
"\t} PRIM;\n\n") "\t} PRIM;\n\n")
# MESH struct # MESH
f.write("typedef struct { \n"+ f.write("typedef struct { \n"+
"\tTMESH * tmesh;\n" + "\tTMESH * tmesh;\n" +
"\tPRIM * index;\n" + "\tPRIM * index;\n" +
@ -198,7 +366,8 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
"\tVANIM * anim;\n" + "\tVANIM * anim;\n" +
"\t} MESH;\n\n") "\t} MESH;\n\n")
# CAM POSITION struct # CAMPOS
f.write("typedef struct {\n" + f.write("typedef struct {\n" +
"\tVECTOR pos;\n" + "\tVECTOR pos;\n" +
"\tSVECTOR rot;\n" + "\tSVECTOR rot;\n" +
@ -206,30 +375,40 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
"\n// Blender cam ~= PSX cam with these settings : NTSC - 320x240, PAL 320x256, pixel ratio 1:1, cam focal length : perspective 90° ( 16 mm ))\n\n") "\n// Blender cam ~= PSX cam with these settings : NTSC - 320x240, PAL 320x256, pixel ratio 1:1, cam focal length : perspective 90° ( 16 mm ))\n\n")
# CAMANGLE # CAMANGLE
f.write("typedef struct {\n" + f.write("typedef struct {\n" +
"\tCAMPOS * campos;\n" + "\tCAMPOS * campos;\n" +
"\tTIM_IMAGE * BGtim;\n" + "\tTIM_IMAGE * BGtim;\n" +
"\tunsigned long * tim_data;\n" + "\tunsigned long * tim_data;\n" +
"\t} CAMANGLE;\n\n") "\t} CAMANGLE;\n\n")
# CAM PATH struct # CAMPATH
f.write("typedef struct {\n" + f.write("typedef struct {\n" +
"\tshort len, cursor, pos;\n" + "\tshort len, cursor, pos;\n" +
"\tVECTOR points[];\n" + "\tVECTOR points[];\n" +
"\t} CAMPATH;\n\n") "\t} CAMPATH;\n\n")
# NODE struc # NODE
f.Write("typedef struct {\n" +
f.write("typedef struct {\n" +
"\tMESH * curPlane;\n" + "\tMESH * curPlane;\n" +
"\tMESH * siblings[];" + "\tMESH * siblings[];\n" +
"\tMESH * objects[];" + "\tMESH * objects[];\n" +
"\t} NODE;\n\n") "\t} NODE;\n\n")
## Camera setup
# List of points defining the camera path
camPathPoints = [] camPathPoints = []
# Define first mesh. Will be used as default if no properties are found in meshes
first_mesh = CleanName(bpy.data.meshes[0].name) first_mesh = CleanName(bpy.data.meshes[0].name)
# set camera position and rotation in the scene # Set camera position and rotation in the scene
for o in range(len(bpy.data.objects)): for o in range(len(bpy.data.objects)):
if bpy.data.objects[o].type == 'CAMERA' and bpy.data.objects[o].data.get('isDefault'): if bpy.data.objects[o].type == 'CAMERA' and bpy.data.objects[o].data.get('isDefault'):
defaultCam = bpy.data.objects[o].name defaultCam = bpy.data.objects[o].name
@ -239,13 +418,18 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
"\t{" + str(round(-(degrees(bpy.data.objects[o].rotation_euler.x)-90)/360 * 4096)) + "," + str(round(degrees(bpy.data.objects[o].rotation_euler.z)/360 * 4096)) + "," + str(round(-(degrees(bpy.data.objects[o].rotation_euler.y))/360 * 4096)) + "}\n" + "\t{" + str(round(-(degrees(bpy.data.objects[o].rotation_euler.x)-90)/360 * 4096)) + "," + str(round(degrees(bpy.data.objects[o].rotation_euler.z)/360 * 4096)) + "," + str(round(-(degrees(bpy.data.objects[o].rotation_euler.y))/360 * 4096)) + "}\n" +
"};\n\n") "};\n\n")
# find camStart and camEnd empties for camera trajectory # Find camera path points and append them to camPathPoints[]
if bpy.data.objects[o].type == 'CAMERA' : if bpy.data.objects[o].type == 'CAMERA' :
if bpy.data.objects[o].name.startswith("camPath") and not bpy.data.objects[o].data.get('isDefault'): if bpy.data.objects[o].name.startswith("camPath") and not bpy.data.objects[o].data.get('isDefault'):
camPathPoints.append(bpy.data.objects[o].name) camPathPoints.append(bpy.data.objects[o].name)
# Write the CAMPATH structure
if camPathPoints: if camPathPoints:
# Populate with points found above
# ~ camPathPoints = list(reversed(camPathPoints)) # ~ camPathPoints = list(reversed(camPathPoints))
for p in range(len(camPathPoints)): for p in range(len(camPathPoints)):
@ -259,15 +443,24 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
if p != len(camPathPoints) - 1: if p != len(camPathPoints) - 1:
f.write(",\n") f.write(",\n")
f.write("\n\t}\n};\n\n") f.write("\n\t}\n};\n\n")
else: else:
# If no camera path points are found, use default
f.write("CAMPATH camPath = {\n" + f.write("CAMPATH camPath = {\n" +
"\t0,\n" + "\t0,\n" +
"\t0,\n" + "\t0,\n" +
"\t0\n" + "\t0\n" +
"};\n\n") "};\n\n")
# Lights : max 3 sunlamp, no space coords
## Lighting setup
# Light sources will be similar to Blender's sunlamp
# A maximum of 3 light sources will be used
# LLM : Local Light Matrix # LLM : Local Light Matrix
if len(bpy.data.lamps) is not None: if len(bpy.data.lamps) is not None:
# ~ f.write( "static MATRIX lgtmat = {\n" + # ~ f.write( "static MATRIX lgtmat = {\n" +
@ -282,15 +475,15 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
f.write( "static MATRIX lgtmat = {\n") f.write( "static MATRIX lgtmat = {\n")
for l in range(len(bpy.data.lamps)): for l in range(len(bpy.data.lamps)):
## intensity
# Lightsource energy
energy = int(bpy.data.lamps[l].energy * 4096) energy = int(bpy.data.lamps[l].energy * 4096)
# Euler based # Get lightsource's world orientation
# get a direction vector from world matrix
lightdir = bpy.data.objects[bpy.data.lamps[l].name].matrix_world * Vector((0,0,-1,0)) lightdir = bpy.data.objects[bpy.data.lamps[l].name].matrix_world * Vector((0,0,-1,0))
f.write( f.write(
"\t" + str(int(lightdir.x * energy)) + "," + "\t" + str(int(lightdir.x * energy)) + "," +
"\t" + str(int(-lightdir.z * energy)) + "," + "\t" + str(int(-lightdir.z * energy)) + "," +
@ -300,6 +493,8 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
if l != len(bpy.data.lamps) - 1: if l != len(bpy.data.lamps) - 1:
f.write(",\n") f.write(",\n")
# If less than 3 light sources exist in blender, fill the matrix with 0s.
if pad: if pad:
f.write(",\n") f.write(",\n")
while cnt < pad: while cnt < pad:
@ -311,6 +506,7 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
f.write("\n\t};\n\n") f.write("\n\t};\n\n")
# LCM : Local Color Matrix # LCM : Local Color Matrix
f.write( "static MATRIX cmat = {\n") f.write( "static MATRIX cmat = {\n")
LCM = [] LCM = []
@ -323,12 +519,16 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
while len(LCM) < 9: while len(LCM) < 9:
LCM.append('0') LCM.append('0')
# Write LC matrix
f.write( f.write(
"\t" + LCM[0] + "," + LCM[3] + "," + LCM[6] + ",\n" + "\t" + LCM[0] + "," + LCM[3] + "," + LCM[6] + ",\n" +
"\t" + LCM[1] + "," + LCM[4] + "," + LCM[7] + ",\n" + "\t" + LCM[1] + "," + LCM[4] + "," + LCM[7] + ",\n" +
"\t" + LCM[2] + "," + LCM[5] + "," + LCM[8] + "\n" ) "\t" + LCM[2] + "," + LCM[5] + "," + LCM[8] + "\n" )
f.write("\t};\n\n") f.write("\t};\n\n")
## Meshes
actorPtr = first_mesh actorPtr = first_mesh
levelPtr = first_mesh levelPtr = first_mesh
propPtr = first_mesh propPtr = first_mesh
@ -338,23 +538,24 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
for m in bpy.data.meshes: for m in bpy.data.meshes:
# Write vertices vectors # Store vertices coordinates by axis to find max/min coordinates
# AABB : Store vertices coordinates by axis
Xvals = [] Xvals = []
Yvals = [] Yvals = []
Zvals = [] Zvals = []
# remove '.' from mesh name
cleanName = CleanName(m.name) cleanName = CleanName(m.name)
# ~ cleanName = m.name.replace('.','_')
# ~ cleanName = unicodedata.normalize('NFKD',cleanName).encode('ASCII', 'ignore').decode() # Write vertices vectors
f.write("SVECTOR "+"model"+cleanName+"_mesh[] = {\n") f.write("SVECTOR "+"model"+cleanName+"_mesh[] = {\n")
for i in range(len(m.vertices)): for i in range(len(m.vertices)):
v = m.vertices[i].co v = m.vertices[i].co
# AABB : append vertices coords by axis # Append vertex coords to lists
Xvals.append(v.x) Xvals.append(v.x)
Yvals.append(v.y) Yvals.append(v.y)
Zvals.append(-v.z) Zvals.append(-v.z)
@ -390,72 +591,124 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
if len(m.uv_textures) != None: if len(m.uv_textures) != None:
for t in range(len(m.uv_textures)): for t in range(len(m.uv_textures)):
if m.uv_textures[t].data[0].image != None: if m.uv_textures[t].data[0].image != None:
f.write("SVECTOR "+"model"+cleanName+"_uv[] = {\n") f.write("SVECTOR "+"model"+cleanName+"_uv[] = {\n")
texture_image = m.uv_textures[t].data[0].image texture_image = m.uv_textures[t].data[0].image
tex_width = texture_image.size[0] tex_width = texture_image.size[0]
tex_height = texture_image.size[1] tex_height = texture_image.size[1]
uv_layer = m.uv_layers[0].data uv_layer = m.uv_layers[0].data
for i in range(len(uv_layer)): for i in range(len(uv_layer)):
u = uv_layer[i].uv u = uv_layer[i].uv
ux = u.x * tex_width ux = u.x * tex_width
uy = u.y * tex_height uy = u.y * tex_height
# ~ if self.exp_Precalc and m.get('isBG'): # ~ if self.exp_Precalc and m.get('isBG'):
# ~ f.write("\t255, 255, 0, 0") # Clamp values to 0-255 to avoid tpage overflow # ~ f.write("\t255, 255, 0, 0") # Clamp values to 0-255 to avoid tpage overflow
# ~ else: # ~ else:
f.write("\t"+str(max(0, min( round(ux) , 255 )))+","+str(max(0, min(round(tex_height - uy) , 255 )))+", 0, 0") # Clamp values to 0-255 to avoid tpage overflow f.write("\t"+str(max(0, min( round(ux) , 255 )))+","+str(max(0, min(round(tex_height - uy) , 255 )))+", 0, 0") # Clamp values to 0-255 to avoid tpage overflow
if i != len(uv_layer) - 1: if i != len(uv_layer) - 1:
f.write(",") f.write(",")
f.write("\n") f.write("\n")
f.write("};\n\n") f.write("};\n\n")
# save uv tex if needed - still have to convert them to tim... # Save UV texture to a file in ./TIM
# It will have to be converted to a tim file
if texture_image.filepath == '': if texture_image.filepath == '':
os.makedirs(dirpath, exist_ok = 1) os.makedirs(dirpath, exist_ok = 1)
texture_image.filepath_raw = folder + os.sep + "TIM" + os.sep + CleanName(texture_image.name) + "." + texture_image.file_format texture_image.filepath_raw = folder + os.sep + "TIM" + os.sep + CleanName(texture_image.name) + "." + texture_image.file_format
texture_image.save() texture_image.save()
# Write vertex colors vectors # Write vertex colors vectors
f.write("CVECTOR "+"model"+cleanName+"_color[] = {\n") f.write("CVECTOR "+"model"+cleanName+"_color[] = {\n")
# If vertex colors exist, use them # If vertex colors exist, use them
if len(m.vertex_colors) != 0: if len(m.vertex_colors) != 0:
colors = m.vertex_colors[0].data colors = m.vertex_colors[0].data
for i in range(len(colors)): for i in range(len(colors)):
f.write("\t"+str(int(colors[i].color.r*255))+","+str(int(colors[i].color.g*255))+","+str(int(colors[i].color.b*255))+", 0") f.write("\t"+str(int(colors[i].color.r*255))+","+str(int(colors[i].color.g*255))+","+str(int(colors[i].color.b*255))+", 0")
if i != len(colors) - 1: if i != len(colors) - 1:
f.write(",") f.write(",")
f.write("\n") f.write("\n")
# If no vertex colors, default to 2 whites, 1 grey # If no vertex colors, default to 2 whites, 1 grey
else: else:
for i in range(len(m.polygons) * 3): for i in range(len(m.polygons) * 3):
if i % 3 == 0: if i % 3 == 0:
f.write("\t80,80,80,0") # Let's add a bit more relief with a shade of grey
f.write("\t80,80,80,0")
else: else:
f.write("\t128,128,128,0") f.write("\t128,128,128,0")
if i != (len(m.polygons) * 3) - 1: if i != (len(m.polygons) * 3) - 1:
f.write(",") f.write(",")
f.write("\n") f.write("\n")
f.write("};\n\n") f.write("};\n\n")
# Write polygons index + type # Write polygons index + type
f.write("PRIM "+"model"+cleanName+"_index[] = {\n") f.write("PRIM "+"model"+cleanName+"_index[] = {\n")
for i in range(len(m.polygons)): for i in range(len(m.polygons)):
poly = m.polygons[i] poly = m.polygons[i]
f.write("\t"+str(poly.vertices[0])+","+str(poly.vertices[1])+","+str(poly.vertices[2])) f.write("\t"+str(poly.vertices[0])+","+str(poly.vertices[1])+","+str(poly.vertices[2]))
if len(poly.vertices) > 3: if len(poly.vertices) > 3:
f.write("," + str(poly.vertices[3]) + ",8") f.write("," + str(poly.vertices[3]) + ",8")
else: else:
f.write(",0,4") f.write(",0,4")
if i != len(m.polygons) - 1: if i != len(m.polygons) - 1:
f.write(",") f.write(",")
f.write("\n") f.write("\n")
f.write("};\n\n") f.write("};\n\n")
# get custom properties isRigidBody, isPrism, isAnim # Get object's custom properties
chkProp = { chkProp = {
'isAnim':0, 'isAnim':0,
'isRigidBody':0, 'isRigidBody':0,
@ -488,16 +741,24 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
if m.get('isLevel'): if m.get('isLevel'):
nodePtr = cleanName nodePtr = cleanName
# write vertex anim if isAnim != 0 # https://stackoverflow.com/questions/9138637/vertex-animation-exporter-for-blender
## Vertex animation
# write vertex anim if isAnim != 0
# Source : https://stackoverflow.com/questions/9138637/vertex-animation-exporter-for-blender
if m.get("isAnim") is not None and m["isAnim"] != 0: if m.get("isAnim") is not None and m["isAnim"] != 0:
#write vertex pos # Write vertex pos
o = bpy.data.objects[m.name] o = bpy.data.objects[m.name]
# ~ frame_start = bpy.context.scene.frame_start # ~ frame_start = bpy.context.scene.frame_start
frame_start = int(bpy.data.actions[m.name].frame_range[0]) frame_start = int(bpy.data.actions[m.name].frame_range[0])
# ~ frame_end = bpy.context.scene.frame_end # ~ frame_end = bpy.context.scene.frame_end
frame_end = int(bpy.data.actions[m.name].frame_range[1]) frame_end = int(bpy.data.actions[m.name].frame_range[1])
nFrame = frame_end - frame_start nFrame = frame_end - frame_start
@ -509,11 +770,13 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
for i in range(frame_start, frame_end): for i in range(frame_start, frame_end):
bpy.context.scene.frame_set(i) bpy.context.scene.frame_set(i)
bpy.context.scene.update() bpy.context.scene.update()
nm = o.to_mesh(bpy.context.scene, True, 'PREVIEW') nm = o.to_mesh(bpy.context.scene, True, 'PREVIEW')
if i == 0 : if i == 0 :
f.write("VANIM model"+cleanName+"_anim = {\n" + f.write("VANIM model"+cleanName+"_anim = {\n" +
"\t" + str(nFrame) + ",\n" + "\t" + str(nFrame) + ",\n" +
"\t" + str(len(nm.vertices)) + ",\n" + "\t" + str(len(nm.vertices)) + ",\n" +
@ -523,28 +786,41 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
"\t" + str(chkProp['lerp']) + ",\n" + "\t" + str(chkProp['lerp']) + ",\n" +
"\t{\n" "\t{\n"
) )
for v in range(len(nm.vertices)): for v in range(len(nm.vertices)):
if v == 0: if v == 0:
# ~ f.write("{\n")
f.write("\t\t//Frame %d\n" % i) f.write("\t\t//Frame %d\n" % i)
f.write("\t\t{ " + str(round(nm.vertices[v].co.x*scale)) + "," + str(round(-nm.vertices[v].co.z*scale)) + "," + str(round(nm.vertices[v].co.y*scale)) + " }") f.write("\t\t{ " + str(round(nm.vertices[v].co.x*scale)) + "," + str(round(-nm.vertices[v].co.z*scale)) + "," + str(round(nm.vertices[v].co.y*scale)) + " }")
if c != len(nm.vertices) * (nFrame) * 3 - 3: if c != len(nm.vertices) * (nFrame) * 3 - 3:
f.write(",\n") f.write(",\n")
if v == len(nm.vertices) - 1: if v == len(nm.vertices) - 1:
f.write("\n") f.write("\n")
c += 3; c += 3;
# ~ if i != (frame_end - frame_start):
# ~ f.write(",")
tmp_meshes.append(nm) tmp_meshes.append(nm)
# ~ tmp_meshes.remove(nm)
f.write("\n\t}\n};\n") f.write("\n\t}\n};\n")
# Remove meshe's working copies
for nm in tmp_meshes: for nm in tmp_meshes:
bpy.data.meshes.remove(nm) bpy.data.meshes.remove(nm)
#Stuff # ~ bpy.data.objects[bpy.data.meshes[0].name].active_shape_key.value : access shape_key # bpy.data.objects[bpy.data.meshes[0].name].active_shape_key.value : access shape_key
## Mesh world transform setup
# Write object matrix, rot and pos vectors
#write object matrix, rot and pos vectors
f.write("MATRIX model"+cleanName+"_matrix = {0};\n" + f.write("MATRIX model"+cleanName+"_matrix = {0};\n" +
"VECTOR model"+cleanName+"_pos = {"+ str(round(bpy.data.objects[m.name].location.x * scale)) + "," + str(round(-bpy.data.objects[m.name].location.z * scale)) + "," + str(round(bpy.data.objects[m.name].location.y * scale)) + ", 0};\n" + "VECTOR model"+cleanName+"_pos = {"+ str(round(bpy.data.objects[m.name].location.x * scale)) + "," + str(round(-bpy.data.objects[m.name].location.z * scale)) + "," + str(round(bpy.data.objects[m.name].location.y * scale)) + ", 0};\n" +
"SVECTOR model"+cleanName+"_rot = {"+ str(round(degrees(bpy.data.objects[m.name].rotation_euler.x)/360 * 4096)) + "," + str(round(degrees(-bpy.data.objects[m.name].rotation_euler.z)/360 * 4096)) + "," + str(round(degrees(bpy.data.objects[m.name].rotation_euler.y)/360 * 4096)) + "};\n" + "SVECTOR model"+cleanName+"_rot = {"+ str(round(degrees(bpy.data.objects[m.name].rotation_euler.x)/360 * 4096)) + "," + str(round(degrees(-bpy.data.objects[m.name].rotation_euler.z)/360 * 4096)) + "," + str(round(degrees(bpy.data.objects[m.name].rotation_euler.y)/360 * 4096)) + "};\n" +
@ -570,67 +846,102 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
"\t};\n\n") "\t};\n\n")
# Write TMESH struct # Write TMESH struct
f.write("TMESH "+"model"+cleanName+" = {\n") f.write("TMESH "+"model"+cleanName+" = {\n")
f.write("\t"+"model"+cleanName+"_mesh, \n") f.write("\t"+"model"+cleanName+"_mesh, \n")
f.write("\t"+"model"+cleanName+"_normal,\n") f.write("\t"+"model"+cleanName+"_normal,\n")
if len(m.uv_textures) != None: if len(m.uv_textures) != None:
for t in range(len(m.uv_textures)): for t in range(len(m.uv_textures)):
if m.uv_textures[0].data[0].image != None: if m.uv_textures[0].data[0].image != None:
f.write("\t"+"model"+cleanName+"_uv,\n") f.write("\t"+"model"+cleanName+"_uv,\n")
else: else:
f.write("\t0,\n") f.write("\t0,\n")
else: else:
f.write("\t0,\n") f.write("\t0,\n")
f.write("\t"+"model"+cleanName+"_color, \n") f.write("\t"+"model"+cleanName+"_color, \n")
# According to libgte.h, TMESH.len should be # of vertices. Meh... # According to libgte.h, TMESH.len should be # of vertices. Meh...
f.write("\t"+str(len(m.polygons))+"\n") f.write("\t"+str(len(m.polygons))+"\n")
f.write("};\n\n") f.write("};\n\n")
# write texture binary name and declare TIM_IMAGE # Write texture binary name and declare TIM_IMAGE
# by default, load the file from the TIM folder # By default, loads the file from the ./TIM folder
# ~ if len(m.uv_textures) != 0:
if len(m.uv_textures) != None: if len(m.uv_textures) != None:
for t in range(len(m.uv_textures)): for t in range(len(m.uv_textures)):
if m.uv_textures[0].data[0].image != None: if m.uv_textures[0].data[0].image != None:
tex_name = texture_image.name tex_name = texture_image.name
prefix = str.partition(tex_name, ".")[0].replace('-','_') prefix = str.partition(tex_name, ".")[0].replace('-','_')
prefix = CleanName(prefix) prefix = CleanName(prefix)
# add Tex name to array if !exist
# Add Tex name to list if it's not in there already
if prefix in timList: if prefix in timList:
break break
else: else:
f.write("extern unsigned long "+"_binary_TIM_" + prefix + "_tim_start[];\n") f.write("extern unsigned long "+"_binary_TIM_" + prefix + "_tim_start[];\n")
f.write("extern unsigned long "+"_binary_TIM_" + prefix + "_tim_end[];\n") f.write("extern unsigned long "+"_binary_TIM_" + prefix + "_tim_end[];\n")
f.write("extern unsigned long "+"_binary_TIM_" + prefix + "_tim_length;\n\n") f.write("extern unsigned long "+"_binary_TIM_" + prefix + "_tim_length;\n\n")
f.write("TIM_IMAGE tim_" + prefix + ";\n\n") f.write("TIM_IMAGE tim_" + prefix + ";\n\n")
timList.append(prefix) timList.append(prefix)
f.write("MESH mesh"+cleanName+" = {\n") f.write("MESH mesh"+cleanName+" = {\n")
f.write("\t&model"+ cleanName +",\n") f.write("\t&model"+ cleanName +",\n")
f.write("\tmodel" + cleanName + "_index,\n") f.write("\tmodel" + cleanName + "_index,\n")
if len(m.uv_textures) != None: if len(m.uv_textures) != None:
for t in range(len(m.uv_textures)): for t in range(len(m.uv_textures)):
if m.uv_textures[0].data[0].image != None: if m.uv_textures[0].data[0].image != None:
tex_name = texture_image.name tex_name = texture_image.name
prefix = str.partition(tex_name, ".")[0].replace('-','_') prefix = str.partition(tex_name, ".")[0].replace('-','_')
prefix = CleanName(prefix) prefix = CleanName(prefix)
f.write("\t&tim_"+ prefix + ",\n") f.write("\t&tim_"+ prefix + ",\n")
f.write("\t_binary_TIM_" + prefix + "_tim_start,\n") f.write("\t_binary_TIM_" + prefix + "_tim_start,\n")
else: else:
f.write("\t0,\n" + f.write("\t0,\n" +
"\t0,\n") "\t0,\n")
else: else:
f.write("\t0,\n" + f.write("\t0,\n" +
"\t0,\n") "\t0,\n")
f.write("\t&model"+cleanName+"_matrix,\n" + f.write("\t&model"+cleanName+"_matrix,\n" +
@ -646,83 +957,115 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
"\t&model"+cleanName+"_p,\n" + "\t&model"+cleanName+"_p,\n" +
"\t&model"+cleanName+"_OTz,\n" + "\t&model"+cleanName+"_OTz,\n" +
"\t&model"+cleanName+"_body") "\t&model"+cleanName+"_body")
if m.get("isAnim") is not None and m["isAnim"] != 0: if m.get("isAnim") is not None and m["isAnim"] != 0:
f.write(",\n\t&model"+cleanName+"_anim\n") f.write(",\n\t&model"+cleanName+"_anim\n")
else: else:
f.write("\n") f.write("\n")
f.write("};\n\n") f.write("};\n\n")
f.write("MESH * meshes[" + str(len(bpy.data.meshes)) + "] = {\n") f.write("MESH * meshes[" + str(len(bpy.data.meshes)) + "] = {\n")
for k in range(len(bpy.data.meshes)): for k in range(len(bpy.data.meshes)):
cleanName = CleanName(bpy.data.meshes[k].name) cleanName = CleanName(bpy.data.meshes[k].name)
f.write("\t&mesh" + cleanName) f.write("\t&mesh" + cleanName)
if k != len(bpy.data.meshes) - 1: if k != len(bpy.data.meshes) - 1:
f.write(",\n") f.write(",\n")
f.write("\n}; \n") f.write("\n}; \n")
# nothing in camAngles, use default camera # If camAngles is empty, use default camera, and do not include pre-calculated backgrounds
if not camAngles: if not camAngles:
f.write("CAMANGLE camAngle_" + CleanName(defaultCam) + " = {\n" + f.write("CAMANGLE camAngle_" + CleanName(defaultCam) + " = {\n" +
"\t&camPos_" + CleanName(defaultCam) + ",\n" + "\t&camPos_" + CleanName(defaultCam) + ",\n" +
"\t0,\n" + "\t0,\n" +
"\t0\n" + "\t0\n" +
"};\n\n") "};\n\n")
# cam angles is populated # If camAngles is populated, use backgrounds and camera angles
for o in camAngles: for o in camAngles:
prefix = CleanName(o.name) prefix = CleanName(o.name)
# include Tim data # Include Tim data
f.write("extern unsigned long "+"_binary_TIM_bg_" + prefix + "_tim_start[];\n") f.write("extern unsigned long "+"_binary_TIM_bg_" + prefix + "_tim_start[];\n")
f.write("extern unsigned long "+"_binary_TIM_bg_" + prefix + "_tim_end[];\n") f.write("extern unsigned long "+"_binary_TIM_bg_" + prefix + "_tim_end[];\n")
f.write("extern unsigned long "+"_binary_TIM_bg_" + prefix + "_tim_length;\n\n") f.write("extern unsigned long "+"_binary_TIM_bg_" + prefix + "_tim_length;\n\n")
# write corresponding TIM_IMAGE struct # Write corresponding TIM_IMAGE struct
f.write("TIM_IMAGE tim_bg_" + prefix + ";\n\n") f.write("TIM_IMAGE tim_bg_" + prefix + ";\n\n")
# write corresponding CamAngle struct # Write corresponding CamAngle struct
f.write("CAMANGLE camAngle_" + prefix + " = {\n" + f.write("CAMANGLE camAngle_" + prefix + " = {\n" +
"\t&camPos_" + prefix + ",\n" + "\t&camPos_" + prefix + ",\n" +
"\t&tim_bg_" + prefix + ",\n" + "\t&tim_bg_" + prefix + ",\n" +
"\t_binary_TIM_bg_" + prefix + "_tim_start\n" + "\t_binary_TIM_bg_" + prefix + "_tim_start\n" +
"};\n\n") "};\n\n")
# write cam angles array for loops # Write camera angles in an array for loops
f.write("CAMANGLE * camAngles[" + str(len(camAngles)) + "] = {\n") f.write("CAMANGLE * camAngles[" + str(len(camAngles)) + "] = {\n")
for o in camAngles: for o in camAngles:
prefix = CleanName(o.name) prefix = CleanName(o.name)
f.write("\t&camAngle_" + prefix + ",\n") f.write("\t&camAngle_" + prefix + ",\n")
f.write("};\n\n") f.write("};\n\n")
f.write("MESH * actorPtr = &mesh" + actorPtr + ";\n") f.write("MESH * actorPtr = &mesh" + actorPtr + ";\n")
f.write("MESH * levelPtr = &mesh" + levelPtr + ";\n") f.write("MESH * levelPtr = &mesh" + levelPtr + ";\n")
f.write("MESH * propPtr = &mesh" + propPtr + ";\n\n") f.write("MESH * propPtr = &mesh" + propPtr + ";\n\n")
f.write("CAMANGLE * camPtr = &camAngle_" + CleanName(defaultCam) + ";\n\n") f.write("CAMANGLE * camPtr = &camAngle_" + CleanName(defaultCam) + ";\n\n")
f.write("NODE * curNode = &node_" + nodePtr + ";\n\n") f.write("NODE * curNode = &node" + nodePtr + ";\n\n")
# Set default camera back in Blender
# set default cam back
if defaultCam != 'NULL': if defaultCam != 'NULL':
bpy.context.scene.camera = bpy.data.objects[defaultCam] bpy.context.scene.camera = bpy.data.objects[defaultCam]
f.close() f.close()
return {'FINISHED'}; return {'FINISHED'};
def menu_func(self, context): def menu_func(self, context):
self.layout.operator(ExportMyFormat.bl_idname, text="PSX Format(.c)"); self.layout.operator(ExportMyFormat.bl_idname, text="PSX Format(.c)");
def register(): def register():
bpy.utils.register_module(__name__); bpy.utils.register_module(__name__);
bpy.types.INFO_MT_file_export.append(menu_func); bpy.types.INFO_MT_file_export.append(menu_func);
def unregister(): def unregister():
bpy.utils.unregister_module(__name__); bpy.utils.unregister_module(__name__);
bpy.types.INFO_MT_file_export.remove(menu_func); bpy.types.INFO_MT_file_export.remove(menu_func);
if __name__ == "__main__": if __name__ == "__main__":
register() register()