diff --git a/add_mesh_spindle.py b/add_mesh_spindle.py new file mode 100644 index 0000000000000000000000000000000000000000..2fef9c618238d8051cf169895b5e1f103ba90970 --- /dev/null +++ b/add_mesh_spindle.py @@ -0,0 +1,168 @@ +# add_mesh_spindle.py Copyright (C) 2008-2009, FourMadMen.com +# +# add spindle to the blender 2.50 add->mesh menu +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ***** END GPL LICENCE BLOCK ***** + +bl_addon_info = { + 'name': 'Add Mesh: Spindle', + 'author': 'fourmadmen', + 'version': '2.0', + 'blender': (2, 5, 3), + 'location': 'View3D > Add > Mesh ', + 'description': 'Adds a mesh Spindle to the Add Mesh menu', + 'url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/Add_Spindle', + 'category': 'Add Mesh'} + +""" +Name: 'Spindle' +Blender: 250 +Group: 'AddMesh' +Tip: 'Add Spindle Object...' +__author__ = ["Four Mad Men", "FourMadMen.com"] +__version__ = '0.10' +__url__ = [""] +email__=["bwiki {at} fourmadmen {dot} com"] + +Usage: + +* Launch from Add Mesh menu + +* Modify parameters as desired or keep defaults + +""" + + +import bpy +import mathutils +from math import pi + + +def add_spindle( spindle_segments, spindle_radius, spindle_height, spindle_cap_height): + + Vector = mathutils.Vector + RotationMatrix = mathutils.RotationMatrix + + verts = [] + faces = [] + + tot_verts = spindle_segments * 2 + 2 + + half_height = spindle_height * .5 + + verts.extend( Vector(0, 0, half_height + spindle_cap_height) ) + verts.extend( Vector(0, 0, -half_height - spindle_cap_height) ) + + i = 2 + for index in range(spindle_segments): + mtx = RotationMatrix( 2 * pi * float(index)/spindle_segments, 3, 'Z' ) + + verts.extend( Vector(spindle_radius, 0, half_height) * mtx ) + it1 = i + i+=1 + + verts.extend( Vector(spindle_radius, 0, -half_height) * mtx ) + ib1 = i + i+=1 + + if i>4: + faces.extend( (it2, it1, 0, it2) ) + faces.extend( (it1, it2, ib2, ib1) ) + faces.extend( (ib1, ib2, 1, ib1) ) + + it2 = it1 + ib2 = ib1 + + faces.extend( (tot_verts-2, 2, 0, tot_verts-2) ) + faces.extend( (3, 2, tot_verts-2, tot_verts-1) ) + faces.extend( (3, tot_verts-1, 1, 3) ) + + return verts, faces + +from bpy.props import FloatProperty +from bpy.props import IntProperty + +class AddSpindle(bpy.types.Operator): + '''Add a spindle mesh.''' + bl_idname = "mesh.spindle_add" + bl_label = "Add Spindle" + bl_options = {'REGISTER', 'UNDO'} + + spindle_segments = IntProperty(name="Segments", + description="Number of segments of the spindle", + default=32, min=3, max=256) + spindle_radius = FloatProperty(name="Radius", + description="Radius of the spindle", + default=1.00, min=0.01, max=100.00) + spindle_height = FloatProperty(name="Height", + description="Height of the spindle", + default=1.00, min=0.01, max=100.00) + spindle_cap_height = FloatProperty(name="Cap Height", + description="Cap height of the spindle", + default=0.50, min=0.01, max=100.00) + + def execute(self, context): + + verts_loc, faces = add_spindle(self.properties.spindle_segments, self.properties.spindle_radius, self.properties.spindle_height, self.properties.spindle_cap_height) + + mesh = bpy.data.meshes.new("Spindle") + + mesh.add_geometry(int(len(verts_loc) / 3), 0, int(len(faces) / 4)) + mesh.verts.foreach_set("co", verts_loc) + mesh.faces.foreach_set("verts_raw", faces) + mesh.faces.foreach_set("smooth", [False] * len(mesh.faces)) + + scene = context.scene + + # ugh + for ob in scene.objects: + ob.selected = False + + mesh.update() + ob_new = bpy.data.objects.new("Spindle", mesh) + ob_new.data = mesh + scene.objects.link(ob_new) + scene.objects.active = ob_new + ob_new.selected = True + + ob_new.location = tuple(context.scene.cursor_location) + + return {'FINISHED'} + + +# Register the operator +# Add to a menu, reuse an icon used elsewhere that happens to have fitting name +# unfortunately, the icon shown is the one I expected from looking at the +# blenderbuttons file from the release/datafiles directory + +menu_func = (lambda self, context: self.layout.operator(AddSpindle.bl_idname, text="Add Spindle", icon='PLUGIN')) + +def register(): + bpy.types.register(AddSpindle) + bpy.types.INFO_MT_mesh_add.append(menu_func) + +def unregister(): + bpy.types.unregister(AddSpindle) + bpy.types.INFO_MT_mesh_add.remove(menu_func) + +# Remove "Spindle" menu from the "Add Mesh" menu. +#space_info.INFO_MT_mesh_add.remove(menu_func) + +if __name__ == "__main__": + register() diff --git a/add_mesh_wedge.py b/add_mesh_wedge.py new file mode 100644 index 0000000000000000000000000000000000000000..eec7005100a9a8afb59820131daa248518b04e9f --- /dev/null +++ b/add_mesh_wedge.py @@ -0,0 +1,151 @@ +# add_mesh_wedge.py Copyright (C) 2008-2009, FourMadMen.com +# +# add wedge to the blender 2.50 add->mesh menu +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ***** END GPL LICENCE BLOCK ***** + +bl_addon_info = { + 'name': 'Add Mesh: Wedge', + 'author': 'fourmadmen', + 'version': '1.1', + 'blender': (2, 5, 3), + 'location': 'View3D > Add > Mesh ', + 'description': 'Adds a mesh Wedge to the Add Mesh menu', + 'url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/Add_Wedge', + 'category': 'Add Mesh'} + + +""" +Name: 'Wedge' +Blender: 250 +Group: 'AddMesh' +Tip: 'Add Wedge Object...' +__author__ = ["Four Mad Men", "FourMadMen.com"] +__version__ = '0.10' +__url__ = [""] +email__=["bwiki {at} fourmadmen {dot} com"] + +Usage: + +* Launch from Add Mesh menu + +* Modify parameters as desired or keep defaults + +""" + + +import bpy +import mathutils + + +def add_wedge( wedge_width, wedge_height, wedge_depth): + + Vector = mathutils.Vector + + verts = [] + faces = [] + + half_width = wedge_width * .5 + half_height = wedge_height * .5 + half_depth = wedge_depth * .5 + + verts.extend( Vector(-half_width, -half_height, half_depth) ) + verts.extend( Vector(-half_width, -half_height, -half_depth) ) + + verts.extend( Vector(half_width, -half_height, half_depth) ) + verts.extend( Vector(half_width, -half_height, -half_depth) ) + + verts.extend( Vector(-half_width, half_height, half_depth) ) + verts.extend( Vector(-half_width, half_height, -half_depth) ) + + faces.extend( [0, 2, 4, 0] ) + faces.extend( [1, 3, 5, 1] ) + faces.extend( [0, 1, 3, 2] ) + faces.extend( [0, 4, 5, 1] ) + faces.extend( [2, 3, 5, 4] ) + + return verts, faces + +from bpy.props import FloatProperty + +class AddWedge(bpy.types.Operator): + '''Add a wedge mesh.''' + bl_idname = "mesh.wedge_add" + bl_label = "Add Wedge" + bl_options = {'REGISTER', 'UNDO'} + + wedge_width = FloatProperty(name="Width", + description="Width of Wedge", + default=2.00, min=0.01, max=100.00) + wedge_height = FloatProperty(name="Height", + description="Height of Wedge", + default=2.00, min=0.01, max=100.00) + wedge_depth = FloatProperty(name="Depth", + description="Depth of Wedge", + default=2.00, min=0.01, max=100.00) + + def execute(self, context): + + verts_loc, faces = add_wedge(self.properties.wedge_width, self.properties.wedge_height, self.properties.wedge_depth) + + mesh = bpy.data.meshes.new("Wedge") + + mesh.add_geometry(int(len(verts_loc) / 3), 0, int(len(faces) / 4)) + mesh.verts.foreach_set("co", verts_loc) + mesh.faces.foreach_set("verts_raw", faces) + mesh.faces.foreach_set("smooth", [False] * len(mesh.faces)) + + scene = context.scene + + # ugh + for ob in scene.objects: + ob.selected = False + + mesh.update() + ob_new = bpy.data.objects.new("Wedge", mesh) + ob_new.data = mesh + scene.objects.link(ob_new) + scene.objects.active = ob_new + ob_new.selected = True + + ob_new.location = tuple(context.scene.cursor_location) + + return {'FINISHED'} + + +# Register the operator +# Add to a menu, reuse an icon used elsewhere that happens to have fitting name +# unfortunately, the icon shown is the one I expected from looking at the +# blenderbuttons file from the release/datafiles directory + +menu_func = (lambda self, context: self.layout.operator(AddWedge.bl_idname, text="Add Wedge", icon='PLUGIN')) + +def register(): + bpy.types.register(AddWedge) + bpy.types.INFO_MT_mesh_add.append(menu_func) + +def unregister(): + bpy.types.unregister(AddWedge) + bpy.types.INFO_MT_mesh_add.remove(menu_func) + +# Remove "Wedge" menu from the "Add Mesh" menu. +#space_info.INFO_MT_mesh_add.remove(menu_func) + +if __name__ == "__main__": + register() diff --git a/export_unreal_psk_psa.py b/export_unreal_psk_psa.py new file mode 100644 index 0000000000000000000000000000000000000000..348800806c356da577830385d3009476ff45061a --- /dev/null +++ b/export_unreal_psk_psa.py @@ -0,0 +1,1588 @@ + # ***** GPL LICENSE BLOCK ***** + # + # This program is free software: you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation, either version 3 of the License, or + # (at your option) any later version. + # + # This program is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with this program. If not, see <http://www.gnu.org/licenses/>. + # All rights reserved. + # ***** GPL LICENSE BLOCK ***** + +bl_addon_info = { + 'name': 'Export: Unreal Skeletal Mesh/Animation (.psk & .psa)', + 'author': 'Darknet', + 'version': '2.0', + 'blender': (2, 5, 3), + 'location': 'File > Export ', + 'description': 'Export Unreal Engine (.psk & .psa)', + 'url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/File_I-O/Unreal_psk_psa', + 'category': 'Import/Export'} + + +""" +Name: 'Unreal Skeletal Mesh/Animation (.psk and .psa) Export' +Blender: 250 +Group: 'Export' +Tooltip: 'Unreal Skeletal Mesh and Animation Export (*.psk, *.psa)' +""" + +__author__ = "Darknet/Optimus_P-Fat/Active_Trash/Sinsoft" +__url__ = ['http://sinsoft.com', 'www.sinsoft.com', 'sinsoft.com'] +__version__ = "0.1.1" + +__bpydoc__ = """\ + +-- Unreal Skeletal Mesh and Animation Export (.psk and .psa) export script v0.0.1 --<br> + +- NOTES: +- This script Exports To Unreal's PSK and PSA file formats for Skeletal Meshes and Animations. <br> +- This script DOES NOT support vertex animation! These require completely different file formats. <br> + +- v0.0.1 +- Initial version + +- v0.0.2 +- This version adds support for more than one material index! + +[ - Edit by: Darknet +- v0.0.3 - v0.0.12 +- This will work on UT3 and it is a stable version that work with vehicle for testing. +- Main Bone fix no dummy needed to be there. +- Just bone issues position, rotation, and offset for psk. +- The armature bone position, rotation, and the offset of the bone is fix. It was to deal with skeleton mesh export for psk. +- Animation is fix for position, offset, rotation bone support one rotation direction when armature build. +- It will convert your mesh into triangular when exporting to psk file. +- Did not work with psa export yet. + +- v0.0.13 +- The animatoin will support different bone rotations when export the animation. + +- v0.0.14 +- Fixed Action set keys frames when there is no pose keys and it will ignore it. + +- v0.0.15 +- Fixed multiple objects when exporting to psk. Select one mesh to export to psk. +- ] + +- v0.1.1 +- Blender 2.50 svn (Support) + +Credit to: +- export_cal3d.py (Position of the Bones Format) +- blender2md5.py (Animation Translation Format) +- export_obj.py (Blender 2.5/Pyhton 3.x Format) + +- freenode #blendercoder -> user -> ideasman42 + +-Give Credit to those who work on this script. +""" + +import os +import time +import datetime +import bpy +import mathutils +import operator + +from struct import pack, calcsize + +MENUPANELBOOL = True + +# REFERENCE MATERIAL JUST IN CASE: +# +# U = x / sqrt(x^2 + y^2 + z^2) +# V = y / sqrt(x^2 + y^2 + z^2) +# +# Triangles specifed counter clockwise for front face +# +#defines for sizeofs +SIZE_FQUAT = 16 +SIZE_FVECTOR = 12 +SIZE_VJOINTPOS = 44 +SIZE_ANIMINFOBINARY = 168 +SIZE_VCHUNKHEADER = 32 +SIZE_VMATERIAL = 88 +SIZE_VBONE = 120 +SIZE_FNAMEDBONEBINARY = 120 +SIZE_VRAWBONEINFLUENCE = 12 +SIZE_VQUATANIMKEY = 32 +SIZE_VVERTEX = 16 +SIZE_VPOINT = 12 +SIZE_VTRIANGLE = 12 + +######################################################################## +# Generic Object->Integer mapping +# the object must be usable as a dictionary key +class ObjMap: + def __init__(self): + self.dict = {} + self.next = 0 + def get(self, obj): + if obj in self.dict: + return self.dict[obj] + else: + id = self.next + self.next = self.next + 1 + self.dict[obj] = id + return id + + def items(self): + getval = operator.itemgetter(0) + getkey = operator.itemgetter(1) + return map(getval, sorted(self.dict.items(), key=getkey)) + +######################################################################## +# RG - UNREAL DATA STRUCTS - CONVERTED FROM C STRUCTS GIVEN ON UDN SITE +# provided here: http://udn.epicgames.com/Two/BinaryFormatSpecifications.html +# updated UDK (Unreal Engine 3): http://udn.epicgames.com/Three/BinaryFormatSpecifications.html +class FQuat: + def __init__(self): + self.X = 0.0 + self.Y = 0.0 + self.Z = 0.0 + self.W = 1.0 + + def dump(self): + data = pack('ffff', self.X, self.Y, self.Z, self.W) + return data + + def __cmp__(self, other): + return cmp(self.X, other.X) \ + or cmp(self.Y, other.Y) \ + or cmp(self.Z, other.Z) \ + or cmp(self.W, other.W) + + def __hash__(self): + return hash(self.X) ^ hash(self.Y) ^ hash(self.Z) ^ hash(self.W) + + def __str__(self): + return "[%f,%f,%f,%f](FQuat)" % (self.X, self.Y, self.Z, self.W) + +class FVector(object): + def __init__(self, X=0.0, Y=0.0, Z=0.0): + self.X = X + self.Y = Y + self.Z = Z + + def dump(self): + data = pack('fff', self.X, self.Y, self.Z) + return data + + def __cmp__(self, other): + return cmp(self.X, other.X) \ + or cmp(self.Y, other.Y) \ + or cmp(self.Z, other.Z) + + def _key(self): + return (type(self).__name__, self.X, self.Y, self.Z) + + def __hash__(self): + return hash(self._key()) + + def __eq__(self, other): + if not hasattr(other, '_key'): + return False + return self._key() == other._key() + + def dot(self, other): + return self.X * other.X + self.Y * other.Y + self.Z * other.Z + + def cross(self, other): + return FVector(self.Y * other.Z - self.Z * other.Y, + self.Z * other.X - self.X * other.Z, + self.X * other.Y - self.Y * other.X) + + def sub(self, other): + return FVector(self.X - other.X, + self.Y - other.Y, + self.Z - other.Z) + +class VJointPos: + def __init__(self): + self.Orientation = FQuat() + self.Position = FVector() + self.Length = 0.0 + self.XSize = 0.0 + self.YSize = 0.0 + self.ZSize = 0.0 + + def dump(self): + data = self.Orientation.dump() + self.Position.dump() + pack('4f', self.Length, self.XSize, self.YSize, self.ZSize) + return data + +class AnimInfoBinary: + def __init__(self): + self.Name = "" # length=64 + self.Group = "" # length=64 + self.TotalBones = 0 + self.RootInclude = 0 + self.KeyCompressionStyle = 0 + self.KeyQuotum = 0 + self.KeyPrediction = 0.0 + self.TrackTime = 0.0 + self.AnimRate = 0.0 + self.StartBone = 0 + self.FirstRawFrame = 0 + self.NumRawFrames = 0 + + def dump(self): + data = pack('64s64siiiifffiii', self.Name, self.Group, self.TotalBones, self.RootInclude, self.KeyCompressionStyle, self.KeyQuotum, self.KeyPrediction, self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, self.NumRawFrames) + return data + +class VChunkHeader: + def __init__(self, name, type_size): + self.ChunkID = name # length=20 + self.TypeFlag = 1999801 # special value + self.DataSize = type_size + self.DataCount = 0 + + def dump(self): + data = pack('20siii', self.ChunkID, self.TypeFlag, self.DataSize, self.DataCount) + return data + +class VMaterial: + def __init__(self): + self.MaterialName = "" # length=64 + self.TextureIndex = 0 + self.PolyFlags = 0 # DWORD + self.AuxMaterial = 0 + self.AuxFlags = 0 # DWORD + self.LodBias = 0 + self.LodStyle = 0 + + def dump(self): + data = pack('64siLiLii', self.MaterialName, self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle) + return data + +class VBone: + def __init__(self): + self.Name = "" # length = 64 + self.Flags = 0 # DWORD + self.NumChildren = 0 + self.ParentIndex = 0 + self.BonePos = VJointPos() + + def dump(self): + data = pack('64sLii', self.Name, self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump() + return data + +#same as above - whatever - this is how Epic does it... +class FNamedBoneBinary: + def __init__(self): + self.Name = "" # length = 64 + self.Flags = 0 # DWORD + self.NumChildren = 0 + self.ParentIndex = 0 + self.BonePos = VJointPos() + + self.IsRealBone = 0 # this is set to 1 when the bone is actually a bone in the mesh and not a dummy + + def dump(self): + data = pack('64sLii', self.Name, self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump() + return data + +class VRawBoneInfluence: + def __init__(self): + self.Weight = 0.0 + self.PointIndex = 0 + self.BoneIndex = 0 + + def dump(self): + data = pack('fii', self.Weight, self.PointIndex, self.BoneIndex) + return data + +class VQuatAnimKey: + def __init__(self): + self.Position = FVector() + self.Orientation = FQuat() + self.Time = 0.0 + + def dump(self): + data = self.Position.dump() + self.Orientation.dump() + pack('f', self.Time) + return data + +class VVertex(object): + def __init__(self): + self.PointIndex = 0 # WORD + self.U = 0.0 + self.V = 0.0 + self.MatIndex = 0 #BYTE + self.Reserved = 0 #BYTE + + def dump(self): + data = pack('HHffBBH', self.PointIndex, 0, self.U, self.V, self.MatIndex, self.Reserved, 0) + return data + + def __cmp__(self, other): + return cmp(self.PointIndex, other.PointIndex) \ + or cmp(self.U, other.U) \ + or cmp(self.V, other.V) \ + or cmp(self.MatIndex, other.MatIndex) \ + or cmp(self.Reserved, other.Reserved) + + def _key(self): + return (type(self).__name__,self.PointIndex, self.U, self.V,self.MatIndex,self.Reserved) + + def __hash__(self): + return hash(self._key()) + + def __eq__(self, other): + if not hasattr(other, '_key'): + return False + return self._key() == other._key() + +class VPoint(object): + def __init__(self): + self.Point = FVector() + + def dump(self): + return self.Point.dump() + + def __cmp__(self, other): + return cmp(self.Point, other.Point) + + def _key(self): + return (type(self).__name__, self.Point) + + def __hash__(self): + return hash(self._key()) + + def __eq__(self, other): + if not hasattr(other, '_key'): + return False + return self._key() == other._key() + +class VTriangle: + def __init__(self): + self.WedgeIndex0 = 0 # WORD + self.WedgeIndex1 = 0 # WORD + self.WedgeIndex2 = 0 # WORD + self.MatIndex = 0 # BYTE + self.AuxMatIndex = 0 # BYTE + self.SmoothingGroups = 0 # DWORD + + def dump(self): + data = pack('HHHBBL', self.WedgeIndex0, self.WedgeIndex1, self.WedgeIndex2, self.MatIndex, self.AuxMatIndex, self.SmoothingGroups) + return data + +# END UNREAL DATA STRUCTS +######################################################################## + +######################################################################## +#RG - helper class to handle the normal way the UT files are stored +#as sections consisting of a header and then a list of data structures +class FileSection: + def __init__(self, name, type_size): + self.Header = VChunkHeader(name, type_size) + self.Data = [] # list of datatypes + + def dump(self): + data = self.Header.dump() + for i in range(len(self.Data)): + data = data + self.Data[i].dump() + return data + + def UpdateHeader(self): + self.Header.DataCount = len(self.Data) + +class PSKFile: + def __init__(self): + self.GeneralHeader = VChunkHeader("ACTRHEAD", 0) + self.Points = FileSection("PNTS0000", SIZE_VPOINT) #VPoint + self.Wedges = FileSection("VTXW0000", SIZE_VVERTEX) #VVertex + self.Faces = FileSection("FACE0000", SIZE_VTRIANGLE) #VTriangle + self.Materials = FileSection("MATT0000", SIZE_VMATERIAL) #VMaterial + self.Bones = FileSection("REFSKELT", SIZE_VBONE) #VBone + self.Influences = FileSection("RAWWEIGHTS", SIZE_VRAWBONEINFLUENCE) #VRawBoneInfluence + + #RG - this mapping is not dumped, but is used internally to store the new point indices + # for vertex groups calculated during the mesh dump, so they can be used again + # to dump bone influences during the armature dump + # + # the key in this dictionary is the VertexGroup/Bone Name, and the value + # is a list of tuples containing the new point index and the weight, in that order + # + # Layout: + # { groupname : [ (index, weight), ... ], ... } + # + # example: + # { 'MyVertexGroup' : [ (0, 1.0), (5, 1.0), (3, 0.5) ] , 'OtherGroup' : [(2, 1.0)] } + + self.VertexGroups = {} + + def AddPoint(self, p): + #print ('AddPoint') + self.Points.Data.append(p) + + def AddWedge(self, w): + #print ('AddWedge') + self.Wedges.Data.append(w) + + def AddFace(self, f): + #print ('AddFace') + self.Faces.Data.append(f) + + def AddMaterial(self, m): + #print ('AddMaterial') + self.Materials.Data.append(m) + + def AddBone(self, b): + #print ('AddBone [%s]: Position: (x=%f, y=%f, z=%f) Rotation=(%f,%f,%f,%f)' % (b.Name, b.BonePos.Position.X, b.BonePos.Position.Y, b.BonePos.Position.Z, b.BonePos.Orientation.X,b.BonePos.Orientation.Y,b.BonePos.Orientation.Z,b.BonePos.Orientation.W)) + self.Bones.Data.append(b) + + def AddInfluence(self, i): + #print ('AddInfluence') + self.Influences.Data.append(i) + + def UpdateHeaders(self): + self.Points.UpdateHeader() + self.Wedges.UpdateHeader() + self.Faces.UpdateHeader() + self.Materials.UpdateHeader() + self.Bones.UpdateHeader() + self.Influences.UpdateHeader() + + def dump(self): + self.UpdateHeaders() + data = self.GeneralHeader.dump() + self.Points.dump() + self.Wedges.dump() + self.Faces.dump() + self.Materials.dump() + self.Bones.dump() + self.Influences.dump() + return data + + def GetMatByIndex(self, mat_index): + if mat_index >= 0 and len(self.Materials.Data) > mat_index: + return self.Materials.Data[mat_index] + else: + m = VMaterial() + m.MaterialName = "Mat%i" % mat_index + self.AddMaterial(m) + return m + + def PrintOut(self): + print ("--- PSK FILE EXPORTED ---") + print ('point count: %i' % len(self.Points.Data)) + print ('wedge count: %i' % len(self.Wedges.Data)) + print ('face count: %i' % len(self.Faces.Data)) + print ('material count: %i' % len(self.Materials.Data)) + print ('bone count: %i' % len(self.Bones.Data)) + print ('inlfuence count: %i' % len(self.Influences.Data)) + print ('-------------------------') + +# PSA FILE NOTES FROM UDN: +# +# The raw key array holds all the keys for all the bones in all the specified sequences, +# organized as follows: +# For each AnimInfoBinary's sequence there are [Number of bones] times [Number of frames keys] +# in the VQuatAnimKeys, laid out as tracks of [numframes] keys for each bone in the order of +# the bones as defined in the array of FnamedBoneBinary in the PSA. +# +# Once the data from the PSK (now digested into native skeletal mesh) and PSA (digested into +# a native animation object containing one or more sequences) are associated together at runtime, +# bones are linked up by name. Any bone in a skeleton (from the PSK) that finds no partner in +# the animation sequence (from the PSA) will assume its reference pose stance ( as defined in +# the offsets & rotations that are in the VBones making up the reference skeleton from the PSK) + +class PSAFile: + def __init__(self): + self.GeneralHeader = VChunkHeader("ANIMHEAD", 0) + self.Bones = FileSection("BONENAMES", SIZE_FNAMEDBONEBINARY) #FNamedBoneBinary + self.Animations = FileSection("ANIMINFO", SIZE_ANIMINFOBINARY) #AnimInfoBinary + self.RawKeys = FileSection("ANIMKEYS", SIZE_VQUATANIMKEY) #VQuatAnimKey + + # this will take the format of key=Bone Name, value = (BoneIndex, Bone Object) + # THIS IS NOT DUMPED + self.BoneLookup = {} + + def dump(self): + data = self.Generalheader.dump() + self.Bones.dump() + self.Animations.dump() + self.RawKeys.dump() + return data + + def AddBone(self, b): + #LOUD + #print "AddBone: " + b.Name + self.Bones.Data.append(b) + + def AddAnimation(self, a): + #LOUD + #print "AddAnimation: %s, TotalBones: %i, AnimRate: %f, NumRawFrames: %i, TrackTime: %f" % (a.Name, a.TotalBones, a.AnimRate, a.NumRawFrames, a.TrackTime) + self.Animations.Data.append(a) + + def AddRawKey(self, k): + #LOUD + #print "AddRawKey [%i]: Time: %f, Quat: x=%f, y=%f, z=%f, w=%f, Position: x=%f, y=%f, z=%f" % (len(self.RawKeys.Data), k.Time, k.Orientation.X, k.Orientation.Y, k.Orientation.Z, k.Orientation.W, k.Position.X, k.Position.Y, k.Position.Z) + self.RawKeys.Data.append(k) + + def UpdateHeaders(self): + self.Bones.UpdateHeader() + self.Animations.UpdateHeader() + self.RawKeys.UpdateHeader() + + def GetBoneByIndex(self, bone_index): + if bone_index >= 0 and len(self.Bones.Data) > bone_index: + return self.Bones.Data[bone_index] + + def IsEmpty(self): + return (len(self.Bones.Data) == 0 or len(self.Animations.Data) == 0) + + def StoreBone(self, b): + self.BoneLookup[b.Name] = [-1, b] + + def UseBone(self, bone_name): + if bone_name in self.BoneLookup: + bone_data = self.BoneLookup[bone_name] + + if bone_data[0] == -1: + bone_data[0] = len(self.Bones.Data) + self.AddBone(bone_data[1]) + #self.Bones.Data.append(bone_data[1]) + + return bone_data[0] + + def GetBoneByName(self, bone_name): + if bone_name in self.BoneLookup: + bone_data = self.BoneLookup[bone_name] + return bone_data[1] + + def GetBoneIndex(self, bone_name): + if bone_name in self.BoneLookup: + bone_data = self.BoneLookup[bone_name] + return bone_data[0] + + def dump(self): + self.UpdateHeaders() + data = self.GeneralHeader.dump() + self.Bones.dump() + self.Animations.dump() + self.RawKeys.dump() + return data + + def PrintOut(self): + print ('--- PSA FILE EXPORTED ---') + print ('bone count: %i' % len(self.Bones.Data)) + print ('animation count: %i' % len(self.Animations.Data)) + print ('rawkey count: %i' % len(self.RawKeys.Data)) + print ('-------------------------') + +#################################### +# helpers to create bone structs +def make_vbone(name, parent_index, child_count, orientation_quat, position_vect): + bone = VBone() + bone.Name = name + bone.ParentIndex = parent_index + bone.NumChildren = child_count + bone.BonePos.Orientation = orientation_quat + bone.BonePos.Position.X = position_vect.x + bone.BonePos.Position.Y = position_vect.y + bone.BonePos.Position.Z = position_vect.z + + #these values seem to be ignored? + #bone.BonePos.Length = tail.length + #bone.BonePos.XSize = tail.x + #bone.BonePos.YSize = tail.y + #bone.BonePos.ZSize = tail.z + + return bone + +def make_namedbonebinary(name, parent_index, child_count, orientation_quat, position_vect, is_real): + bone = FNamedBoneBinary() + bone.Name = name + bone.ParentIndex = parent_index + bone.NumChildren = child_count + bone.BonePos.Orientation = orientation_quat + bone.BonePos.Position.X = position_vect.x + bone.BonePos.Position.Y = position_vect.y + bone.BonePos.Position.Z = position_vect.z + bone.IsRealBone = is_real + return bone + +################################################## +#RG - check to make sure face isnt a line +#The face has to be triangle not a line +def is_1d_face(blender_face,mesh): + #ID Vertex of id point + v0 = blender_face.verts[0] + v1 = blender_face.verts[1] + v2 = blender_face.verts[2] + + return (mesh.verts[v0].co == mesh.verts[v1].co or \ + mesh.verts[v1].co == mesh.verts[v2].co or \ + mesh.verts[v2].co == mesh.verts[v0].co) + return False + +################################################## +# http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Triangulate_NMesh + +#blender 2.50 format using the Operators/command convert the mesh to tri mesh +def triangulateNMesh(object): + bneedtri = False + scene = bpy.context.scene + bpy.ops.object.mode_set(mode='OBJECT') + for i in scene.objects: i.selected = False #deselect all objects + object.selected = True + scene.objects.active = object #set the mesh object to current + bpy.ops.object.mode_set(mode='OBJECT') + print("Checking mesh if needs to convert quad to Tri...") + for face in object.data.faces: + if (len(face.verts) > 3): + bneedtri = True + break + + bpy.ops.object.mode_set(mode='OBJECT') + if bneedtri == True: + print("Converting quad to tri mesh...") + me_da = object.data.copy() #copy data + me_ob = object.copy() #copy object + #note two copy two types else it will use the current data or mesh + me_ob.data = me_da + bpy.context.scene.objects.link(me_ob)#link the object to the scene #current object location + for i in scene.objects: i.selected = False #deselect all objects + me_ob.selected = True + scene.objects.active = me_ob #set the mesh object to current + bpy.ops.object.mode_set(mode='EDIT') #Operators + bpy.ops.mesh.select_all(action='SELECT')#select all the face/vertex/edge + bpy.ops.mesh.quads_convert_to_tris() #Operators + bpy.context.scene.update() + bpy.ops.object.mode_set(mode='OBJECT') # set it in object + bpy.context.scene.unrealtriangulatebool = True + print("Triangulate Mesh Done!") + else: + print("No need to convert tri mesh.") + me_ob = object + return me_ob + +#Blender Bone Index +class BBone: + def __init__(self): + self.bone = "" + self.index = 0 +bonedata = [] +BBCount = 0 +#deal with mesh bones groups vertex point +def BoneIndex(bone): + global BBCount, bonedata + #print("//==============") + #print(bone.name , "ID:",BBCount) + BB = BBone() + BB.bone = bone.name + BB.index = BBCount + bonedata.append(BB) + BBCount += 1 + for current_child_bone in bone.children: + BoneIndex(current_child_bone) + +def BoneIndexArmature(blender_armature): + global BBCount + #print("\n Buildng bone before mesh \n") + #objectbone = blender_armature.pose #Armature bone + #print(blender_armature) + objectbone = blender_armature[0].pose + #print(dir(ArmatureData)) + + for bone in objectbone.bones: + if(bone.parent == None): + BoneIndex(bone) + #BBCount += 1 + break + +# Actual object parsing functions +def parse_meshes(blender_meshes, psk_file): + #this is use to call the bone name and the index array for group index matches + global bonedata + #print("BONE DATA",len(bonedata)) + print ("----- parsing meshes -----") + print("Number of Object Meshes:",len(blender_meshes)) + for current_obj in blender_meshes: #number of mesh that should be one mesh here + + current_obj = triangulateNMesh(current_obj) + print("Mesh Name:",current_obj.name) + current_mesh = current_obj.data + + #if len(current_obj.materials) > 0: + # object_mat = current_obj.materials[0] + object_material_index = current_obj.active_material_index + + points = ObjMap() + wedges = ObjMap() + + discarded_face_count = 0 + print (" -- Dumping Mesh Faces -- LEN:", len(current_mesh.faces)) + for current_face in current_mesh.faces: + #print ' -- Dumping UVs -- ' + #print current_face.uv_textures + + if len(current_face.verts) != 3: + raise RuntimeError("Non-triangular face (%i)" % len(current_face.v)) + + #No Triangulate Yet + # if len(current_face.verts) != 3: + # raise RuntimeError("Non-triangular face (%i)" % len(current_face.verts)) + # #TODO: add two fake faces made of triangles? + + #RG - apparently blender sometimes has problems when you do quad to triangle + # conversion, and ends up creating faces that have only TWO points - + # one of the points is simply in the vertex list for the face twice. + # This is bad, since we can't get a real face normal for a LINE, we need + # a plane for this. So, before we add the face to the list of real faces, + # ensure that the face is actually a plane, and not a line. If it is not + # planar, just discard it and notify the user in the console after we're + # done dumping the rest of the faces + + if not is_1d_face(current_face,current_mesh): + #print("faces") + wedge_list = [] + vect_list = [] + + #get or create the current material + m = psk_file.GetMatByIndex(object_material_index) + + face_index = current_face.index + has_UV = False + faceUV = None + + if len(current_mesh.uv_textures) > 0: + has_UV = True + faceUV = current_mesh.active_uv_texture.data[face_index]#UVs for current face + #size(data) is number of texture faces. Each face has UVs + #print("DATA face uv: ",len(faceUV.uv), " >> ",(faceUV.uv[0][0])) + + for i in range(3): + vert_index = current_face.verts[i] + vert = current_mesh.verts[vert_index] + uv = [] + #assumes 3 UVs Per face (for now). + if (has_UV): + if len(faceUV.uv) != 3: + print ("WARNING: Current face is missing UV coordinates - writing 0,0...") + print ("WARNING: Face has more than 3 UVs - writing 0,0...") + uv = [0.0, 0.0] + else: + #uv.append(faceUV.uv[i][0]) + #uv.append(faceUV.uv[i][1]) + uv = [faceUV.uv[i][0],faceUV.uv[i][1]] #OR bottom works better # 24 for cube + #uv = list(faceUV.uv[i]) #30 just cube + else: + print ("No UVs?") + uv = [0.0, 0.0] + #print("UV >",uv) + #uv = [0.0, 0.0] #over ride uv that is not fixed + #print(uv) + #flip V coordinate because UEd requires it and DOESN'T flip it on its own like it + #does with the mesh Y coordinates. + #this is otherwise known as MAGIC-2 + uv[1] = 1.0 - uv[1] + + #deal with the min and max value + #if value is over the set limit it will null the uv texture + if (uv[0] > 1): + uv[0] = 1 + if (uv[0] < 0): + uv[0] = 0 + if (uv[1] > 1): + uv[1] = 1 + if (uv[1] < 0): + uv[1] = 0 + + + # RE - Append untransformed vector (for normal calc below) + # TODO: convert to Blender.mathutils + vect_list.append(FVector(vert.co.x, vert.co.y, vert.co.z)) + + # Transform position for export + #vpos = vert.co * object_material_index + vpos = vert.co * current_obj.matrix + # Create the point + p = VPoint() + p.Point.X = vpos.x + p.Point.Y = vpos.y + p.Point.Z = vpos.z + + # Create the wedge + w = VVertex() + w.MatIndex = object_material_index + w.PointIndex = points.get(p) # get index from map + #Set UV TEXTURE + w.U = uv[0] + w.V = uv[1] + index_wedge = wedges.get(w) + wedge_list.append(index_wedge) + + #print results + #print 'result PointIndex=%i, U=%f, V=%f, wedge_index=%i' % ( + # w.PointIndex, + # w.U, + # w.V, + # wedge_index) + + # Determine face vertex order + # get normal from blender + no = current_face.normal + + # TODO: convert to Blender.mathutils + # convert to FVector + norm = FVector(no[0], no[1], no[2]) + + # Calculate the normal of the face in blender order + tnorm = vect_list[1].sub(vect_list[0]).cross(vect_list[2].sub(vect_list[1])) + + # RE - dot the normal from blender order against the blender normal + # this gives the product of the two vectors' lengths along the blender normal axis + # all that matters is the sign + dot = norm.dot(tnorm) + + # print results + #print 'face norm: (%f,%f,%f), tnorm=(%f,%f,%f), dot=%f' % ( + # norm.X, norm.Y, norm.Z, + # tnorm.X, tnorm.Y, tnorm.Z, + # dot) + + tri = VTriangle() + # RE - magic: if the dot product above > 0, order the vertices 2, 1, 0 + # if the dot product above < 0, order the vertices 0, 1, 2 + # if the dot product is 0, then blender's normal is coplanar with the face + # and we cannot deduce which side of the face is the outside of the mesh + if (dot > 0): + (tri.WedgeIndex2, tri.WedgeIndex1, tri.WedgeIndex0) = wedge_list + elif (dot < 0): + (tri.WedgeIndex0, tri.WedgeIndex1, tri.WedgeIndex2) = wedge_list + else: + dindex0 = current_face.verts[0]; + dindex1 = current_face.verts[1]; + dindex2 = current_face.verts[2]; + raise RuntimeError("normal vector coplanar with face! points:", current_mesh.verts[dindex0].co, current_mesh.verts[dindex1].co, current_mesh.verts[dindex2].co) + + tri.MatIndex = object_material_index + #print(tri) + psk_file.AddFace(tri) + + else: + discarded_face_count = discarded_face_count + 1 + + print (" -- Dumping Mesh Points -- LEN:",len(points.dict)) + for point in points.items(): + psk_file.AddPoint(point) + print (" -- Dumping Mesh Wedge -- LEN:",len(wedges.dict)) + for wedge in wedges.items(): + psk_file.AddWedge(wedge) + + #RG - if we happend upon any non-planar faces above that we've discarded, + # just let the user know we discarded them here in case they want + # to investigate + + if discarded_face_count > 0: + print ("INFO: Discarded %i non-planar faces." % (discarded_face_count)) + + #RG - walk through the vertex groups and find the indexes into the PSK points array + #for them, then store that index and the weight as a tuple in a new list of + #verts for the group that we can look up later by bone name, since Blender matches + #verts to bones for influences by having the VertexGroup named the same thing as + #the bone + + #vertex group. + for bonegroup in bonedata: + #print("bone gourp build:",bonegroup.bone) + vert_list = [] + for current_vert in current_mesh.verts: + #print("INDEX V:",current_vert.index) + vert_index = current_vert.index + for vgroup in current_vert.groups:#vertex groupd id + vert_weight = vgroup.weight + if(bonegroup.index == vgroup.group): + p = VPoint() + vpos = current_vert.co * current_obj.matrix + p.Point.X = vpos.x + p.Point.Y = vpos.y + p.Point.Z = vpos.z + #print(current_vert.co) + point_index = points.get(p) #point index + v_item = (point_index, vert_weight) + vert_list.append(v_item) + #bone name, [point id and wieght] + #print("Add Vertex Group:",bonegroup.bone, " No. Points:",len(vert_list)) + psk_file.VertexGroups[bonegroup.bone] = vert_list + + #unrealtriangulatebool #this will remove the mesh from the scene + if (bpy.context.scene.unrealtriangulatebool == True): + print("Remove tmp Mesh [ " ,current_obj.name, " ] from scene >" ,(bpy.context.scene.unrealtriangulatebool )) + bpy.ops.object.mode_set(mode='OBJECT') # set it in object + bpy.context.scene.objects.unlink(current_obj) + +def make_fquat(bquat): + quat = FQuat() + + #flip handedness for UT = set x,y,z to negative (rotate in other direction) + quat.X = -bquat.x + quat.Y = -bquat.y + quat.Z = -bquat.z + + quat.W = bquat.w + return quat + +def make_fquat_default(bquat): + quat = FQuat() + + quat.X = bquat.x + quat.Y = bquat.y + quat.Z = bquat.z + + quat.W = bquat.w + return quat + +# ================================================================================================= +# TODO: remove this 1am hack +nbone = 0 +def parse_bone(blender_bone, psk_file, psa_file, parent_id, is_root_bone, parent_matrix, parent_root): + global nbone # look it's evil! + #print '-------------------- Dumping Bone ---------------------- ' + + #If bone does not have parent that mean it the root bone + if blender_bone.parent == None: + parent_root = blender_bone + + + child_count = len(blender_bone.children) + #child of parent + child_parent = blender_bone.parent + + if child_parent != None: + print ("--Bone Name:",blender_bone.name ," parent:" , blender_bone.parent.name, "ID:", nbone) + else: + print ("--Bone Name:",blender_bone.name ," parent: None" , "ID:", nbone) + + + + + if child_parent != None: + quat_root = blender_bone.matrix + quat = make_fquat(quat_root.to_quat()) + + quat_parent = child_parent.matrix.to_quat().inverse() + parent_head = child_parent.head * quat_parent + parent_tail = child_parent.tail * quat_parent + + set_position = (parent_tail - parent_head) + blender_bone.head + else: + # ROOT BONE + #This for root + set_position = blender_bone.head * parent_matrix #ARMATURE OBJECT Locction + rot_mat = blender_bone.matrix * parent_matrix.rotation_part() #ARMATURE OBJECT Rotation + #print(dir(rot_mat)) + + quat = make_fquat_default(rot_mat.to_quat()) + + print ("[[======= FINAL POSITION:", set_position) + final_parent_id = parent_id + + #RG/RE - + #if we are not seperated by a small distance, create a dummy bone for the displacement + #this is only needed for root bones, since UT assumes a connected skeleton, and from here + #down the chain we just use "tail" as an endpoint + #if(head.length > 0.001 and is_root_bone == 1): + if(0): + pb = make_vbone("dummy_" + blender_bone.name, parent_id, 1, FQuat(), tail) + psk_file.AddBone(pb) + pbb = make_namedbonebinary("dummy_" + blender_bone.name, parent_id, 1, FQuat(), tail, 0) + psa_file.StoreBone(pbb) + final_parent_id = nbone + nbone = nbone + 1 + #tail = tail-head + + my_id = nbone + + pb = make_vbone(blender_bone.name, final_parent_id, child_count, quat, set_position) + psk_file.AddBone(pb) + pbb = make_namedbonebinary(blender_bone.name, final_parent_id, child_count, quat, set_position, 1) + psa_file.StoreBone(pbb) + + nbone = nbone + 1 + + #RG - dump influences for this bone - use the data we collected in the mesh dump phase + # to map our bones to vertex groups + #print("///////////////////////") + #print("set influence") + if blender_bone.name in psk_file.VertexGroups: + vertex_list = psk_file.VertexGroups[blender_bone.name] + #print("vertex list:", len(vertex_list), " of >" ,blender_bone.name ) + for vertex_data in vertex_list: + #print("set influence vettex") + point_index = vertex_data[0] + vertex_weight = vertex_data[1] + influence = VRawBoneInfluence() + influence.Weight = vertex_weight + #influence.BoneIndex = my_id + influence.BoneIndex = my_id + influence.PointIndex = point_index + #print(influence) + #print ('Adding Bone Influence for [%s] = Point Index=%i, Weight=%f' % (blender_bone.name, point_index, vertex_weight)) + #print("adding influence") + psk_file.AddInfluence(influence) + + #blender_bone.matrix_local + #recursively dump child bones + mainparent = parent_matrix + #if len(blender_bone.children) > 0: + for current_child_bone in blender_bone.children: + parse_bone(current_child_bone, psk_file, psa_file, my_id, 0, mainparent, parent_root) + +def parse_armature(blender_armature, psk_file, psa_file): + print ("----- parsing armature -----") + print ('blender_armature length: %i' % (len(blender_armature))) + + #magic 0 sized root bone for UT - this is where all armature dummy bones will attach + #dont increment nbone here because we initialize it to 1 (hackity hackity hack) + + #count top level bones first. NOT EFFICIENT. + child_count = 0 + for current_obj in blender_armature: + current_armature = current_obj.data + bones = [x for x in current_armature.bones if not x.parent == None] + child_count += len(bones) + + for current_obj in blender_armature: + print ("Current Armature Name: " + current_obj.name) + current_armature = current_obj.data + #armature_id = make_armature_bone(current_obj, psk_file, psa_file) + + #we dont want children here - only the top level bones of the armature itself + #we will recursively dump the child bones as we dump these bones + """ + bones = [x for x in current_armature.bones if not x.parent == None] + #will ingore this part of the ocde + """ + for current_bone in current_armature.bones: #list the bone. #note this will list all the bones. + if(current_bone.parent == None): + parse_bone(current_bone, psk_file, psa_file, 0, 0, current_obj.matrix, None) + break + +# get blender objects by type +def get_blender_objects(objects, intype): + return [x for x in objects if x.type == intype] + +#strips current extension (if any) from filename and replaces it with extension passed in +def make_filename_ext(filename, extension): + new_filename = '' + extension_index = filename.find('.') + + if extension_index == -1: + new_filename = filename + extension + else: + new_filename = filename[0:extension_index] + extension + + return new_filename + +# returns the quaternion Grassman product a*b +# this is the same as the rotation a(b(x)) +# (ie. the same as B*A if A and B are matrices representing +# the rotations described by quaternions a and b) +def grassman(a, b): + return mathutils.Quaternion( + a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z, + a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y, + a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x, + a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w) + +def parse_animation(blender_scene, blender_armatures, psa_file): + #to do list: + #need to list the action sets + #need to check if there animation + #need to check if animation is has one frame then exit it + print ('\n----- parsing animation -----') + #print(dir(blender_scene.render)) + render_data = blender_scene.render + bHaveAction = True + + anim_rate = render_data.fps + print("==== Blender Settings ====") + print ('Scene: %s Start Frame: %i, End Frame: %i' % (blender_scene.name, blender_scene.start_frame, blender_scene.end_frame)) + print ('Frames Per Sec: %i' % anim_rate) + print ("Default FPS: 24" ) + + cur_frame_index = 0 + + #print(dir(bpy.data.actions)) + #print(dir(bpy.context.scene.set)) + + #list of armature objects + for arm in blender_armatures: + #check if there animation data from armature or something + if not arm.animation_data: + print("======================================") + print("Check Animation Data: None") + print("Armature has no animation, skipping...") + print("======================================") + break + + if not arm.animation_data.action: + print("======================================") + print("Check Action: None") + print("Armature has no animation, skipping...") + print("======================================") + break + act = arm.animation_data.action + #print(dir(act)) + action_name = act.name + + if not len(act.fcurves): + print("//===========================================================") + print("// None bone pose set keys for this action set... skipping...") + print("//===========================================================") + bHaveAction = False + + #this deal with action export control + if bHaveAction == True: + print("") + print("==== Action Set ====") + print("Action Name:",action_name) + #look for min and max frame that current set keys + framemin, framemax = act.get_frame_range() + #print("max frame:",framemax) + start_frame = framemin + end_frame = framemax + scene_frames = range(start_frame, end_frame+1) + frame_count = len(scene_frames) + #=================================================== + anim = AnimInfoBinary() + anim.Name = action_name + anim.Group = "" #what is group? + anim.NumRawFrames = frame_count + anim.AnimRate = anim_rate + anim.FirstRawFrame = cur_frame_index + #=================================================== + count_previous_keys = len(psa_file.RawKeys.Data) + print("Frame Key Set Count:",frame_count, "Total Frame:",frame_count) + #print("init action bones...") + unique_bone_indexes = {} + # bone lookup table + bones_lookup = {} + + #build bone node for animation keys needed to be set + for bone in arm.data.bones: + bones_lookup[bone.name] = bone + #print("bone name:",bone.name) + frame_count = len(scene_frames) + #print ('Frame Count: %i' % frame_count) + pose_data = arm.pose + + #these must be ordered in the order the bones will show up in the PSA file! + ordered_bones = {} + ordered_bones = sorted([(psa_file.UseBone(x.name), x) for x in pose_data.bones], key=operator.itemgetter(0)) + + ############################# + # ORDERED FRAME, BONE + #for frame in scene_frames: + + for i in range(frame_count): + frame = scene_frames[i] + #LOUD + #print ("==== outputting frame %i ===" % frame) + + if frame_count > i+1: + next_frame = scene_frames[i+1] + #print "This Frame: %i, Next Frame: %i" % (frame, next_frame) + else: + next_frame = -1 + #print "This Frame: %i, Next Frame: NONE" % frame + + #frame start from 1 as number one from blender + blender_scene.set_frame(frame) + + cur_frame_index = cur_frame_index + 1 + for bone_data in ordered_bones: + bone_index = bone_data[0] + pose_bone = bone_data[1] + #print("[=====POSE NAME:",pose_bone.name) + + #print("LENG >>.",len(bones_lookup)) + blender_bone = bones_lookup[pose_bone.name] + + #just need the total unique bones used, later for this AnimInfoBinary + unique_bone_indexes[bone_index] = bone_index + #LOUD + #print ("-------------------", pose_bone.name) + head = pose_bone.head + + posebonemat = mathutils.Matrix(pose_bone.matrix) + parent_pose = pose_bone.parent + if parent_pose != None: + parentposemat = mathutils.Matrix(parent_pose.matrix) + #blender 2.4X it been flip around with new 2.50 (mat1 * mat2) should now be (mat2 * mat1) + posebonemat = parentposemat.invert() * posebonemat + head = posebonemat.translation_part() + quat = posebonemat.to_quat().normalize() + vkey = VQuatAnimKey() + vkey.Position.X = head.x + vkey.Position.Y = head.y + vkey.Position.Z = head.z + + if parent_pose != None: + quat = make_fquat(quat) + else: + quat = make_fquat_default(quat) + + vkey.Orientation = quat + #print("Head:",head) + #print("Orientation",quat) + + #time from now till next frame = diff / framesPerSec + if next_frame >= 0: + diff = next_frame - frame + else: + diff = 1.0 + + #print ("Diff = ", diff) + vkey.Time = float(diff)/float(anim_rate) + + psa_file.AddRawKey(vkey) + + #done looping frames + #done looping armatures + #continue adding animInfoBinary counts here + + anim.TotalBones = len(unique_bone_indexes) + print("Bones Count:",anim.TotalBones) + anim.TrackTime = float(frame_count) / anim.AnimRate + print("Time Track Frame:",anim.TrackTime) + psa_file.AddAnimation(anim) + print("==== Finish Action Build(s) ====") + +exportmessage = "Export Finish" + +def fs_callback(filename, context, user_setting): + #this deal with repeat export and the reset settings + global bonedata, BBCount, nbone, exportmessage + bonedata = []#clear array + BBCount = 0 + nbone = 0 + + start_time = time.clock() + + print ("========EXPORTING TO UNREAL SKELETAL MESH FORMATS========\r\n") + print("Blender Version:", bpy.app.version_string) + + psk = PSKFile() + psa = PSAFile() + + #sanity check - this should already have the extension, but just in case, we'll give it one if it doesn't + psk_filename = make_filename_ext(filename, '.psk') + + #make the psa filename + psa_filename = make_filename_ext(filename, '.psa') + + print ('PSK File: ' + psk_filename) + print ('PSA File: ' + psa_filename) + + barmature = True + bmesh = True + blender_meshes = [] + blender_armature = [] + selectmesh = [] + selectarmature = [] + + current_scene = context.scene + cur_frame = current_scene.current_frame #store current frame before we start walking them during animation parse + objects = current_scene.objects + + print("Checking object count...") + for next_obj in objects: + if next_obj.type == 'MESH': + blender_meshes.append(next_obj) + if (next_obj.selected): + #print("mesh object select") + selectmesh.append(next_obj) + if next_obj.type == 'ARMATURE': + blender_armature.append(next_obj) + if (next_obj.selected): + #print("armature object select") + selectarmature.append(next_obj) + + print("Mesh Count:",len(blender_meshes)," Armature Count:",len(blender_armature)) + print("====================================") + print("Checking Mesh Condtion(s):") + if len(blender_meshes) == 1: + print(" - One Mesh Scene") + elif (len(blender_meshes) > 1) and (len(selectmesh) == 1): + print(" - One Mesh [Select]") + else: + print(" - Too Many Meshes!") + print(" - Select One Mesh Object!") + bmesh = False + print("====================================") + print("Checking Armature Condtion(s):") + if len(blender_armature) == 1: + print(" - One Armature Scene") + elif (len(blender_armature) > 1) and (len(selectarmature) == 1): + print(" - One Armature [Select]") + else: + print(" - Too Armature Meshes!") + print(" - Select One Armature Object Only!") + barmature = False + + if (bmesh == False) or (barmature == False): + exportmessage = "Export Fail! Check Log." + print("=================================") + print("= Export Fail! =") + print("=================================") + else: + exportmessage = "Export Finish!" + #need to build a temp bone index for mesh group vertex + BoneIndexArmature(blender_armature) + + try: + ####################### + # STEP 1: MESH DUMP + # we build the vertexes, wedges, and faces in here, as well as a vertexgroup lookup table + # for the armature parse + print("//===============================") + print("// STEP 1") + print("//===============================") + parse_meshes(blender_meshes, psk) + except: + context.scene.set_frame(cur_frame) #set frame back to original frame + print ("Exception during Mesh Parse") + raise + + try: + ####################### + # STEP 2: ARMATURE DUMP + # IMPORTANT: do this AFTER parsing meshes - we need to use the vertex group data from + # the mesh parse in here to generate bone influences + print("//===============================") + print("// STEP 2") + print("//===============================") + parse_armature(blender_armature, psk, psa) + + except: + context.scene.set_frame(cur_frame) #set frame back to original frame + print ("Exception during Armature Parse") + raise + + try: + ####################### + # STEP 3: ANIMATION DUMP + # IMPORTANT: do AFTER parsing bones - we need to do bone lookups in here during animation frames + print("//===============================") + print("// STEP 3") + print("//===============================") + parse_animation(current_scene, blender_armature, psa) + + except: + context.scene.set_frame(cur_frame) #set frame back to original frame + print ("Exception during Animation Parse") + raise + + # reset current frame + + context.scene.set_frame(cur_frame) #set frame back to original frame + + ########################## + # FILE WRITE + print("//===========================================") + print("// bExportPsk:",bpy.context.scene.unrealexportpsk," bExportPsa:",bpy.context.scene.unrealexportpsa) + print("//===========================================") + if bpy.context.scene.unrealexportpsk == True: + print("Writing Skeleton Mesh Data...") + #RG - dump psk file + psk.PrintOut() + file = open(psk_filename, "wb") + file.write(psk.dump()) + file.close() + print ("Successfully Exported File: " + psk_filename) + if bpy.context.scene.unrealexportpsa == True: + print("Writing Animaiton Data...") + #RG - dump psa file + if not psa.IsEmpty(): + psa.PrintOut() + file = open(psa_filename, "wb") + file.write(psa.dump()) + file.close() + print ("Successfully Exported File: " + psa_filename) + else: + print ("No Animations (.psa file) to Export") + + print ('PSK/PSA Export Script finished in %.2f seconds' % (time.clock() - start_time)) + + #MSG BOX EXPORT COMPLETE + #... + + #DONE + print ("PSK/PSA Export Complete") + +def write_data(path, context, user_setting): + print("//============================") + print("// running psk/psa export...") + print("//============================") + fs_callback(path, context, user_setting) + pass + +from bpy.props import * + +exporttypedata = [] + +# [index,text field,0] #or something like that +exporttypedata.append(("0","PSK","Export PSK")) +exporttypedata.append(("1","PSA","Export PSA")) +exporttypedata.append(("2","ALL","Export ALL")) + +IntProperty= bpy.types.Scene.IntProperty + +IntProperty(attr="unrealfpsrate", name="fps rate", + description="Set the frame per second (fps) for unreal.", + default=24,min=1,max=100) + +bpy.types.Scene.EnumProperty( attr="unrealexport_settings", + name="Export:", + description="Select a export settings (psk/psa/all)...", + items = exporttypedata, default = '0') + +bpy.types.Scene.BoolProperty( attr="unrealtriangulatebool", + name="Triangulate Mesh", + description="Convert Quad to Tri Mesh Boolean...", + default=False) + +bpy.types.Scene.BoolProperty( attr="unrealexportpsk", + name="bool export psa", + description="bool for exporting this psk format", + default=False) + +bpy.types.Scene.BoolProperty( attr="unrealexportpsa", + name="bool export psa", + description="bool for exporting this psa format", + default=False) + +class ExportUDKAnimData(bpy.types.Operator): + global exportmessage + '''Export Skeleton Mesh / Animation Data file(s)''' + bl_idname = "export.udk_anim_data" # this is important since its how bpy.ops.export.udk_anim_data is constructed + bl_label = "Export PSK/PSA" + __doc__ = "One mesh and one armature else select one mesh or armature to be exported." + + # List of operator properties, the attributes will be assigned + # to the class instance from the operator settings before calling. + + # TODO, add props + path = StringProperty(name="File Path", description="File path used for exporting the PSA file", maxlen= 1024, default= "") + use_setting = BoolProperty(name="No Options Yet", description="No Options Yet", default= True) + filename = StringProperty(name="filename", description="", maxlen= 1024, default= "") + directory = StringProperty(name="directory", description="", maxlen= 1024, default= "") + pskexportbool = BoolProperty(name="Export PSK", description="Export Skeletal Mesh", default= True) + psaexportbool = BoolProperty(name="Export PSA", description="Export Action Set (Animation Data)", default= True) + #fpssettomg = IntProperty(name="FPS", attr="fpsunrealexport",description="", default=24, min=1, max=120, soft_min=1, soft_max=120, step=1, options={'ANIMATABLE'}) + + def poll(self, context): + return context.active_object != None + + def execute(self, context): + #check if skeleton mesh is needed to be exported + if (self.properties.pskexportbool): + bpy.context.scene.unrealexportpsk = True + else: + bpy.context.scene.unrealexportpsk = False + #check if animation data is needed to be exported + if (self.properties.psaexportbool): + bpy.context.scene.unrealexportpsa = True + else: + bpy.context.scene.unrealexportpsa = False + + #print(">>>>>",dir(self)) + #self.properties.pskexportbool + #self.layout.prop(context,context.scene.render,"fps") + write_data(self.properties.path, context, self.properties.use_setting) + + self.report({'WARNING', 'INFO'}, exportmessage) + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.manager + wm.add_fileselect(self) + return {'RUNNING_MODAL'} + +def menu_func(self, context): + bpy.context.scene.unrealexportpsk = True + bpy.context.scene.unrealexportpsa = True + default_path = bpy.data.filename.replace(".blend", ".psk") + self.layout.operator ("export.udk_anim_data", text="Skeleton Mesh / Animation Data (.psk/.psa)").path = default_path + +class VIEW3D_PT_unrealtools_objectmode(bpy.types.Panel): + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_label = "Unreal Tools" + + def poll(self, context): + return context.active_object + + def draw(self, context): + layout = self.layout + #layout.label(text="Unreal Tools") + rd = context.scene + #drop box + layout.prop(rd, "unrealexport_settings",expand=True) + #layout.prop(rd, "unrealexport_settings") + #button + layout.operator("object.UnrealExport") + #FPS #it use the real data from your scene + layout.prop(rd.render, "fps") + + #row = layout.row() + #row.label(text="Action Set(s)(not build)") + #for action in bpy.data.actions: + #print(dir( action)) + #print(action.get_frame_range()) + #row = layout.row() + #row.prop(action, "name") + + #print(dir(action.groups[0])) + #for g in action.groups:#those are bones + #print("group...") + #print(dir(g)) + #print("////////////") + #print((g.name)) + #print("////////////") + + #row.label(text="Active:" + action.select) + btrimesh = False + +class OBJECT_OT_UnrealExport(bpy.types.Operator): + global exportmessage + bl_idname = "OBJECT_OT_UnrealExport" + bl_label = "Unreal Export" + __doc__ = "Select export setting for .psk/.psa or both." + + def invoke(self, context, event): + path = StringProperty(name="File Path", description="File path used for exporting the PSA file", maxlen= 1024, default= "") + print("Init Export Script:") + if(int(bpy.context.scene.unrealexport_settings) == 0): + bpy.context.scene.unrealexportpsk = True + bpy.context.scene.unrealexportpsa = False + print("Exporting PSK...") + if(int(bpy.context.scene.unrealexport_settings) == 1): + bpy.context.scene.unrealexportpsk = False + bpy.context.scene.unrealexportpsa = True + print("Exporting PSA...") + if(int(bpy.context.scene.unrealexport_settings) == 2): + bpy.context.scene.unrealexportpsk = True + bpy.context.scene.unrealexportpsa = True + print("Exporting ALL...") + default_path = bpy.data.filename.replace(".blend", ".psk") + fs_callback(default_path, bpy.context, False) + self.report({'WARNING', 'INFO'}, exportmessage) + return{'FINISHED'} + +def register(): + global MENUPANELBOOL + if MENUPANELBOOL: + bpy.types.register(OBJECT_OT_UnrealExport) + bpy.types.register(VIEW3D_PT_unrealtools_objectmode) + bpy.types.register(ExportUDKAnimData) + bpy.types.INFO_MT_file_export.append(menu_func) + +def unregister(): + global MENUPANELBOOL + if MENUPANELBOOL: + bpy.types.unregister(OBJECT_OT_UnrealExport) + bpy.types.unregister(VIEW3D_PT_unrealtools_objectmode) + bpy.types.unregister(ExportUDKAnimData) + bpy.types.INFO_MT_file_export.remove(menu_func) + +if __name__ == "__main__": + register() \ No newline at end of file diff --git a/import_scene_unreal_psk.py b/import_scene_unreal_psk.py new file mode 100644 index 0000000000000000000000000000000000000000..f56f08d513e41a987b035485dafab102011958fa --- /dev/null +++ b/import_scene_unreal_psk.py @@ -0,0 +1,598 @@ +#!BPY +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +bl_addon_info = { + 'name': 'Import: Unreal Skeleton Mesh(.psk)', + 'author': 'Darknet', + 'version': '2.0', + 'blender': (2, 5, 3), + 'location': 'File > Import ', + 'description': 'Import Unreal Engine (.psk)', + 'url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/File_I-O/Unreal_psk_psa', + 'category': 'Import/Export'} + +""" +Name: 'Skeleton Mesh Import(.psk)' +Blender: 250 +Group: 'Import' +Tip: 'Import mesh data from Unreal Tournament PSK file.' + +Updated by: Darknet + +# Unreal Tournament PSK file to Blender mesh converter V1.0 +# Author: D.M. Sturgeon (camg188 at the elYsium forum) +# Imports a *psk file to a new mesh + +#-No UV Texutre +#-No Weight +#-No Armature Bones +#-No Material ID +#-Export Text Log From Current Location File (Bool ) +""" + +import bpy +import mathutils +import os +import sys +import string +import math +import re +from string import * +from struct import * +from math import * + +#from bpy.props import * + +import mathutils + +vector = mathutils.Vector + +#output log in to txt file +DEBUGLOG = False + +scale = 1.0 +bonesize = 1.0 +md5_bones=[] + +def unpack_list(list_of_tuples): + l = [] + for t in list_of_tuples: + l.extend(t) + return l +""" +class md5_bone: + bone_index=0 + name="" + bindpos=[] + bindmat = mathutils.Quaternion() + parent="" + parent_index=0 + blenderbone=None + roll=0 + + def __init__(self): + self.bone_index=0 + self.name="" + self.bindpos=[0.0]*3 + self.bindmat=[None]*3 #is this how you initilize a 2d-array + for i in range(3): self.bindmat[i] = [0.0]*3 + self.parent="" + self.parent_index=0 + self.blenderbone=None + + def dump(self): + print ("bone index: ", self.bone_index) + print ("name: ", self.name) + print ("bind position: ", self.bindpos) + print ("bind translation matrix: ", self.bindmat) + print ("parent: ", self.parent) + print ("parent index: ", self.parent_index) + print ("blenderbone: ", self.blenderbone) +""" +class md5_bone: + bone_index=0 + name="" + bindpos=[] + bindmat=[] + scale = [] + parent="" + parent_index=0 + blenderbone=None + roll=0 + + def __init__(self): + self.bone_index=0 + self.name="" + self.bindpos=[0.0]*3 + self.scale=[0.0]*3 + self.bindmat=[None]*3 #is this how you initilize a 2d-array + for i in range(3): self.bindmat[i] = [0.0]*3 + self.parent="" + self.parent_index=0 + self.blenderbone=None + + def dump(self): + print ("bone index: ", self.bone_index) + print ("name: ", self.name) + print ("bind position: ", self.bindpos) + print ("bind translation matrix: ", self.bindmat) + print ("parent: ", self.parent) + print ("parent index: ", self.parent_index) + print ("blenderbone: ", self.blenderbone) + +#http://www.blender.org/forum/viewtopic.php?t=13340&sid=8b17d5de07b17960021bbd72cac0495f +def fixRollZ(b): + v = (b.tail-b.head)/b.length + b.roll -= math.degrees(math.atan2(v[0]*v[2]*(1 - v[1]),v[0]*v[0] + v[1]*v[2]*v[2])) +def fixRoll(b): + v = (b.tail-b.head)/b.length + if v[2]*v[2] > .5: + #align X-axis + b.roll += math.degrees(math.atan2(v[0]*v[2]*(1 - v[1]),v[2]*v[2] + v[1]*v[0]*v[0])) + else: + #align Z-axis + b.roll -= math.degrees(math.atan2(v[0]*v[2]*(1 - v[1]),v[0]*v[0] + v[1]*v[2]*v[2])) + +def pskimport(infile): + global DEBUGLOG + print ("--------------------------------------------------") + print ("---------SCRIPT EXECUTING PYTHON IMPORTER---------") + print ("--------------------------------------------------") + print ("Importing file: ", infile) + + md5_bones=[] + pskfile = open(infile,'rb') + if (DEBUGLOG): + logpath = infile.replace(".psk", ".txt") + print("logpath:",logpath) + logf = open(logpath,'w') + + def printlog(strdata): + if (DEBUGLOG): + logf.write(strdata) + + objName = infile.split('\\')[-1].split('.')[0] + + me_ob = bpy.data.meshes.new(objName) + print("objName:",objName) + printlog(("New Mesh = " + me_ob.name + "\n")) + #read general header + indata = unpack('20s3i',pskfile.read(32)) + #not using the general header at this time + #================================================================================================== + # vertex point + #================================================================================================== + #read the PNTS0000 header + indata = unpack('20s3i',pskfile.read(32)) + recCount = indata[3] + printlog(( "Nbr of PNTS0000 records: " + str(recCount) + "\n")) + counter = 0 + verts = [] + while counter < recCount: + counter = counter + 1 + indata = unpack('3f',pskfile.read(12)) + #print(indata[0],indata[1],indata[2]) + verts.extend([(indata[0],indata[1],indata[2])]) + #Tmsh.verts.append(NMesh.Vert(indata[0],indata[1],indata[2])) + + #================================================================================================== + # UV + #================================================================================================== + #read the VTXW0000 header + indata = unpack('20s3i',pskfile.read(32)) + recCount = indata[3] + printlog( "Nbr of VTXW0000 records: " + str(recCount)+ "\n") + counter = 0 + UVCoords = [] + #UVCoords record format = [index to PNTS, U coord, v coord] + while counter < recCount: + counter = counter + 1 + indata = unpack('hhffhh',pskfile.read(16)) + UVCoords.append([indata[0],indata[2],indata[3]]) + #print([indata[0],indata[2],indata[3]]) + #print([indata[1],indata[2],indata[3]]) + + #================================================================================================== + # Face + #================================================================================================== + #read the FACE0000 header + indata = unpack('20s3i',pskfile.read(32)) + recCount = indata[3] + printlog( "Nbr of FACE0000 records: "+ str(recCount) + "\n") + #PSK FACE0000 fields: WdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp + #associate MatIdx to an image, associate SmthGrp to a material + SGlist = [] + counter = 0 + faces = [] + faceuv = [] + while counter < recCount: + counter = counter + 1 + indata = unpack('hhhbbi',pskfile.read(12)) + #the psk values are: nWdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp + #indata[0] = index of UVCoords + #UVCoords[indata[0]]=[index to PNTS, U coord, v coord] + #UVCoords[indata[0]][0] = index to PNTS + PNTSA = UVCoords[indata[0]][0] + PNTSB = UVCoords[indata[1]][0] + PNTSC = UVCoords[indata[2]][0] + #print(PNTSA,PNTSB,PNTSC) #face id vertex + #faces.extend([0,1,2,0]) + faces.extend([PNTSA,PNTSB,PNTSC,0]) + uv = [] + u0 = UVCoords[indata[0]][1] + v0 = UVCoords[indata[0]][2] + uv.append([u0,v0]) + u1 = UVCoords[indata[1]][1] + v1 = UVCoords[indata[1]][2] + uv.append([u1,v1]) + u2 = UVCoords[indata[2]][1] + v2 = UVCoords[indata[2]][2] + uv.append([u2,v2]) + faceuv.append(uv) + #print("UV: ",u0,v0) + #update the uv var of the last item in the Tmsh.faces list + # which is the face just added above + ##Tmsh.faces[-1].uv = [(u0,v0),(u1,v1),(u2,v2)] + #print("smooth:",indata[5]) + #collect a list of the smoothing groups + if SGlist.count(indata[5]) == 0: + SGlist.append(indata[5]) + print("smooth:",indata[5]) + #assign a material index to the face + #Tmsh.faces[-1].materialIndex = SGlist.index(indata[5]) + printlog( "Using Materials to represent PSK Smoothing Groups...\n") + #========== + # skip something... + #========== + + #================================================================================================== + # Material + #================================================================================================== + ## + #read the MATT0000 header + indata = unpack('20s3i',pskfile.read(32)) + recCount = indata[3] + printlog("Nbr of MATT0000 records: " + str(recCount) + "\n" ) + printlog(" - Not importing any material data now. PSKs are texture wrapped! \n") + counter = 0 + while counter < recCount: + counter = counter + 1 + indata = unpack('64s6i',pskfile.read(88)) + ## + + #================================================================================================== + # Bones (Armature) + #================================================================================================== + #read the REFSKEL0 header + indata = unpack('20s3i',pskfile.read(32)) + recCount = indata[3] + printlog( "Nbr of REFSKEL0 records: " + str(recCount) + "\n") + Bns = [] + bone = [] + nobone = 0 + #================================================================================================== + # Bone Data + #================================================================================================== + counter = 0 + print ("---PRASE--BONES---") + while counter < recCount: + indata = unpack('64s3i11f',pskfile.read(120)) + #print( "DATA",str(indata)) + bone.append(indata) + + createbone = md5_bone() + #temp_name = indata[0][:30] + temp_name = indata[0] + + temp_name = bytes.decode(temp_name) + temp_name = temp_name.lstrip(" ") + temp_name = temp_name.rstrip(" ") + temp_name = temp_name.strip() + temp_name = temp_name.strip( bytes.decode(b'\x00')) + print ("temp_name:", temp_name, "||") + createbone.name = temp_name + createbone.bone_index = counter + createbone.parent_index = indata[3] + createbone.bindpos[0] = indata[8] + createbone.bindpos[1] = indata[9] + createbone.bindpos[2] = indata[10] + createbone.scale[0] = indata[12] + createbone.scale[1] = indata[13] + createbone.scale[2] = indata[14] + + #w,x,y,z + if (counter == 0):#main parent + print("no parent bone") + createbone.bindmat = mathutils.Quaternion(indata[7],indata[4],indata[5],indata[6]) + #createbone.bindmat = mathutils.Quaternion(indata[7],-indata[4],-indata[5],-indata[6]) + else:#parent + print("parent bone") + createbone.bindmat = mathutils.Quaternion(indata[7],-indata[4],-indata[5],-indata[6]) + #createbone.bindmat = mathutils.Quaternion(indata[7],indata[4],indata[5],indata[6]) + + md5_bones.append(createbone) + counter = counter + 1 + bnstr = (str(indata[0])) + Bns.append(bnstr) + + for pbone in md5_bones: + pbone.parent = md5_bones[pbone.parent_index].name + + bonecount = 0 + for armbone in bone: + temp_name = armbone[0][:30] + #print ("BONE NAME: ",len(temp_name)) + temp_name=str((temp_name)) + #temp_name = temp_name[1] + #print ("BONE NAME: ",temp_name) + bonecount +=1 + print ("-------------------------") + print ("----Creating--Armature---") + print ("-------------------------") + + #================================================================================================ + #Check armature if exist if so create or update or remove all and addnew bone + #================================================================================================ + #bpy.ops.object.mode_set(mode='OBJECT') + meshname ="ArmObject" + objectname = "armaturedata" + bfound = False + arm = None + for obj in bpy.data.objects: + if (obj.name == meshname): + bfound = True + arm = obj + break + + if bfound == False: + armdata = bpy.data.armatures.new(objectname) + ob_new = bpy.data.objects.new(meshname, armdata) + #ob_new = bpy.data.objects.new(meshname, 'ARMATURE') + #ob_new.data = armdata + bpy.context.scene.objects.link(ob_new) + #bpy.ops.object.mode_set(mode='OBJECT') + for i in bpy.context.scene.objects: i.selected = False #deselect all objects + ob_new.selected = True + #set current armature to edit the bone + bpy.context.scene.objects.active = ob_new + #set mode to able to edit the bone + bpy.ops.object.mode_set(mode='EDIT') + #newbone = ob_new.data.edit_bones.new('test') + #newbone.tail.y = 1 + print("creating bone(s)") + for bone in md5_bones: + #print(dir(bone)) + newbone = ob_new.data.edit_bones.new(bone.name) + #parent the bone + parentbone = None + print("bone name:",bone.name) + #note bone location is set in the real space or global not local + if bone.name != bone.parent: + + pos_x = bone.bindpos[0] + pos_y = bone.bindpos[1] + pos_z = bone.bindpos[2] + + #print( "LINKING:" , bone.parent ,"j") + parentbone = ob_new.data.edit_bones[bone.parent] + newbone.parent = parentbone + rotmatrix = bone.bindmat.to_matrix().resize4x4().rotation_part() + + #parent_head = parentbone.head * parentbone.matrix.to_quat().inverse() + #parent_tail = parentbone.tail * parentbone.matrix.to_quat().inverse() + #location=vector(pos_x,pos_y,pos_z) + #set_position = (parent_tail - parent_head) + location + #print("tmp head:",set_position) + + #pos_x = set_position.x + #pos_y = set_position.y + #pos_z = set_position.z + + newbone.head.x = parentbone.head.x + pos_x + newbone.head.y = parentbone.head.y + pos_y + newbone.head.z = parentbone.head.z + pos_z + print("head:",newbone.head) + newbone.tail.x = parentbone.head.x + (pos_x + bonesize * rotmatrix[1][0]) + newbone.tail.y = parentbone.head.y + (pos_y + bonesize * rotmatrix[1][1]) + newbone.tail.z = parentbone.head.z + (pos_z + bonesize * rotmatrix[1][2]) + else: + rotmatrix = bone.bindmat.to_matrix().resize4x4().rotation_part() + newbone.head.x = bone.bindpos[0] + newbone.head.y = bone.bindpos[1] + newbone.head.z = bone.bindpos[2] + newbone.tail.x = bone.bindpos[0] + bonesize * rotmatrix[1][0] + newbone.tail.y = bone.bindpos[1] + bonesize * rotmatrix[1][1] + newbone.tail.z = bone.bindpos[2] + bonesize * rotmatrix[1][2] + #print("no parent") + + bpy.context.scene.update() + + #================================================================================================== + #END BONE DATA BUILD + #================================================================================================== + VtxCol = [] + for x in range(len(Bns)): + #change the overall darkness of each material in a range between 0.1 and 0.9 + tmpVal = ((float(x)+1.0)/(len(Bns))*0.7)+0.1 + tmpVal = int(tmpVal * 256) + tmpCol = [tmpVal,tmpVal,tmpVal,0] + #Change the color of each material slightly + if x % 3 == 0: + if tmpCol[0] < 128: tmpCol[0] += 60 + else: tmpCol[0] -= 60 + if x % 3 == 1: + if tmpCol[1] < 128: tmpCol[1] += 60 + else: tmpCol[1] -= 60 + if x % 3 == 2: + if tmpCol[2] < 128: tmpCol[2] += 60 + else: tmpCol[2] -= 60 + #Add the material to the mesh + VtxCol.append(tmpCol) + + #================================================================================================== + # Bone Weight + #================================================================================================== + #read the RAWW0000 header + indata = unpack('20s3i',pskfile.read(32)) + recCount = indata[3] + printlog( "Nbr of RAWW0000 records: " + str(recCount) +"\n") + #RAWW0000 fields: Weight|PntIdx|BoneIdx + RWghts = [] + counter = 0 + while counter < recCount: + counter = counter + 1 + indata = unpack('fii',pskfile.read(12)) + RWghts.append([indata[1],indata[2],indata[0]]) + #RWghts fields = PntIdx|BoneIdx|Weight + RWghts.sort() + printlog( "len(RWghts)=" + str(len(RWghts)) + "\n") + #Tmsh.update() + + #set the Vertex Colors of the faces + #face.v[n] = RWghts[0] + #RWghts[1] = index of VtxCol + """ + for x in range(len(Tmsh.faces)): + for y in range(len(Tmsh.faces[x].v)): + #find v in RWghts[n][0] + findVal = Tmsh.faces[x].v[y].index + n = 0 + while findVal != RWghts[n][0]: + n = n + 1 + TmpCol = VtxCol[RWghts[n][1]] + #check if a vertex has more than one influence + if n != len(RWghts)-1: + if RWghts[n][0] == RWghts[n+1][0]: + #if there is more than one influence, use the one with the greater influence + #for simplicity only 2 influences are checked, 2nd and 3rd influences are usually very small + if RWghts[n][2] < RWghts[n+1][2]: + TmpCol = VtxCol[RWghts[n+1][1]] + Tmsh.faces[x].col.append(NMesh.Col(TmpCol[0],TmpCol[1],TmpCol[2],0)) + """ + if (DEBUGLOG): + logf.close() + #================================================================================================== + #Building Mesh + #================================================================================================== + print("vertex:",len(verts),"faces:",len(faces)) + me_ob.add_geometry(len(verts), 0, int(len(faces)/4)) + me_ob.verts.foreach_set("co", unpack_list(verts)) + + me_ob.faces.foreach_set("verts_raw", faces) + me_ob.faces.foreach_set("smooth", [False] * len(me_ob.faces)) + me_ob.update() + + #=================================================================================================== + #UV Setup + #=================================================================================================== + texture = [] + texturename = "text1" + #print(dir(bpy.data)) + if (len(faceuv) > 0): + me_ob.add_uv_texture() #add one uv texture + for i, face in enumerate(me_ob.faces): + blender_tface= me_ob.uv_textures[0].data[i] #face + blender_tface.uv1 = faceuv[i][0] #uv = (0,0) + blender_tface.uv2 = faceuv[i][1] #uv = (0,0) + blender_tface.uv3 = faceuv[i][2] #uv = (0,0) + texture.append(me_ob.uv_textures[0]) + + #for tex in me_ob.uv_textures: + #print("mesh tex:",dir(tex)) + #print((tex.name)) + + #=================================================================================================== + #Material Setup + #=================================================================================================== + materialname = "mat" + materials = [] + + matdata = bpy.data.materials.new(materialname) + #color is 0 - 1 not in 0 - 255 + #matdata.mirror_color=(float(0.04),float(0.08),float(0.44)) + matdata.diffuse_color=(float(0.04),float(0.08),float(0.44))#blue color + #print(dir(me_ob.uv_textures[0].data)) + texdata = None + texdata = bpy.data.textures[len(bpy.data.textures)-1] + if (texdata != None): + #print(texdata.name) + #print(dir(texdata)) + texdata.name = "texturelist1" + matdata.active_texture = texdata + materials.append(matdata) + #matdata = bpy.data.materials.new(materialname) + #materials.append(matdata) + #= make sure the list isnt too big + for material in materials: + #add material to the mesh list of materials + me_ob.add_material(material) + #=================================================================================================== + # + #=================================================================================================== + obmesh = bpy.data.objects.new(objName,me_ob) + #check if there is a material to set to + if len(materials) > 0: + obmesh.active_material = materials[0] #material setup tmp + + bpy.context.scene.objects.link(obmesh) + + bpy.context.scene.update() + + print ("PSK2Blender completed") +#End of def pskimport######################### + +def getInputFilename(filename): + checktype = filename.split('\\')[-1].split('.')[1] + print ("------------",filename) + if checktype.upper() != 'PSK': + print (" Selected file = ",filename) + raise (IOError, "The selected input file is not a *.psk file") + pskimport(filename) + +from bpy.props import * + +class IMPORT_OT_psk(bpy.types.Operator): + '''Load a skeleton mesh psk File''' + bl_idname = "import_scene.psk" + bl_label = "Import PSK" + + # List of operator properties, the attributes will be assigned + # to the class instance from the operator settings before calling. + path = StringProperty(name="File Path", description="File path used for importing the OBJ file", maxlen= 1024, default= "") + + def execute(self, context): + getInputFilename(self.properties.path) + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.manager + wm.add_fileselect(self) + return {'RUNNING_MODAL'} + +menu_func = lambda self, context: self.layout.operator(IMPORT_OT_psk.bl_idname, text="Skeleton Mesh (.psk)") + +def register(): + bpy.types.register(IMPORT_OT_psk) + bpy.types.INFO_MT_file_import.append(menu_func) + +def unregister(): + bpy.types.unregister(IMPORT_OT_psk) + bpy.types.INFO_MT_file_import.remove(menu_func) + +if __name__ == "__main__": + register() +#note this only read the data and will not be place in the scene +#getInputFilename('C:\\blenderfiles\\BotA.psk') +#getInputFilename('C:\\blenderfiles\\AA.PSK')