Fixed how strings are written.
This commit is contained in:
parent
142e394a9a
commit
9c39d1de13
389
__init__.py
Normal file
389
__init__.py
Normal file
@ -0,0 +1,389 @@
|
||||
bl_info = {
|
||||
"name": "Export Event Horizon Model (.ehm)",
|
||||
"author": "Arron Nelson (AKA Karutoh)",
|
||||
"version": (1, 0, 0),
|
||||
"blender": (3, 1, 0),
|
||||
"location": "File > Export > EHM",
|
||||
"description": "The script exports Blender geometry to a Event Horizon Model file format.",
|
||||
"category": "Import-Export"
|
||||
}
|
||||
|
||||
import sys
|
||||
import struct
|
||||
import mathutils
|
||||
import bmesh
|
||||
|
||||
import bpy
|
||||
from bpy_extras.io_utils import ExportHelper, axis_conversion
|
||||
from bpy.props import StringProperty, BoolProperty, EnumProperty
|
||||
from bpy.types import Operator, Mesh
|
||||
|
||||
"""
|
||||
PropertyChange.type == 0 //X-Axis Position
|
||||
PropertyChange.type == 1 //Y-Axis Position
|
||||
PropertyChange.type == 2 //Z-Axis Position
|
||||
PropertyChange.type == 3 //X-Axis Scale
|
||||
PropertyChange.type == 4 //Y-Axis Scale
|
||||
PropertyChange.type == 5 //Z-Axis Scale
|
||||
PropertyChange.type == 6 //X-Axis Rotation (Quat)
|
||||
PropertyChange.type == 7 //Y-Axis Rotation (Quat)
|
||||
PropertyChange.type == 8 //Z-Axis Rotation (Quat)
|
||||
PropertyChange.type == 9 //W-Axis Rotation (Quat)
|
||||
"""
|
||||
|
||||
class KeyFrame:
|
||||
def __init__(self, num, timeStamp):
|
||||
self.num = num
|
||||
self.timeStamp = timeStamp
|
||||
self.pos = mathutils.Vector((0.0, 0.0, 0.0))
|
||||
self.rot = mathutils.Quaternion()
|
||||
self.scale = mathutils.Vector((0.0, 0.0, 0.0))
|
||||
|
||||
class BoneAnimation:
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
self.keyFrames = []
|
||||
|
||||
def GetKeyFrame(self, num):
|
||||
for frame in self.keyFrames:
|
||||
if frame.num == num:
|
||||
return frame
|
||||
|
||||
return None
|
||||
|
||||
def AddKeyFrame(self, keyFrame):
|
||||
if self.GetKeyFrame(keyFrame.num) != None:
|
||||
return None
|
||||
|
||||
self.keyFrames.append(keyFrame)
|
||||
|
||||
return self.keyFrames[len(self.keyFrames) - 1]
|
||||
|
||||
def Triangulate(mesh):
|
||||
#mesh.update_from_editmode()
|
||||
#mesh.data.calc_normals_split()
|
||||
|
||||
edit = bmesh.new()
|
||||
edit.from_mesh(mesh.data)
|
||||
bmesh.ops.triangulate(edit, faces = edit.faces, quad_method='BEAUTY', ngon_method='BEAUTY')
|
||||
edit.to_mesh(mesh.data)
|
||||
edit.free()
|
||||
|
||||
def FindBIndexByGIndex(mesh, gi, skeletons):
|
||||
for s in skeletons:
|
||||
for bi, b in enumerate(s.data.bones):
|
||||
if mesh.vertex_groups.find(b.name) == gi:
|
||||
return bi
|
||||
|
||||
return 0xFF
|
||||
|
||||
def ExportSkeletons(bytes, skeletons):
|
||||
if len(skeletons) >= 1:
|
||||
#Bone Count
|
||||
bytes.extend(struct.pack("<B", len(skeletons[0].data.bones)))
|
||||
|
||||
for b in skeletons[0].data.bones:
|
||||
bytes.extend(struct.pack("<Q", len(b.name)))
|
||||
bytes.extend(str.encode(b.name))
|
||||
|
||||
|
||||
flipMatrix = axis_conversion(from_forward='Y', from_up='Z', to_forward='Z', to_up='Y').to_4x4()
|
||||
|
||||
if b.parent is None:
|
||||
bytes.extend(struct.pack("<B", 0xFF))
|
||||
WriteMat4(bytes, flipMatrix @ b.matrix_local);
|
||||
else:
|
||||
bytes.extend(struct.pack("<B", skeletons[0].data.bones.find(b.parent.name)))
|
||||
WriteMat4(bytes, flipMatrix @ (b.parent.matrix_local.inverted() @ b.matrix_local));
|
||||
|
||||
WriteMat4(bytes, (flipMatrix @ b.matrix_local).inverted());
|
||||
else:
|
||||
#Bone Count
|
||||
bytes.extend(struct.pack("<B", 0))
|
||||
|
||||
def ExportAnimations(bytes, skeletons, animations):
|
||||
if len(skeletons) >= 1:
|
||||
fps = bpy.context.scene.render.fps / bpy.context.scene.render.fps_base
|
||||
|
||||
#Animation Count
|
||||
bytes.extend(struct.pack("<Q", len(animations)))
|
||||
|
||||
for a in animations:
|
||||
bpy.context.view_layer.objects.active = skeletons[0]
|
||||
bpy.context.object.animation_data_create()
|
||||
bpy.context.object.animation_data.action = a
|
||||
|
||||
#Animation Name
|
||||
bytes.extend(struct.pack("<Q", len(a.name)))
|
||||
bytes.extend(str.encode(a.name))
|
||||
|
||||
duration = 0.0
|
||||
boneAnims = []
|
||||
|
||||
for i, b in enumerate(skeletons[0].data.bones):
|
||||
for f in a.fcurves:
|
||||
if f.data_path == f'pose.bones["{b.name}"].location':
|
||||
result = None
|
||||
|
||||
for ba in boneAnims:
|
||||
if ba.id == i:
|
||||
result = ba
|
||||
|
||||
if result == None:
|
||||
boneAnims.append(BoneAnimation(i))
|
||||
result = boneAnims[len(boneAnims) - 1]
|
||||
|
||||
for k in f.keyframe_points:
|
||||
keyFrame = result.GetKeyFrame(k.co.x)
|
||||
if keyFrame == None:
|
||||
keyFrame = result.AddKeyFrame(KeyFrame(k.co.x, k.co.x / fps))
|
||||
if keyFrame.timeStamp > duration:
|
||||
duration = keyFrame.timeStamp
|
||||
|
||||
if f.array_index == 0:
|
||||
keyFrame.pos.x = k.co.y
|
||||
elif f.array_index == 1:
|
||||
keyFrame.pos.z = k.co.y
|
||||
elif f.array_index == 2:
|
||||
keyFrame.pos.y = k.co.y
|
||||
|
||||
elif f.data_path == f'pose.bones["{b.name}"].scale':
|
||||
result = None
|
||||
|
||||
for ba in boneAnims:
|
||||
if ba.id == i:
|
||||
result = ba
|
||||
|
||||
if result == None:
|
||||
boneAnims.append(BoneAnimation(i))
|
||||
result = boneAnims[len(boneAnims) - 1]
|
||||
|
||||
for k in f.keyframe_points:
|
||||
keyFrame = result.GetKeyFrame(k.co.x)
|
||||
if keyFrame == None:
|
||||
keyFrame = result.AddKeyFrame(KeyFrame(k.co.x, k.co.x / fps))
|
||||
if keyFrame.timeStamp > duration:
|
||||
duration = keyFrame.timeStamp
|
||||
|
||||
if f.array_index == 0:
|
||||
keyFrame.scale.x = k.co.y
|
||||
elif f.array_index == 1:
|
||||
keyFrame.scale.z = k.co.y
|
||||
elif f.array_index == 2:
|
||||
keyFrame.scale.y = k.co.y
|
||||
|
||||
elif f.data_path == f'pose.bones["{b.name}"].rotation_quaternion':
|
||||
result = None
|
||||
|
||||
for ba in boneAnims:
|
||||
if ba.id == i:
|
||||
result = ba
|
||||
|
||||
if result == None:
|
||||
boneAnims.append(BoneAnimation(i))
|
||||
result = boneAnims[len(boneAnims) - 1]
|
||||
|
||||
for k in f.keyframe_points:
|
||||
keyFrame = result.GetKeyFrame(k.co.x)
|
||||
if keyFrame == None:
|
||||
keyFrame = result.AddKeyFrame(KeyFrame(k.co.x, k.co.x / fps))
|
||||
if keyFrame.timeStamp > duration:
|
||||
duration = keyFrame.timeStamp
|
||||
|
||||
if f.array_index == 0:
|
||||
keyFrame.rot.w = k.co.y
|
||||
elif f.array_index == 1:
|
||||
keyFrame.rot.x = k.co.y
|
||||
elif f.array_index == 2:
|
||||
keyFrame.rot.y = k.co.y
|
||||
elif f.array_index == 3:
|
||||
keyFrame.rot.z = k.co.y
|
||||
|
||||
#Duration
|
||||
bytes.extend(struct.pack("<f", duration))
|
||||
|
||||
#Change Count
|
||||
bytes.extend(struct.pack("<B", len(boneAnims)))
|
||||
|
||||
for ba in boneAnims:
|
||||
#Bone Id
|
||||
bytes.extend(struct.pack("<B", ba.id))
|
||||
|
||||
#Key Frame Count
|
||||
bytes.extend(struct.pack("<Q", len(ba.keyFrames)))
|
||||
|
||||
for kf in ba.keyFrames:
|
||||
#Key Frame Number
|
||||
bytes.extend(struct.pack("<f", kf.num))
|
||||
|
||||
#Key Frame Time Stamp
|
||||
bytes.extend(struct.pack("<f", kf.timeStamp))
|
||||
|
||||
#Position
|
||||
bytes.extend(struct.pack("<f", kf.pos.x))
|
||||
bytes.extend(struct.pack("<f", kf.pos.y))
|
||||
bytes.extend(struct.pack("<f", kf.pos.z))
|
||||
|
||||
#Rotation
|
||||
bytes.extend(struct.pack("<f", kf.rot.w))
|
||||
bytes.extend(struct.pack("<f", kf.rot.x))
|
||||
bytes.extend(struct.pack("<f", kf.rot.y))
|
||||
bytes.extend(struct.pack("<f", kf.rot.z))
|
||||
|
||||
#Scale
|
||||
bytes.extend(struct.pack("<f", kf.scale.x))
|
||||
bytes.extend(struct.pack("<f", kf.scale.y))
|
||||
bytes.extend(struct.pack("<f", kf.scale.z))
|
||||
else:
|
||||
bytes.extend(struct.pack("<Q", 0))
|
||||
|
||||
def WriteMat4(bytes, mat):
|
||||
for x in range(4):
|
||||
for y in range(4):
|
||||
bytes.extend(struct.pack("<f", mat[y][x]))
|
||||
|
||||
def WriteMeshes(bytes, meshes, skeletons, animations):
|
||||
newTrans = axis_conversion(from_forward='Y', from_up='Z', to_forward='Z', to_up='Y').to_4x4()
|
||||
|
||||
#Mesh Count
|
||||
bytes.extend(struct.pack("<Q", len(meshes)))
|
||||
|
||||
for mesh in meshes:
|
||||
mesh.data.transform(newTrans)
|
||||
|
||||
vertBuff = []
|
||||
uvBuff = []
|
||||
faceBuff = []
|
||||
|
||||
Triangulate(mesh)
|
||||
|
||||
#Mesh Name Count
|
||||
bytes.extend(struct.pack("<Q", len(mesh.name)))
|
||||
|
||||
#Mesh Name
|
||||
bytes.extend(str.encode(mesh.name))
|
||||
|
||||
#CloneMesh(mesh)
|
||||
for i, loop in enumerate(mesh.data.loops):
|
||||
thisVertex = mesh.data.vertices[loop.vertex_index]
|
||||
thisUV = mesh.data.uv_layers.active.data[i].uv
|
||||
|
||||
#check if already in the list
|
||||
found = 0
|
||||
for i in range(len(vertBuff)):
|
||||
if(abs(vertBuff[i].co.x - thisVertex.co.x) <= max(1e-09 * max(abs(vertBuff[i].co.x), abs(thisVertex.co.x)), 0.0)):
|
||||
if(abs(vertBuff[i].co.y - thisVertex.co.y) <= max(1e-09 * max(abs(vertBuff[i].co.y), abs(thisVertex.co.y)), 0.0)):
|
||||
if(abs(vertBuff[i].co.z - thisVertex.co.z) <= max(1e-09 * max(abs(vertBuff[i].co.z), abs(thisVertex.co.z)), 0.0)):
|
||||
if(abs(uvBuff[i].x - thisUV.x) <= max(1e-09 * max(abs(uvBuff[i].x), abs(thisUV.x)), 0.0)):
|
||||
if(abs(uvBuff[i].y - thisUV.y) <= max(1e-09 * max(abs(uvBuff[i].y), abs(thisUV.y)), 0.0)):
|
||||
faceBuff.append(int(i))
|
||||
found = 1
|
||||
break
|
||||
|
||||
#otherwise stash a new vertex
|
||||
if found == 0:
|
||||
faceBuff.append(len(vertBuff)) #index
|
||||
vertBuff.append(thisVertex) #vertex obj
|
||||
uvBuff.append(thisUV) #float, float
|
||||
|
||||
#Vertex Count
|
||||
bytes.extend(struct.pack("<Q", len(vertBuff)))
|
||||
|
||||
for i in range(len(vertBuff)):
|
||||
#Coordinate
|
||||
bytes.extend(struct.pack("<f", vertBuff[i].co.x))
|
||||
bytes.extend(struct.pack("<f", vertBuff[i].co.y))
|
||||
bytes.extend(struct.pack("<f", vertBuff[i].co.z))
|
||||
|
||||
#Normal
|
||||
bytes.extend(struct.pack("<f", vertBuff[i].normal.x))
|
||||
bytes.extend(struct.pack("<f", vertBuff[i].normal.y))
|
||||
bytes.extend(struct.pack("<f", vertBuff[i].normal.z))
|
||||
|
||||
#UV
|
||||
bytes.extend(struct.pack("<f", uvBuff[i].x))
|
||||
bytes.extend(struct.pack("<f", 1.0 - uvBuff[i].y))
|
||||
|
||||
#Vertex Bones/Weights
|
||||
for gi in range(4):
|
||||
if gi < len(vertBuff[i].groups):
|
||||
bytes.extend(struct.pack("<B", FindBIndexByGIndex(mesh, vertBuff[i].groups[gi].group, skeletons)))
|
||||
else:
|
||||
bytes.extend(struct.pack("<B", 0xFF))
|
||||
|
||||
for gi in range(4):
|
||||
if gi < len(vertBuff[i].groups):
|
||||
bytes.extend(struct.pack("<f", vertBuff[i].groups[gi].weight))
|
||||
else:
|
||||
bytes.extend(struct.pack("<f", 0.0))
|
||||
|
||||
|
||||
#Index Count
|
||||
bytes.extend(struct.pack("<Q", len(faceBuff)))
|
||||
|
||||
for i in faceBuff:
|
||||
bytes.extend(struct.pack("<I", i))
|
||||
|
||||
ExportSkeletons(bytes, skeletons)
|
||||
ExportAnimations(bytes, skeletons, animations)
|
||||
|
||||
mesh.data.transform(newTrans.inverted())
|
||||
|
||||
def Write(context, filepath):
|
||||
f = open(filepath, "wb")
|
||||
|
||||
bytes = bytearray()
|
||||
|
||||
|
||||
#Version
|
||||
bytes.extend(struct.pack("<I", 1))
|
||||
bytes.extend(struct.pack("<I", 0))
|
||||
bytes.extend(struct.pack("<I", 0))
|
||||
|
||||
meshes = []
|
||||
skeletons = []
|
||||
animations = bpy.data.actions
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "MESH":
|
||||
meshes.append(obj)
|
||||
elif obj.type == "ARMATURE":
|
||||
skeletons.append(obj)
|
||||
|
||||
WriteMeshes(bytes, meshes, skeletons, animations)
|
||||
|
||||
f.write(bytes)
|
||||
|
||||
f.close()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class ExportEHM(Operator, ExportHelper):
|
||||
"""Export to the Event Horizon Model format (.ehm)"""
|
||||
bl_idname = "export.ehm"
|
||||
bl_label = "Export EHM"
|
||||
filename_ext = ".ehm"
|
||||
|
||||
filter_glob: StringProperty(
|
||||
default="*.ehm",
|
||||
options={'HIDDEN'},
|
||||
maxlen=255
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
return Write(context, self.filepath)
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(ExportEHM.bl_idname, text="Event Horizon Model")
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(ExportEHM)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func)
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(ExportEHM)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
bpy.ops.export.ehm('INVOKE_DEFAULT')
|
Loading…
Reference in New Issue
Block a user