Compare commits
No commits in common. "3ff68c37cd1d2848c4e400fbaff18a393e28e1b3" and "142e394a9ae63124c12ca5c8a5d13ccbf1334c7e" have entirely different histories.
3ff68c37cd
...
142e394a9a
387
__init__.py
387
__init__.py
@ -1,387 +0,0 @@
|
|||||||
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(str.encode(a.name))
|
|
||||||
bytes.append(0)
|
|
||||||
|
|
||||||
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
|
|
||||||
bytes.extend(str.encode(mesh.name))
|
|
||||||
bytes.append(0)
|
|
||||||
|
|
||||||
#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…
x
Reference in New Issue
Block a user