Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Phan <darkneter@gmail.com>2012-04-26 09:42:51 +0400
committerJohn Phan <darkneter@gmail.com>2012-04-26 09:42:51 +0400
commit3fc0ae81ffeef7e62339d4f8644d8c7625809adb (patch)
tree3d99c54fded0e6b1aa7f5f29f7177886ec626c75 /io_export_unreal_psk_psa.py
parente7b12df8eb73d249bd67b2920edac0f013dd59fc (diff)
clean up script and updated. still need get some bugs out a a bit.
Diffstat (limited to 'io_export_unreal_psk_psa.py')
-rw-r--r--io_export_unreal_psk_psa.py3904
1 files changed, 1776 insertions, 2128 deletions
diff --git a/io_export_unreal_psk_psa.py b/io_export_unreal_psk_psa.py
index f4a23726..792a4176 100644
--- a/io_export_unreal_psk_psa.py
+++ b/io_export_unreal_psk_psa.py
@@ -1,4 +1,4 @@
-# ***** GPL LICENSE BLOCK *****
+#====================== 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
@@ -13,13 +13,15 @@
# 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 *****
+#
+#======================= END GPL LICENSE BLOCK =============================
bl_info = {
"name": "Export Unreal Engine Format(.psk/.psa)",
"author": "Darknet/Optimus_P-Fat/Active_Trash/Sinsoft/VendorX",
- "version": (2, 5),
- "blender": (2, 6, 3),
+ "version": (2, 4),
+ "blender": (2, 6, 2),
+ "api": 36079,
"location": "File > Export > Skeletal Mesh/Animation Data (.psk/.psa)",
"description": "Export Skeleletal Mesh/Animation Data",
"warning": "",
@@ -77,16 +79,78 @@ Credit to:
- http://sinsoft.com
"""
+
+#===========================================================================
+"""
+NOTES for Jan 2012 refactor (Spoof)
+
+ * THIS IS A WORK IN PROGRESS. These modifications were originally
+ intended for internal use and are incomplete. Use at your own risk! *
+
+TODO
+
+- (Blender 2.62) changes to Matrix math
+- (Blender 2.62) check for long names
+- option to manually set the root bone for export
+
+CHANGES
+
+- new bone parsing to allow advanced rigging
+- identification of armature and mesh
+- removed the need to apply an action to the armature
+- fixed anim rate to work correctly in UDK (no more FPS fudging)
+- progress reporting while processing smooth groups
+- more informative logging
+- code refactor for clarity and modularity
+ - naming conventions unified to use lowercase_with_underscore
+ - C++ datatypes and PSK/PSA classes remain CamelCaseStyle for clarity
+ - names such as 'ut' and 'unreal' unified to 'udk'
+ - simplification of code structure
+ - removed legacy code paths
+
+USAGE
+
+This version of the exporter is more selective over which bones are considered
+part of the UDK skeletal mesh, and allows greater flexibility for adding
+control bones to aid in animation.
+
+Taking advantage of this script requires the following methodology:
+
+ * Place all exportable bones into a bone hierarchy extending from a single
+ root. This root bone must have use_deform enabled. All other root bones
+ in the armature must disable use_deform. *
+
+The script searches for a root bone with use_deform set true and considers all
+bones parented to it as part of the UDK skeletal mesh. Thus only these bones
+are exported and all other bones are ignored.
+
+This removes many restrictions on the rigger/animator, who can add control
+bone hierarchies to the rig, and keyframe any element into actions. With this
+approach you can build complex animation rigs in a similar vein to the Rigify
+add-on, by Nathan Vegdahl. However...
+
+ * Rigify is incompatible with this script *
+
+Rigify interlaces deformer bones within a single hierarchy making it difficult
+to deconstruct for export. It also splits some meta-rig bones into multiple
+deformer bones (bad for optimising a game character). I had partial success
+writing a parser for the structure, but it was taking too much time and,
+considering the other issues with Rigify, it was abandoned.
+"""
+#===========================================================================
+
+
import os
import time
import bpy
import mathutils
import random
import operator
-import bmesh
+import sys
from struct import pack
+
# REFERENCE MATERIAL JUST IN CASE:
#
# U = x / sqrt(x^2 + y^2 + z^2)
@@ -94,1772 +158,1719 @@ from struct import pack
#
# 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
-MaterialName = []
-
-# ======================================================================
-# TODO: remove this 1am hack
-nbone = 0
-exportmessage = "Export Finish"
-exportfile = True
-########################################################################
+# 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
+
+MaterialName = []
+
+
+#===========================================================================
+# Custom exception class
+#===========================================================================
+class Error( Exception ):
+
+ def __init__(self, message):
+ self.message = message
+
+
+#===========================================================================
+# Verbose logging with loop truncation
+#===========================================================================
+def verbose( msg, iteration=-1, max_iterations=4, msg_truncated="..." ):
+
+ if bpy.context.scene.udk_option_verbose == True:
+ # limit the number of times a loop can output messages
+ if iteration > max_iterations:
+ return
+ elif iteration == max_iterations:
+ print(msg_truncated)
+ return
+
+ print(msg)
+
+
+#===========================================================================
+# Log header/separator
+#===========================================================================
+def header( msg, justify='LEFT', spacer='_', cols=78 ):
+
+ if justify == 'LEFT':
+ s = '{:{spacer}<{cols}}'.format(msg+" ", spacer=spacer, cols=cols)
+
+ elif justify == 'RIGHT':
+ s = '{:{spacer}>{cols}}'.format(" "+msg, spacer=spacer, cols=cols)
+
+ else:
+ s = '{:{spacer}^{cols}}'.format(" "+msg+" ", spacer=spacer, cols=cols)
+
+ return "\n" + s + "\n"
+
+
+#===========================================================================
# 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))
+
+ 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)
-
+
+ def __init__(self):
+ self.X = 0.0
+ self.Y = 0.0
+ self.Z = 0.0
+ self.W = 1.0
+
+ def dump(self):
+ return pack('ffff', self.X, self.Y, self.Z, self.W)
+
+ 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)
+
+ def __init__(self, X=0.0, Y=0.0, Z=0.0):
+ self.X = X
+ self.Y = Y
+ self.Z = Z
+
+ def dump(self):
+ return pack('fff', self.X, self.Y, self.Z)
+
+ 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
-
+
+ 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):
+ return self.Orientation.dump() + self.Position.dump() + pack('4f', self.Length, self.XSize, self.YSize, self.ZSize)
+
+
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', str.encode(self.Name), str.encode(self.Group), self.TotalBones, self.RootInclude, self.KeyCompressionStyle, self.KeyQuotum, self.KeyPrediction, self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, self.NumRawFrames)
- return data
+
+ 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):
+ return pack('64s64siiiifffiii', str.encode(self.Name), str.encode(self.Group), self.TotalBones, self.RootInclude, self.KeyCompressionStyle, self.KeyQuotum, self.KeyPrediction, self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, self.NumRawFrames)
+
class VChunkHeader:
- def __init__(self, name, type_size):
- self.ChunkID = str.encode(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
-
+
+ def __init__(self, name, type_size):
+ self.ChunkID = str.encode(name) # length=20
+ self.TypeFlag = 1999801 # special value
+ self.DataSize = type_size
+ self.DataCount = 0
+
+ def dump(self):
+ return pack('20siii', self.ChunkID, self.TypeFlag, self.DataSize, self.DataCount)
+
+
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):
- print("DATA MATERIAL:",self.MaterialName)
- data = pack('64siLiLii', str.encode(self.MaterialName), self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle)
- return data
+
+ 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):
+ #print("DATA MATERIAL:",self.MaterialName)
+ return pack('64siLiLii', str.encode(self.MaterialName), self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle)
+
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', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
- return data
-#same as above - whatever - this is how Epic does it...
+ def __init__(self):
+ self.Name = "" # length = 64
+ self.Flags = 0 # DWORD
+ self.NumChildren = 0
+ self.ParentIndex = 0
+ self.BonePos = VJointPos()
+
+ def dump(self):
+ return pack('64sLii', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
+
+
+#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', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
- return data
-
+
+ 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):
+ return pack('64sLii', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
+
+
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
-
+
+ def __init__(self):
+ self.Weight = 0.0
+ self.PointIndex = 0
+ self.BoneIndex = 0
+
+ def dump(self):
+ return pack('fii', self.Weight, self.PointIndex, self.BoneIndex)
+
+
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
-
+
+ def __init__(self):
+ self.Position = FVector()
+ self.Orientation = FQuat()
+ self.Time = 0.0
+
+ def dump(self):
+ return self.Position.dump() + self.Orientation.dump() + pack('f', self.Time)
+
+
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()
-
+
+ def __init__(self):
+ self.PointIndex = 0 # WORD
+ self.U = 0.0
+ self.V = 0.0
+ self.MatIndex = 0 # BYTE
+ self.Reserved = 0 # BYTE
+ self.SmoothGroup = 0
+
+ def dump(self):
+ return pack('HHffBBH', self.PointIndex, 0, self.U, self.V, self.MatIndex, self.Reserved, 0)
+
+ 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) \
+ or cmp(self.SmoothGroup, other.SmoothGroup )
+
+ 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 VPointSimple:
+
+ def __init__(self):
+ self.Point = FVector()
+
+ def __cmp__(self, other):
+ return cmp(self.Point, other.Point)
+
+ def __hash__(self):
+ return hash(self._key())
+
+ def _key(self):
+ return (type(self).__name__, self.Point)
+
+ 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()
-
+
+ def __init__(self):
+ self.Point = FVector()
+ self.SmoothGroup = 0
+
+ def dump(self):
+ return self.Point.dump()
+
+ def __cmp__(self, other):
+ return cmp(self.Point, other.Point) \
+ or cmp(self.SmoothGroup, other.SmoothGroup)
+
+ def _key(self):
+ return (type(self).__name__, self.Point, self.SmoothGroup)
+
+ def __hash__(self):
+ return hash(self._key()) \
+ ^ hash(self.SmoothGroup)
+
+ 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
+
+ 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):
+ return pack('HHHBBL', self.WedgeIndex0, self.WedgeIndex1, self.WedgeIndex2, self.MatIndex, self.AuxMatIndex, self.SmoothingGroups)
# 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
+#===========================================================================
+# 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)
-
+
+ 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)
+
+
+#===========================================================================
+# PSK
+#===========================================================================
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()
- # modified by VendorX
- m.MaterialName = MaterialName[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:
+
+ 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):
+ self.Points.Data.append(p)
+
+ def AddWedge(self, w):
+ self.Wedges.Data.append(w)
+
+ def AddFace(self, f):
+ self.Faces.Data.append(f)
+
+ def AddMaterial(self, m):
+ self.Materials.Data.append(m)
+
+ def AddBone(self, b):
+ self.Bones.Data.append(b)
+
+ def AddInfluence(self, i):
+ 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()
+ # modified by VendorX
+ m.MaterialName = MaterialName[mat_index]
+ self.AddMaterial(m)
+ return m
+
+ def PrintOut(self):
+ print( "{:>16} {:}".format( "Points", len(self.Points.Data) ) )
+ print( "{:>16} {:}".format( "Wedges", len(self.Wedges.Data) ) )
+ print( "{:>16} {:}".format( "Faces", len(self.Faces.Data) ) )
+ print( "{:>16} {:}".format( "Materials", len(self.Materials.Data) ) )
+ print( "{:>16} {:}".format( "Bones", len(self.Bones.Data) ) )
+ print( "{:>16} {:}".format( "Influences", len(self.Influences.Data) ) )
+
+
+#===========================================================================
+# PSA
#
-# 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.
+# 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)
-
+# 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 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
-
-##################################################
+
+ 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 AddBone(self, b):
+ self.Bones.Data.append(b)
+
+ def AddAnimation(self, a):
+ self.Animations.Data.append(a)
+
+ def AddRawKey(self, k):
+ 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()
+ return self.GeneralHeader.dump() + self.Bones.dump() + self.Animations.dump() + self.RawKeys.dump()
+
+ def PrintOut(self):
+ print( "{:>16} {:}".format( "Bones", len(self.Bones.Data) ) )
+ print( "{:>16} {:}".format( "Animations", len(self.Animations.Data) ) )
+ print( "{:>16} {:}".format( "Raw keys", len(self.RawKeys.Data) ) )
+
+
+#===========================================================================
+# 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
+
+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()
+ #print(dir(bquat))
+ quat.X = bquat.x
+ quat.Y = bquat.y
+ quat.Z = bquat.z
+ quat.W = bquat.w
+ return quat
+
+
+#===========================================================================
#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.vertices[0]
- v1 = blender_face.vertices[1]
- v2 = blender_face.vertices[2]
-
- return (mesh.vertices[v0].co == mesh.vertices[v1].co or \
- mesh.vertices[v1].co == mesh.vertices[v2].co or \
- mesh.vertices[v2].co == mesh.vertices[v0].co)
- return False
-#blender 2.63 format using the Operators/Commands to merge the meshes into one
-def meshmerge(selectedobjects):
- bpy.ops.object.mode_set(mode='OBJECT')
- cloneobjects = []
- if len(selectedobjects) > 1:
- print("selectedobjects:",len(selectedobjects))
- count = 0 #reset count
- for count in range(len( selectedobjects)):
- #print("Index:",count)
- if selectedobjects[count] != None:
- cloneobjects.append(selectedobjects[count])
- for i in bpy.data.objects: i.select = False #deselect all objects
- count = 0 #reset count
- for count in range(len( cloneobjects)):
- if count == 0:
- bpy.context.scene.objects.active = cloneobjects[count]
- print("Set Active Object:",cloneobjects[count].name)
- cloneobjects[count].select = True
- bpy.ops.object.join()
- return cloneobjects[0]
-
-##################################################
+#===========================================================================
+def is_1d_face( face, mesh ):
+ #ID Vertex of id point
+ v0 = face.vertices[0]
+ v1 = face.vertices[1]
+ v2 = face.vertices[2]
+
+ return (mesh.vertices[v0].co == mesh.vertices[v1].co \
+ or mesh.vertices[v1].co == mesh.vertices[v2].co \
+ or mesh.vertices[v2].co == mesh.vertices[v0].co)
+ return False
+
+
+#===========================================================================
+# Smoothing group
+# (renamed to seperate it from VVertex.SmoothGroup)
+#===========================================================================
+class SmoothingGroup:
+
+ static_id = 1
+
+ def __init__(self):
+ self.faces = []
+ self.neighboring_faces = []
+ self.neighboring_groups = []
+ self.id = -1
+ self.local_id = SmoothingGroup.static_id
+ SmoothingGroup.static_id += 1
+
+ def __cmp__(self, other):
+ if isinstance(other, SmoothingGroup):
+ return cmp( self.local_id, other.local_id )
+ return -1
+
+ def __hash__(self):
+ return hash(self.local_id)
+
+ # searches neighboring faces to determine which smoothing group ID can be used
+ def get_valid_smoothgroup_id(self):
+ temp_id = 1
+ for group in self.neighboring_groups:
+ if group != None and group.id == temp_id:
+ if temp_id < 0x80000000:
+ temp_id = temp_id << 1
+ else:
+ raise Error("Smoothing Group ID Overflowed, Smoothing Group evidently has more than 31 neighboring groups")
+
+ self.id = temp_id
+ return self.id
+
+ def make_neighbor(self, new_neighbor):
+ if new_neighbor not in self.neighboring_groups:
+ self.neighboring_groups.append( new_neighbor )
+
+ def contains_face(self, face):
+ return (face in self.faces)
+
+ def add_neighbor_face(self, face):
+ if not face in self.neighboring_faces:
+ self.neighboring_faces.append( face )
+
+ def add_face(self, face):
+ if not face in self.faces:
+ self.faces.append( face )
+
+
+def determine_edge_sharing( mesh ):
+
+ edge_sharing_list = dict()
+
+ for edge in mesh.edges:
+ edge_sharing_list[edge.key] = []
+
+ for face in mesh.tessfaces:
+ for key in face.edge_keys:
+ if not face in edge_sharing_list[key]:
+ edge_sharing_list[key].append(face) # mark this face as sharing this edge
+
+ return edge_sharing_list
+
+
+def find_edges( mesh, key ):
+ """ Temp replacement for mesh.findEdges().
+ This is painfully slow.
+ """
+ for edge in mesh.edges:
+ v = edge.vertices
+ if key[0] == v[0] and key[1] == v[1]:
+ return edge.index
+
+
+def add_face_to_smoothgroup( mesh, face, edge_sharing_list, smoothgroup ):
+
+ if face in smoothgroup.faces:
+ return
+
+ smoothgroup.add_face(face)
+
+ for key in face.edge_keys:
+
+ edge_id = find_edges(mesh, key)
+
+ if edge_id != None:
+
+ # not sharp
+ if not( mesh.edges[edge_id].use_edge_sharp):
+
+ for shared_face in edge_sharing_list[key]:
+ if shared_face != face:
+ # recursive
+ add_face_to_smoothgroup( mesh, shared_face, edge_sharing_list, smoothgroup )
+ # sharp
+ else:
+ for shared_face in edge_sharing_list[key]:
+ if shared_face != face:
+ smoothgroup.add_neighbor_face( shared_face )
+
+
+def determine_smoothgroup_for_face( mesh, face, edge_sharing_list, smoothgroup_list ):
+
+ for group in smoothgroup_list:
+ if (face in group.faces):
+ return
+
+ smoothgroup = SmoothingGroup();
+ add_face_to_smoothgroup( mesh, face, edge_sharing_list, smoothgroup )
+
+ if not smoothgroup in smoothgroup_list:
+ smoothgroup_list.append( smoothgroup )
+
+
+def build_neighbors_tree( smoothgroup_list ):
+
+ for group in smoothgroup_list:
+ for face in group.neighboring_faces:
+ for neighbor_group in smoothgroup_list:
+ if neighbor_group.contains_face( face ) and neighbor_group not in group.neighboring_groups:
+ group.make_neighbor( neighbor_group )
+ neighbor_group.make_neighbor( group )
+
+
+#===========================================================================
+# parse_smooth_groups
+#===========================================================================
+def parse_smooth_groups( mesh ):
+
+ print("Parsing smooth groups...")
+
+ t = time.clock()
+ smoothgroup_list = []
+ edge_sharing_list = determine_edge_sharing(mesh)
+ #print("faces:",len(mesh.tessfaces))
+ interval = int(len(mesh.tessfaces) / 100)
+ #print("interval:",interval)
+ for face in mesh.tessfaces:
+ #print(dir(face))
+ determine_smoothgroup_for_face(mesh, face, edge_sharing_list, smoothgroup_list)
+ # progress indicator, writes to console without scrolling #bug in the code for face to tessface
+ #if face.index > 0 and (face.index % interval) == 0:
+ #print("Processing... {}%\r".format( int(face.index / len(mesh.tessfaces) * 100) ), end='')
+ #sys.stdout.flush()
+ print("Completed" + ' '*20)
+
+ verbose("len(smoothgroup_list)={}".format(len(smoothgroup_list)))
+
+ build_neighbors_tree(smoothgroup_list)
+
+ for group in smoothgroup_list:
+ group.get_valid_smoothgroup_id()
+
+ print("Smooth group parsing completed in {:.2f}s".format(time.clock() - t))
+ return smoothgroup_list
+
+
+#===========================================================================
# http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Triangulate_NMesh
-#blender 2.63 format using the Operators/Commands to convert the mesh to tri mesh
-def triangulateNMesh(object):
- print("Converting quad to tri mesh...",object.name)
- scene = bpy.context.scene
- bpy.ops.object.mode_set(mode='OBJECT')
- for i in scene.objects: i.select = False #deselect all objects
- object.select = True
- scene.objects.active = object #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
- print("Triangulate Mesh Done!")
- return object
-
-#Texture not working still find a way to get it work
-# Actual object parsing functions
-def parse_meshes(blender_meshes, psk_file):
- global exportmessage, exportfile
- print("Number of Object Meshes:",len(blender_meshes))
- for current_obj in blender_meshes: #number of mesh that should be one mesh here
- if current_obj.parent != None:
- print("Armature Found!")
- else:
- exportmessage = "Mesh is not parent to Armature!"
- bpy.context.scene.objects.unlink(current_obj)
- exportfile = False
- return
- #material
- if len(current_obj.material_slots) > 0:
- counter = 0
- while counter < len(current_obj.material_slots):
- print("[MATERIAL IDX:",counter,"=]")
- MaterialName.append(current_obj.material_slots[counter].name)
-
- #if current_obj.material_slots[counter].material.texture_slots[0] != None:
- #if current_obj.material_slots[counter].material.texture_slots[0].texture.image.filepath != None:
- #print("TEXTURE PATH:",current_obj.material_slots[counter].material.texture_slots[0].texture.image.filepath)
-
- #create the current material
- matdata = psk_file.GetMatByIndex(counter)
- matdata.MaterialName = current_obj.material_slots[counter].name
- matdata.TextureIndex = counter
- matdata.AuxMaterial = counter
- #print("materials: ",MaterialName[counter])
- counter += 1
- print("PSK INDEX:",matdata.TextureIndex)
-
- points = ObjMap()
- wedges = ObjMap()
- discarded_face_count = 0
- has_UV = True
- faceUV = None
- scene = bpy.context.scene #get current scene
- EXPORT_APPLY_MODIFIERS = True
- current_obj = triangulateNMesh(current_obj) #convert tri incase
- me = current_obj.to_mesh(scene, EXPORT_APPLY_MODIFIERS, 'PREVIEW') #apply modified mesh and write mesh
- faceuv = len(me.uv_textures) > 0 #check if has uv texture
- if faceuv:
- uv_layer = me.tessface_uv_textures.active.data[:]
- else:
- print("No UV Texture is added and will be ignore!")
- has_UV = False
-
- for face in me.tessfaces:
- #print("Vertices count:",len(face.vertices))
- #get or create the current material
- object_material_index = face.material_index
- psk_file.GetMatByIndex(object_material_index)
- #print(dir(face))
- #print(dir(uv_layer[face.index]))
- if len(face.vertices) != 3:
- #raise RuntimeError("Non-triangular face (%i)" % len(face.vertices))
- exportmessage = "MESH IS NOT TRIANGLE (Alt + T)"
- exportfile = False
- return
-
- if not is_1d_face(face,me):#face , Mesh
- wedge_list = []
- vect_list = []
-
- for i in range(3): #UV TEXTURE, VERTICES
- vert_index = face.vertices[i]
- vert = me.vertices[vert_index]
- uv = []
- if (has_UV):
- faceUV = uv_layer[face.index]#UV TEXTURE
- 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 = [faceUV.uv[i][0],faceUV.uv[i][1]] #OR bottom works better # 24 for cube
- else:
- uv = [0.0, 0.0]
-
- uv[1] = 1.0 - uv[1] #flip uv
-
- if bpy.context.scene.limituv:#set limit to 0-1 or not
- 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
-
- vect_list.append(FVector(vert.co.x, vert.co.y, vert.co.z))
- vpos = current_obj.matrix_local * vert.co
-
- # 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)
-
- # Determine face vertex order
- # get normal from blender
- no = 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 = face.vertices[0];
- dindex1 = face.vertices[1];
- dindex2 = face.vertices[2];
-
- me.vertices[dindex0].select = True
- me.vertices[dindex1].select = True
- me.vertices[dindex2].select = True
- print("normal vector coplanar with face! points:", me.vertices[dindex0].co, current_mesh.vertices[dindex1].co, current_mesh.vertices[dindex2].co)
- exportmessage = "One of the face is dot or period or line, coplanar with the face"
- exportfile = False
- bpy.context.scene.objects.unlink(current_obj)
- return
- #print(dir(current_face))
- #face.select = True
-
- if face.use_smooth == True:
- tri.SmoothingGroups = 0
- else:
- tri.SmoothingGroups = 1
-
- 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)
- if len(points.dict) > 32767:
- #raise RuntimeError("Vertex point reach max limited 32767 in pack data. Your",len(points.dict))
- print("Vertex point reach max limited 32767 in pack data. Your",len(points.dict))
- exportmessage = "You have went over the limit on your Vectices/Vertex points. Limit is 32767."
- exportfile = False
- bpy.context.scene.objects.unlink(current_obj)
- return
-
- 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 obvgroup in current_obj.vertex_groups:
- #print("bone gourp build:",obvgroup.name)#print bone name
- #print(dir(obvgroup))
- vert_list = []
- for current_vert in me.vertices:
- #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(obvgroup.index == vgroup.group):
- p = VPoint()
- vpos = current_obj.matrix_local * current_vert.co
- 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:",obvgroup.name, " No. Points:",len(vert_list))
- psk_file.VertexGroups[obvgroup.name] = vert_list
- #unlink copy of object from scene
- 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()
- #print(dir(bquat))
- quat.X = bquat.x
- quat.Y = bquat.y
- quat.Z = bquat.z
-
- quat.W = bquat.w
- return quat
+# blender 2.50 format using the Operators/command convert the mesh to tri mesh
+#===========================================================================
+def triangulate_mesh( object ):
+
+ verbose(header("triangulateNMesh"))
+ #print(type(object))
+ scene = bpy.context.scene
+
+ me_ob = object.copy()
+ me_ob.data = object.to_mesh(bpy.context.scene, True, 'PREVIEW') #write data object
+ bpy.context.scene.objects.link(me_ob)
+ bpy.context.scene.update()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for i in scene.objects:
+ i.select = False # deselect all objects
+
+ me_ob.select = True
+ scene.objects.active = me_ob
+
+ #bpy.ops.object.mode_set(mode='OBJECT')
+
+ print("Copy and Convert mesh just incase any way...")
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')# select all the face/vertex/edge
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.quads_convert_to_tris()
+ bpy.context.scene.update()
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ bpy.context.scene.udk_option_triangulate = True
+
+ verbose("Triangulated mesh")
+
+ me_ob.data = me_ob.to_mesh(bpy.context.scene, True, 'PREVIEW') #write data object
+ bpy.context.scene.update()
+ return me_ob
+
+
+#===========================================================================
+# parse_mesh
+#===========================================================================
+def parse_mesh( mesh, psk ):
+ #bpy.ops.object.mode_set(mode='OBJECT')
+ #error ? on commands for select object?
+ print(header("MESH", 'RIGHT'))
+ print("Mesh object:", mesh.name)
+ scene = bpy.context.scene
+ for i in scene.objects: i.select = False # deselect all objects
+ scene.objects.active = mesh
+ mesh = triangulate_mesh(mesh)
+ #print("FACES----:",len(mesh.data.tessfaces))
+ verbose("Working mesh object: {}".format(mesh.name))
+
+ #collect a list of the material names
+ print("Materials...")
+
+ mat_slot_index = 0
+
+ for slot in mesh.material_slots:
+
+ print(" Material {} '{}'".format(mat_slot_index, slot.name))
+ MaterialName.append(slot.name)
+ #if slot.material.texture_slots[0] != None:
+ #if slot.material.texture_slots[0].texture.image.filepath != None:
+ #print(" Texture path {}".format(slot.material.texture_slots[0].texture.image.filepath))
+ #create the current material
+ v_material = psk.GetMatByIndex(mat_slot_index)
+ v_material.MaterialName = slot.name
+ v_material.TextureIndex = mat_slot_index
+ v_material.AuxMaterial = mat_slot_index
+ mat_slot_index += 1
+ verbose(" PSK index {}".format(v_material.TextureIndex))
+
+ #END slot in mesh.material_slots
+
+ # object_mat = mesh.materials[0]
+ #object_material_index = mesh.active_material_index
+ #FIXME ^ this is redundant due to "= face.material_index" in face loop
-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 ---------------------- '
+ wedges = ObjMap()
+ points = ObjMap()
+ points_linked = {}
+
+ discarded_face_count = 0
- #If bone does not have parent that mean it the root bone
- if blender_bone.parent is 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_quaternion())
- #print("DIR:",dir(child_parent.matrix.to_quaternion()))
- quat_parent = child_parent.matrix.to_quaternion().inverted()
- parent_head = quat_parent * child_parent.head
- parent_tail = quat_parent * child_parent.tail
-
- set_position = (parent_tail - parent_head) + blender_bone.head
- else:
- # ROOT BONE
- #This for root
- set_position = parent_matrix * blender_bone.head #ARMATURE OBJECT Locction
- rot_mat = blender_bone.matrix * parent_matrix.to_3x3() #ARMATURE OBJECT Rotation
- #print(dir(rot_mat))
-
- quat = make_fquat_default(rot_mat.to_quaternion())
-
- #print ("[[======= FINAL POSITION:", set_position)
- final_parent_id = parent_id
-
- #RG/RE -
- #if we are not separated 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)
+ smoothgroup_list = parse_smooth_groups(mesh.data)
+
+ print("{} faces".format(len(mesh.data.tessfaces)))
+
+ print("Smooth groups active:", bpy.context.scene.udk_option_smoothing_groups)
+
+ for face in mesh.data.tessfaces:
+
+ smoothgroup_id = 0x80000000
+
+ for smooth_group in smoothgroup_list:
+ if smooth_group.contains_face(face):
+ smoothgroup_id = smooth_group.id
+ break
+
+ #print ' -- Dumping UVs -- '
+ #print current_face.uv_textures
+ # modified by VendorX
+ object_material_index = face.material_index
+
+ if len(face.vertices) != 3:
+ raise Error("Non-triangular face (%i)" % len(face.vertices))
+
+ #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(face, mesh.data):
+
+ wedge_list = []
+ vect_list = []
+
+ #get or create the current material
+ psk.GetMatByIndex(object_material_index)
- 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.PointIndex = point_index
- #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):
- global exportmessage, exportfile
- 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 is 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 is None]
- #will ingore this part of the ocde
- """
- if len(current_armature.bones) == 0:
- #raise RuntimeError("Warning add two bones else it will crash the unreal editor.")
- exportmessage = "Warning add two bones else it will crash the unreal editor."
- exportfile = False
- return
- if len(current_armature.bones) == 1:
- #raise RuntimeError("Warning add one more bone else it will crash the unreal editor.")
- exportmessage = "Warning add one more bone else it will crash the unreal editor."
- exportfile = False
- return
-
- mainbonecount = 0;
- for current_bone in current_armature.bones: #list the bone. #note this will list all the bones.
- if(current_bone.parent is None):
- mainbonecount += 1
- print("Main Bone",mainbonecount)
- if mainbonecount > 1:
- print("Warning there no main bone.")
- #raise RuntimeError("There too many Main bones. Number main bones:",mainbonecount)
- exportmessage = "There no Main Bone!"
- exportfile = False
- return
- for current_bone in current_armature.bones: #list the bone. #note this will list all the bones.
- if(current_bone.parent is None):
- parse_bone(current_bone, psk_file, psa_file, 0, 0, current_obj.matrix_local, 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 -----')
- render_data = blender_scene.render
- bHaveAction = True
-
- anim_rate = render_data.fps
-
- print("==== Blender Settings ====")
+ face_index = face.index
+ has_uv = False
+ face_uv = None
+
+ if len(mesh.data.uv_textures) > 0:
+ has_uv = True
+ uv_layer = mesh.data.tessface_uv_textures.active
+ face_uv = uv_layer.data[face_index]
+ #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 = face.vertices[i]
+ vert = mesh.data.vertices[vert_index]
+ uv = []
+ #assumes 3 UVs Per face (for now)
+ if (has_uv):
+ if len(face_uv.uv) != 3:
+ print("WARNING: face has more or less than 3 UV coordinates - writing 0,0...")
+ uv = [0.0, 0.0]
+ else:
+ uv = [face_uv.uv[i][0],face_uv.uv[i][1]] #OR bottom works better # 24 for cube
+ else:
+ #print ("No UVs?")
+ uv = [0.0, 0.0]
+
+ #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]
+
+ # clamp UV coords if udk_option_clamp_uv is True
+ if bpy.context.scene.udk_option_clamp_uv:
+ 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 = mesh.matrix_local * vert.co
+
+ # Create the point
+ p = VPoint()
+ p.Point.X = vpos.x
+ p.Point.Y = vpos.y
+ p.Point.Z = vpos.z
+ if bpy.context.scene.udk_option_smoothing_groups:#is this necessary?
+ p.SmoothGroup = smoothgroup_id
+
+ lPoint = VPointSimple()
+ lPoint.Point.X = vpos.x
+ lPoint.Point.Y = vpos.y
+ lPoint.Point.Z = vpos.z
+
+ if lPoint in points_linked:
+ if not(p in points_linked[lPoint]):
+ points_linked[lPoint].append(p)
+ else:
+ points_linked[lPoint] = [p]
+
+ # Create the wedge
+ w = VVertex()
+ w.MatIndex = object_material_index
+ w.PointIndex = points.get(p) # store keys
+ w.U = uv[0]
+ w.V = uv[1]
+ if bpy.context.scene.udk_option_smoothing_groups:#is this necessary?
+ w.SmoothGroup = smoothgroup_id
+ index_wedge = wedges.get(w)
+ wedge_list.append(index_wedge)
+
+ #print results
+ #print("result PointIndex={}, U={:.6f}, V={:.6f}, wedge_index={}".format(
+ # w.PointIndex,
+ # w.U,
+ # w.V,
+ # index_wedge))
+
+ #END for i in range(3)
- print ('Scene: %s Start Frame: %i, End Frame: %i' % (blender_scene.name, blender_scene.frame_start, blender_scene.frame_end))
- print ('Frames Per Sec: %i' % anim_rate)
- print ("Default FPS: 24" )
-
- cur_frame_index = 0
- if (bpy.context.scene.UEActionSetSettings == '1') or (bpy.context.scene.UEActionSetSettings == '2'):
- print("Action Set(s) Settings Idx:",bpy.context.scene.UEActionSetSettings)
- print("[==== Action list ====]")
-
- print("Number of Action set(s):",len(bpy.data.actions))
-
- for action in bpy.data.actions:#current number action sets
- print("+Action Name:",action.name)
- print("Group Count:",len(action.groups))
- #print("Groups:")
- #for bone in action.groups:
- #print("> Name: ",bone.name)
- #print(dir(bone))
-
- amatureobject = None #this is the armature set to none
- bonenames = [] #bone name of the armature bones list
-
- for arm in blender_armatures:
- amatureobject = arm
- #print(dir(amatureobject))
- collection = amatureobject.myCollectionUEA #collection of the object
- print("\n[==== Armature Object ====]")
- if amatureobject != None:
- print("+Name:",amatureobject.name)
- print("+Number of bones:", len(amatureobject.pose.bones),"\n")
- for bone in amatureobject.pose.bones:
- bonenames.append(bone.name)
-
- for ActionNLA in bpy.data.actions:
- FoundAction = True
- if bpy.context.scene.UEActionSetSettings == '2':
- for c in collection:
- if c.name == ActionNLA.name:
- if c.mybool == True:
- FoundAction = True
- else:
- FoundAction = False
- break
- if FoundAction == False:
- print("========================================")
- print("Skipping Action Set!",ActionNLA.name)
- print("Action Group Count:", len(ActionNLA.groups))
- print("Bone Group Count:", len(amatureobject.pose.bones))
- print("========================================")
- #break
-
- nobone = 0
- nomatchbone = 0
+ # Determine face vertex order
- baction = True
- #print("\nChecking actions matching groups with bone names...")
- #Check if the bone names matches the action groups names
- print("=================================")
- print("=================================")
- for abone in bonenames:
- #print("bone name:",abone)
- bfound = False
- for group in ActionNLA.groups:
- #print("name:>>",abone)
- if abone == group.name:
- nobone += 1
- bfound = True
- break
- if bfound == False:
- #print("Not Found!:",abone)
- nomatchbone += 1
- #else:
- #print("Found!:",abone)
-
- print("Armature Bones Count:",nobone , " Action Groups Counts:",len(ActionNLA.groups)," Left Out Count:",nomatchbone)
- #if the bones are less some missing bones that were added to the action group names than export this
- if (nobone <= len(ActionNLA.groups)) and (bpy.context.scene.unrealignoreactionmatchcount == True) :
- #print("Action Set match: Pass")
- print("Ingore Action groups Count from Armature bones.")
- baction = True
- #if action groups matches the bones length and names matching the gourps do something
- elif ((len(ActionNLA.groups) == len(bonenames)) and (nobone == len(ActionNLA.groups))):
- #print("Action Set match: Pass")
- baction = True
- else:
- print("Action Set match: Fail")
- #print("Action Name:",ActionNLA.name)
- baction = False
-
- if (baction == True) and (FoundAction == True):
- arm = amatureobject #set armature object
- 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
- #print("Last Action Name:",arm.animation_data.action.name)
- arm.animation_data.action = ActionNLA
- #print("Set Action Name:",arm.animation_data.action.name)
- bpy.context.scene.update()
- act = arm.animation_data.action
- 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.frame_range
- #print("max frame:",framemax)
- start_frame = int(framemin)
- end_frame = int(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) # UNUSED
- 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.frame_set(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] # UNUSED
-
- #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)
- #print(dir(posebonemat))
-
- #print("quat",posebonemat)
- #
- # Error looop action get None in matrix
- # looping on each armature give invert and normalize for None
- #
- parent_pose = pose_bone.parent
-
- if parent_pose != None:
- parentposemat = mathutils.Matrix(parent_pose.matrix)
- posebonemat = parentposemat.inverted() * posebonemat
-
- head = posebonemat.to_translation()
- quat = posebonemat.to_quaternion().normalized()
-
- 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("------------------------------------\n")
- else:
- print("[==== Action Set ====]")
- print("Action Name:",ActionNLA.name)
- print("Action Group Count:", len(ActionNLA.groups))
- print("Bone Group Count:", len(amatureobject.pose.bones))
- print("Action set Skip!")
- print("------------------------------------\n")
- print("==== Finish Action Build(s) ====")
- else:
- print("[==== Action Set Single Export====]")
- #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("---- Action Start ----")
- print("Action Name:",action_name)
- #look for min and max frame that current set keys
- framemin, framemax = act.frame_range
- #print("max frame:",framemax)
- start_frame = int(framemin)
- end_frame = int(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) # UNUSED
- 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.frame_set(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] # UNUSED
-
- #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.inverted() * posebonemat
- head = posebonemat.to_translation()
- quat = posebonemat.to_quaternion().normalized()
- vkey = VQuatAnimKey()
- vkey.Position.X = head.x
- vkey.Position.Y = head.y
- vkey.Position.Z = head.z
- #print("quat:",quat)
- 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("---- Action End ----")
- print("==== Finish Action Build ====")
-
-def fs_callback(filename, context):
- #this deal with repeat export and the reset settings
- global nbone, exportmessage, exportfile
- nbone = 0
-
- start_time = time.clock()
-
- print ("========EXPORTING TO UNREAL SKELETAL MESH FORMATS========\r\n")
- print("Blender Version:", bpy.app.version[1],"-")
-
- 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.frame_current #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.select):
- #print("mesh object select")
- copymesh = next_obj.copy()
- bpy.context.scene.objects.link(copymesh)
- selectmesh.append(copymesh)
- if next_obj.type == 'ARMATURE':
- blender_armature.append(next_obj)
- if (next_obj.select):
- #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 there 1 mesh in scene add to the array
- if len(blender_meshes) == 1:
- mesh = blender_meshes[0]
- print("MESH NAME",blender_meshes[0].name)
- blender_meshes = []
- copymesh = mesh.copy()
- bpy.context.scene.objects.link(copymesh)
- blender_meshes.append(copymesh)
- print(" - One Mesh Scene")
- #if there more than one mesh and one mesh select add to array
- elif (len(blender_meshes) > 1) and (len(selectmesh) == 1):
- if selectmesh[0].location.x != 0 and selectmesh[0].location.y != 0 and selectmesh[0].location.z != 0:
- exportmessage = "Error, Mesh Object not Center right should be (0,0,0)."
- for mesh in selectmesh:#remove object
- bpy.context.scene.objects.unlink(mesh)
- exportfile = False
- return
- blender_meshes = []
- copymesh = selectmesh[0]
- blender_meshes.append(copymesh)
- print(" - One Mesh [Select]")
- elif (len(blender_meshes) > 1) and (len(selectmesh) >= 1):#if there more than one mesh and some area off center check.
- #code build check for merge mesh before ops
- print("More than one mesh is selected!")
- centermesh = []
- notcentermesh = []
- countm = 0
- for countm in range(len(selectmesh)):
- #selectmesh[]
- if selectmesh[countm].location.x == 0 and selectmesh[countm].location.y == 0 and selectmesh[countm].location.z == 0:
- centermesh.append(selectmesh[countm])
- else:
- notcentermesh.append(selectmesh[countm])
- if len(centermesh) > 0:
- print("Center Object Found!")
- blender_meshes = []
- selectmesh = []
- countm = 0
- for countm in range(len(centermesh)):
- selectmesh.append(centermesh[countm])
- for countm in range(len(notcentermesh)):
- selectmesh.append(notcentermesh[countm])
- blender_meshes.append(meshmerge(selectmesh))
- else:
- bmesh = False
- print("Center Object Not Found")
- else:
- print(" - Too Many Meshes!")
- exportmessage = " - Select Mesh(s) For Export!"
- exportfile = False
- print(" - Select One Mesh Object!")
- bmesh = False
- return
-
- 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(" - Too Armature Meshes!")
- print(" - [Select] One Armature!")
- else:
- barmature = False
- exportmessage = "None Armature! Create One!"
- return
- bMeshScale = True
- bMeshCenter = True
- if len(blender_meshes) > 0:
- if blender_meshes[0].scale.x == 1 and blender_meshes[0].scale.y == 1 and blender_meshes[0].scale.z == 1:
- #print("Okay")
- bMeshScale = True
- elif blender_meshes[0].scale.x != 1 and blender_meshes[0].scale.y != 1 and blender_meshes[0].scale.z != 1:
- print("Error, Mesh Object not scale right should be (1,1,1).")
- exportmessage = "Error, Mesh Object not scale right should be (1,1,1)."
- exportfile = False
- return
- elif blender_meshes[0].location.x == 0 and blender_meshes[0].location.y == 0 and blender_meshes[0].location.z == 0:
- #print("Okay")
- bMeshCenter = True
- return
- elif blender_meshes[0].location.x != 0 and blender_meshes[0].location.y != 0 and blender_meshes[0].location.z != 0:
- print("Error, Mesh Object not center.",blender_meshes[0].location)
- #exportmessage = "Error, Mesh Object not center."
- exportmessage = "Error, Mesh Object not center."
- exportfile = False
- bMeshCenter = False
- return
- else:
- exportmessage = "Please Select your Meshes that matches that Armature for Export."
- exportfile = False
- return
- else:
- bmesh = False
- bArmatureScale = True
- bArmatureCenter = True
- if blender_armature and blender_armature[0] is not None:
- if blender_armature[0].scale.x == 1 and blender_armature[0].scale.y == 1 and blender_armature[0].scale.z == 1:
- #print("Okay")
- bArmatureScale = True
- else:
- print("Error, Armature Object not scale right should be (1,1,1).")
- exportmessage = "Error, Armature Object not scale right should be (1,1,1)."
- bArmatureScale = False
- exportfile = False
- return
- if blender_armature[0].location.x == 0 and blender_armature[0].location.y == 0 and blender_armature[0].location.z == 0:
- #print("Okay")
- bArmatureCenter = True
- else:
- print("Error, Armature Object not center.",blender_armature[0].location)
- exportmessage = "Error, Armature Object not center."
- bArmatureCenter = False
- exportfile = False
- return
+ # TODO: convert to Blender.Mathutils
+ # get normal from blender
+ no = face.normal
+ # 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)
+
+ 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 = face.vertices[0];
+ dindex1 = face.vertices[1];
+ dindex2 = face.vertices[2];
+
+ mesh.data.vertices[dindex0].select = True
+ mesh.data.vertices[dindex1].select = True
+ mesh.data.vertices[dindex2].select = True
+
+ raise Error("Normal coplanar with face! points:", mesh.data.vertices[dindex0].co, mesh.data.vertices[dindex1].co, mesh.data.vertices[dindex2].co)
+
+ face.select = True
+ #print("smooth:",(current_face.use_smooth))
+ #not sure if this right
+ #tri.SmoothingGroups
+ if face.use_smooth == True:
+ tri.SmoothingGroups = 1
+ else:
+ tri.SmoothingGroups = 0
+
+ #tri.SmoothingGroups = 1
+ tri.MatIndex = object_material_index
+
+ if bpy.context.scene.udk_option_smoothing_groups:
+ tri.SmoothingGroups = smoothgroup_id
+
+ psk.AddFace(tri)
+
+ #END if not is_1d_face(current_face, mesh.data)
+
+ else:
+ discarded_face_count += 1
+ #END face in mesh.data.faces
+
+ print("{} points".format(len(points.dict)))
+
+ for point in points.items():
+ psk.AddPoint(point)
+ if len(points.dict) > 32767:
+ raise Error("Mesh vertex limit exceeded! {} > 32767".format(len(points.dict)))
+
+ print("{} wedges".format(len(wedges.dict)))
+
+ for wedge in wedges.items():
+ psk.AddWedge(wedge)
+
+ # alert the user to degenerate face issues
+ if discarded_face_count > 0:
+ print("WARNING: Mesh contained degenerate faces (non-planar)")
+ print(" Discarded {} faces".format(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
+
+ #[print(x, len(points_linked[x])) for x in points_linked]
+ #print("pointsindex length ",len(points_linked))
+ #vertex group
+
+ # all vertex groups of the mesh (obj)...
+ for obj_vertex_group in mesh.vertex_groups:
+
+ #print(" bone group build:",obj_vertex_group.name)#print bone name
+ #print(dir(obj_vertex_group))
+ verbose("obj_vertex_group.name={}".format(obj_vertex_group.name))
+
+ vertex_list = []
+
+ # all vertices in the mesh...
+ for vertex in mesh.data.vertices:
+ #print(dir(vertex))
+ # all groups this vertex is a member of...
+ for vgroup in vertex.groups:
+
+ if vgroup.group == obj_vertex_group.index:
+
+ vertex_weight = vgroup.weight
+ p = VPointSimple()
+ vpos = mesh.matrix_local * vertex.co
+ p.Point.X = vpos.x
+ p.Point.Y = vpos.y
+ p.Point.Z = vpos.z
+
+ for point in points_linked[p]:
+ point_index = points.get(point) #point index
+ v_item = (point_index, vertex_weight)
+ vertex_list.append(v_item)
+
+ #bone name, [point id and wieght]
+ #print("Add Vertex Group:",obj_vertex_group.name, " No. Points:",len(vertex_list))
+ psk.VertexGroups[obj_vertex_group.name] = vertex_list
+
+ # remove the temporary triangulated mesh
+ if bpy.context.scene.udk_option_triangulate == True:
+ verbose("Removing temporary triangle mesh: {}".format(mesh.name))
+ bpy.ops.object.mode_set(mode='OBJECT') # OBJECT mode
+ mesh.parent = None # unparent to avoid phantom links
+ bpy.context.scene.objects.unlink(mesh) # unlink
+
+
+#===========================================================================
+# Collate bones that belong to the UDK skeletal mesh
+#===========================================================================
+def parse_armature( armature, psk, psa ):
+
+ print(header("ARMATURE", 'RIGHT'))
+ verbose("Armature object: {} Armature data: {}".format(armature.name, armature.data.name))
+
+ # generate a list of root bone candidates
+ root_candidates = [b for b in armature.data.bones if b.parent == None and b.use_deform == True]
+
+ # should be a single, unambiguous result
+ if len(root_candidates) == 0:
+ raise Error("Cannot find root for UDK bones. The root bone must use deform.")
+
+ if len(root_candidates) > 1:
+ raise Error("Ambiguous root for UDK. More than one root bone is using deform.")
+
+ # prep for bone collection
+ udk_root_bone = root_candidates[0]
+ udk_bones = []
+ BoneUtil.static_bone_id = 0 # replaces global
+
+ # traverse bone chain
+ print("{: <3} {: <48} {: <20}".format("ID", "Bone", "Status"))
+ print()
+ recurse_bone(udk_root_bone, udk_bones, psk, psa, 0, armature.matrix_local)
+
+ # final validation
+ if len(udk_bones) < 3:
+ raise Error("Less than three bones may crash UDK (legacy issue?)")
+
+ # return a list of bones making up the entire udk skel
+ # this is passed to parse_animation instead of working from keyed bones in the action
+ return udk_bones
+
+
+#===========================================================================
+# bone current bone
+# bones bone list
+# psk the PSK file object
+# psa the PSA file object
+# parent_id
+# parent_matrix
+# indent text indent for recursive log
+#===========================================================================
+def recurse_bone( bone, bones, psk, psa, parent_id, parent_matrix, indent="" ):
+
+ status = "Ok"
+
+ bones.append(bone);
+
+ if not bone.use_deform:
+ status = "No effect"
+
+ # calc parented bone transform
+ if bone.parent != None:
+ quat = make_fquat(bone.matrix.to_quaternion())
+ quat_parent = bone.parent.matrix.to_quaternion().inverted()
+ parent_head = quat_parent * bone.parent.head
+ parent_tail = quat_parent * bone.parent.tail
+ translation = (parent_tail - parent_head) + bone.head
+
+ # calc root bone transform
+ else:
+ translation = parent_matrix * bone.head # ARMATURE OBJECT Location
+ rot_matrix = bone.matrix * parent_matrix.to_3x3() # ARMATURE OBJECT Rotation
+ quat = make_fquat_default(rot_matrix.to_quaternion())
+
+ bone_id = BoneUtil.static_bone_id # ALT VERS
+ BoneUtil.static_bone_id += 1 # ALT VERS
+
+ child_count = len(bone.children)
+
+ psk.AddBone( make_vbone(bone.name, parent_id, child_count, quat, translation) )
+ psa.StoreBone( make_namedbonebinary(bone.name, parent_id, child_count, quat, translation, 1) )
+
+ #RG - dump influences for this bone - use the data we collected in the mesh dump phase to map our bones to vertex groups
+ if bone.name in psk.VertexGroups:
+
+ vertex_list = psk.VertexGroups[bone.name]
+ #print("vertex list:", len(vertex_list), " of >" ,bone.name )
+ for vertex_data in vertex_list:
- #print("location:",blender_armature[0].location.x)
-
- if (bmesh == False) or (barmature == False) or (bArmatureCenter == False) or (bArmatureScale == False)or (bMeshScale == False) or (bMeshCenter == False):
- exportmessage = "Export Fail! Check Log."
- print("=================================")
- print("= Export Fail! =")
- print("=================================")
- return
- else:
- exportmessage = "Export Finish!"
- #print("blender_armature:",dir(blender_armature[0]))
- #print(blender_armature[0].scale)
+ point_index = vertex_data[0]
+ vertex_weight = vertex_data[1]
+ influence = VRawBoneInfluence()
+ influence.Weight = vertex_weight
+ influence.BoneIndex = bone_id
+ influence.PointIndex = point_index
+ #print (" AddInfluence to vertex {}, weight={},".format(point_index, vertex_weight))
+ psk.AddInfluence(influence)
+
+ else:
+ status = "No vertex group"
+ #FIXME overwriting previous status error?
+
+ print("{:<3} {:<48} {:<20}".format(bone_id, indent+bone.name, status))
+
+ #bone.matrix_local
+ #recursively dump child bones
+
+ for child_bone in bone.children:
+ recurse_bone(child_bone, bones, psk, psa, bone_id, parent_matrix, " "+indent)
- 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.frame_set(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.frame_set(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.frame_set(cur_frame) #set frame back to original frame
- print ("Exception during Animation Parse")
- raise
-
- # reset current frame
- if exportfile == False:
- print("EXPORT STOP!")
- return
- context.scene.frame_set(cur_frame) #set frame back to original frame
+# FIXME rename? remove?
+class BoneUtil:
+ static_bone_id = 0 # static property to replace global
+
+
+#===========================================================================
+# armature the armature
+# udk_bones list of bones to be exported
+# actions_to_export list of actions to process for export
+# psa the PSA file object
+#===========================================================================
+def parse_animation( armature, udk_bones, actions_to_export, psa ):
+
+ print(header("ANIMATION", 'RIGHT'))
+
+ context = bpy.context
+ anim_rate = context.scene.render.fps
+
+ verbose("Armature object: {}".format(armature.name))
+ print("Scene: {} FPS: {} Frames: {} to {}".format(context.scene.name, anim_rate, context.scene.frame_start, context.scene.frame_end))
+ print("Processing {} action(s)".format(len(actions_to_export)))
+ print()
+ if armature.animation_data == None:
+ print("None Actions Set! skipping...")
+ return
+ restoreAction = armature.animation_data.action # Q: is animation_data always valid?
+
+ restoreFrame = context.scene.frame_current # we already do this in export_proxy, but we'll do it here too for now
+ raw_frame_index = 0 # used to set FirstRawFrame, seperating actions in the raw keyframe array
+
+ # action loop...
+ for action in actions_to_export:
+
+ # removed: check for armature with no animation; all it did was force you to add one
+
+ if not len(action.fcurves):
+ print("{} has no keys, skipping".format(action.name))
+ continue
+
+ # apply action to armature and update scene
+ armature.animation_data.action = action
+ context.scene.update()
+
+ # min/max frames define range
+ framemin, framemax = action.frame_range
+ start_frame = int(framemin)
+ end_frame = int(framemax)
+ scene_range = range(start_frame, end_frame + 1)
+ frame_count = len(scene_range)
+
+ # create the AnimInfoBinary
+ anim = AnimInfoBinary()
+ anim.Name = action.name
+ anim.Group = "" # unused?
+ anim.NumRawFrames = frame_count
+ anim.AnimRate = anim_rate
+ anim.FirstRawFrame = raw_frame_index
+
+ print("{}, frames {} to {} ({} frames)".format(action.name, start_frame, end_frame, frame_count))
+
+ # removed: bone lookup table
+
+ # build a list of pose bones relevant to the collated udk_bones
+ # fixme: could be done once, prior to loop?
+ udk_pose_bones = []
+ for b in udk_bones:
+ for pb in armature.pose.bones:
+ if b.name == pb.name:
+ udk_pose_bones.append(pb)
+ break;
+
+ # sort in the order the bones appear in the PSA file
+ ordered_bones = {}
+ ordered_bones = sorted([(psa.UseBone(b.name), b) for b in udk_pose_bones], key=operator.itemgetter(0))
+
+ # NOTE: posebone.bone references the obj/edit bone
+ # REMOVED: unique_bone_indexes is redundant?
+
+ # frame loop...
+ for i in range(frame_count):
+
+ frame = scene_range[i]
+
+ #verbose("FRAME {}".format(i), i) # test loop sampling
+
+ # advance to frame (automatically updates the pose)
+ context.scene.frame_set(frame)
+
+ # compute the key for each bone
+ for bone_data in ordered_bones:
+
+ bone_index = bone_data[0]
+ pose_bone = bone_data[1]
+ pose_bone_matrix = mathutils.Matrix(pose_bone.matrix)
+
+ if pose_bone.parent != None:
+ pose_bone_parent_matrix = mathutils.Matrix(pose_bone.parent.matrix)
+ pose_bone_matrix = pose_bone_parent_matrix.inverted() * pose_bone_matrix
+
+ head = pose_bone_matrix.to_translation()
+ quat = pose_bone_matrix.to_quaternion().normalized()
+
+ if pose_bone.parent != None:
+ quat = make_fquat(quat)
+ else:
+ quat = make_fquat_default(quat)
+
+ vkey = VQuatAnimKey()
+ vkey.Position.X = head.x
+ vkey.Position.Y = head.y
+ vkey.Position.Z = head.z
+ vkey.Orientation = quat
+
+ # frame delta = 1.0 / fps
+ vkey.Time = 1.0 / float(anim_rate) # according to C++ header this is "disregarded"
+
+ psa.AddRawKey(vkey)
+
+ # END for bone_data in ordered_bones
+
+ raw_frame_index += 1
+
+ # END for i in range(frame_count)
+
+ anim.TotalBones = len(ordered_bones) # REMOVED len(unique_bone_indexes)
+ anim.TrackTime = float(frame_count) # frame_count/anim.AnimRate makes more sense, but this is what actually works in UDK
+
+ verbose("anim.TotalBones={}, anim.TrackTime={}".format(anim.TotalBones, anim.TrackTime))
+
+ psa.AddAnimation(anim)
+
+ # END for action in actions
+
+ # restore
+ armature.animation_data.action = restoreAction
+ context.scene.frame_set(restoreFrame)
+
+
+#===========================================================================
+# Collate actions to be exported
+# Modify this to filter for one, some or all actions. For now use all.
+# RETURNS list of actions
+#===========================================================================
+def collate_actions():
+ verbose(header("collate_actions"))
+ actions_to_export = []
+
+ for action in bpy.data.actions:
+
+ verbose(" + {}".format(action.name))
+ actions_to_export.append(action)
+
+ return actions_to_export
+
+
+#===========================================================================
+# Locate the target armature and mesh for export
+# RETURNS armature, mesh
+#===========================================================================
+def find_armature_and_mesh():
+ verbose(header("find_armature_and_mesh", 'LEFT', '<', 60))
+
+ context = bpy.context
+ active_object = context.active_object
+ armature = None
+ mesh = None
+
+ # TODO:
+ # this could be more intuitive
+
+ # try the active object
+ if active_object and active_object.type == 'ARMATURE':
+ armature = active_object
+
+ # otherwise, try for a single armature in the scene
+ else:
+ all_armatures = [obj for obj in context.scene.objects if obj.type == 'ARMATURE']
+
+ if len(all_armatures) == 1:
+ armature = all_armatures[0]
+
+ elif len(all_armatures) > 1:
+ raise Error("Please select an armature in the scene")
+
+ else:
+ raise Error("No armatures in scene")
+
+ verbose("Found armature: {}".format(armature.name))
+
+
+ # try the active object
+ if active_object and active_object.type == 'MESH':
+
+ if active_object.parent == armature:
+ mesh = active_object
+
+ else:
+ raise Error("The selected mesh is not parented to the armature")
+
+ # otherwise, expect a single mesh parented to the armature (other object types are ignored)
+ else:
+ parented_meshes = [obj for obj in armature.children if obj.type == 'MESH']
+
+ if len(parented_meshes) == 1:
+ mesh = parented_meshes[0]
+
+ elif len(parented_meshes) > 1:
+ raise Error("More than one mesh parented to armature")
+
+ else:
+ raise Error("No mesh parented to armature")
+
+ verbose("Found mesh: {}".format(mesh.name))
+
+ return armature, mesh
+
+
+#===========================================================================
+# Returns a list of vertex groups in the mesh. Can be modified to filter
+# groups as necessary.
+# UNUSED
+#===========================================================================
+def collate_vertex_groups( mesh ):
+ verbose("collate_vertex_groups")
+ groups = []
+
+ for group in mesh.vertex_groups:
+
+ groups.append(group)
+ verbose(" " + group.name)
+
+ return groups
+
+
+#===========================================================================
+# Main
+#===========================================================================
+def export(filepath):
+ print(header("Export", 'RIGHT'))
+
+ t = time.clock()
+ context = bpy.context
+
+ print("Blender Version {}.{}.{}".format(bpy.app.version[0], bpy.app.version[1], bpy.app.version[2]))
+ print("Filepath: {}".format(filepath))
+
+ verbose("PSK={}, PSA={}".format(context.scene.udk_option_export_psk, context.scene.udk_option_export_psa))
+
+ # find armature and mesh
+ # [change this to implement alternative methods; raise Error() if not found]
+ udk_armature, udk_mesh = find_armature_and_mesh()
+
+ # check misc conditions
+ if not (udk_armature.scale.x == udk_armature.scale.y == udk_armature.scale.z == 1):
+ raise Error("bad armature scale: armature object should have uniform scale of 1 (ALT-S)")
+
+ if not (udk_mesh.scale.x == udk_mesh.scale.y == udk_mesh.scale.z == 1):
+ raise Error("bad mesh scale: mesh object should have uniform scale of 1 (ALT-S)")
+
+ if not (udk_armature.location.x == udk_armature.location.y == udk_armature.location.z == 0):
+ raise Error("bad armature location: armature should be located at origin (ALT-G)")
+
+ if not (udk_mesh.location.x == udk_mesh.location.y == udk_mesh.location.z == 0):
+ raise Error("bad mesh location: mesh should be located at origin (ALT-G)")
+
+ # prep
+ psk = PSKFile()
+ psa = PSAFile()
+
+ # step 1
+ parse_mesh(udk_mesh, psk)
+
+ # step 2
+ udk_bones = parse_armature(udk_armature, psk, psa)
+
+ # step 3
+ if context.scene.udk_option_export_psa == True:
+ actions = collate_actions()
+ parse_animation(udk_armature, udk_bones, actions, psa)
+
+ # write files
+ print(header("Exporting", 'CENTER'))
+
+ psk_filename = filepath + '.psk'
+ psa_filename = filepath + '.psa'
+
+ if context.scene.udk_option_export_psk == True:
+
+ print("Skeletal mesh data...")
+ psk.PrintOut()
+ file = open(psk_filename, "wb")
+ file.write(psk.dump())
+ file.close()
+ print("Exported: " + psk_filename)
+ print()
+
+ if context.scene.udk_option_export_psa == True:
+
+ print("Animation data...")
+ if not psa.IsEmpty():
+ psa.PrintOut()
+ file = open(psa_filename, "wb")
+ file.write(psa.dump())
+ file.close()
+ print("Exported: " + psa_filename)
+
+ else:
+ print("No Animation (.psa file) to export")
+
+ print()
+
+ print("Export completed in {:.2f} seconds".format((time.clock() - t)))
+
+from bpy.props import *
+
+#===========================================================================
+# Operator
+#===========================================================================
+class Operator_UDKExport( bpy.types.Operator ):
+
+ bl_idname = "object.udk_export"
+ bl_label = "Export now"
+ __doc__ = "Export to UDK"
+
+ def execute(self, context):
+ print( "\n"*8 )
+
+ scene = bpy.context.scene
+
+ scene.udk_option_export_psk = (scene.udk_option_export == '0' or scene.udk_option_export == '2')
+ scene.udk_option_export_psa = (scene.udk_option_export == '1' or scene.udk_option_export == '2')
+
+ filepath = get_dst_path()
+
+ # cache settings
+ restore_frame = scene.frame_current
+
+ message = "Finish Export!"
+ try:
+ export(filepath)
+
+ except Error as err:
+ print(err.message)
+ message = err.message
+
+ # restore settings
+ scene.frame_set(restore_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))
- print( "Current Script version: ",bl_info['version'])
- #MSG BOX EXPORT COMPLETE
- #...
-
- #DONE
- print ("PSK/PSA Export Complete")
-
-def write_data(path, context):
- print("//============================")
- print("// running psk/psa export...")
- print("//============================")
- fs_callback(path, context)
- pass
+ self.report({'ERROR'}, message)
+
+ # restore settings
+ scene.frame_set(restore_frame)
+
+ return {'FINISHED'}
+
+#===========================================================================
+# Operator
+#===========================================================================
+class Operator_ToggleConsole( bpy.types.Operator ):
+ bl_idname = "object.toggle_console"
+ bl_label = "Toggle console"
+ __doc__ = "Show or hide the console"
+
+ #def invoke(self, context, event):
+ # bpy.ops.wm.console_toggle()
+ # return{'FINISHED'}
+ def execute(self, context):
+ bpy.ops.wm.console_toggle()
+ return {'FINISHED'}
+
+
+#===========================================================================
+# Get filepath for export
+#===========================================================================
+def get_dst_path():
+ if bpy.context.scene.udk_option_filename_src == '0':
+ if bpy.context.active_object:
+ path = os.path.split(bpy.data.filepath)[0] + "\\" + bpy.context.active_object.name# + ".psk"
+ else:
+ path = os.path.split(bpy.data.filepath)[0] + "\\" + "Unknown";
+ else:
+ path = os.path.splitext(bpy.data.filepath)[0]# + ".psk"
+ return path
+
+# fixme
from bpy.props import *
-bpy.types.Scene.unrealfpsrate = IntProperty(
- name="fps rate",
- description="Set the frame per second (fps) for unreal",
- default=24,min=1,max=100)
-
-bpy.types.Scene.unrealexport_settings = EnumProperty(
- name="Export:",
- description="Select a export settings (psk/psa/all)",
- items = [("0","PSK","Export PSK"),
- ("1","PSA","Export PSA"),
- ("2","ALL","Export ALL")],
- default = '0')
-
-bpy.types.Scene.UEActionSetSettings = EnumProperty(
- name="Action Set(s) Export Type",
- description="For Exporting Single, All, and Select Action Set(s)",
- items = [("0","Single","Single Action Set Export"),
- ("1","All","All Action Sets Export"),
- ("2","Select","Select Action Set(s) Export")],
- default = '0')
-
-bpy.types.Scene.unrealtriangulatebool = BoolProperty(
- name="Triangulate Mesh",
- description="Convert Quad to Tri Mesh Boolean",
- default=False)
-
-bpy.types.Scene.unrealignoreactionmatchcount = BoolProperty(
- name="Acion Group Ignore Count",
- description="It will ingore Action group count as long is matches the " \
- "Armature bone count to match and over ride the armature " \
- "animation data",
- default=False)
-
-bpy.types.Scene.unrealdisplayactionsets = BoolProperty(
- name="Show Action Set(s)",
- description="Display Action Sets Information",
- default=False)
-
-bpy.types.Scene.unrealexportpsk = BoolProperty(
- name="bool export psa",
- description="bool for exporting this psk format",
- default=True)
-
-bpy.types.Scene.unrealexportpsa = BoolProperty(
- name="bool export psa",
- description="bool for exporting this psa format",
- default=True)
-
-bpy.types.Scene.limituv = BoolProperty(
- name="bool limit UV",
- description="limit UV co-ordinates to [0-1]",
- default=False)
-
-class UEAPropertyGroup(bpy.types.PropertyGroup):
- ## create Properties for the collection entries:
- mystring = bpy.props.StringProperty()
- mybool = bpy.props.BoolProperty(
- name="Export",
- description="Check if you want to export the action set",
- default = False)
-
-bpy.utils.register_class(UEAPropertyGroup)
-
-## create CollectionProperty and link it to the property class
-bpy.types.Object.myCollectionUEA = bpy.props.CollectionProperty(type = UEAPropertyGroup)
-bpy.types.Object.myCollectionUEA_index = bpy.props.IntProperty(min = -1, default = -1)
-
-## create operator to add or remove entries to/from the Collection
-class OBJECT_OT_add_remove_Collection_Items_UE(bpy.types.Operator):
- bl_label = "Add or Remove"
- bl_idname = "collection.add_remove_ueactions"
- __doc__ = """Button for Add, Remove, Refresh Action Set(s) list"""
- set = bpy.props.StringProperty()
-
- def invoke(self, context, event):
- obj = context.object
- collection = obj.myCollectionUEA
- if self.set == "remove":
- print("remove")
- index = obj.myCollectionUEA_index
- collection.remove(index) # This remove on item in the collection list function of index value
- if self.set == "add":
- print("add")
- added = collection.add() # This add at the end of the collection list
- added.name = "Action"+ str(random.randrange(0, 101, 2))
- if self.set == "refresh":
- print("refresh")
- # ArmatureSelect = None # UNUSED
- ActionNames = []
- BoneNames = []
- for obj in bpy.data.objects:
- if obj.type == 'ARMATURE' and obj.select == True:
- print("Armature Name:",obj.name)
- # ArmatureSelect = obj # UNUSED
- for bone in obj.pose.bones:
- BoneNames.append(bone.name)
- break
- actionsetmatchcount = 0
- for ActionNLA in bpy.data.actions:
- nobone = 0
- for group in ActionNLA.groups:
- for abone in BoneNames:
- if abone == group.name:
- nobone += 1
- break
- if (len(ActionNLA.groups) == len(BoneNames)) and (nobone == len(ActionNLA.groups)):
- actionsetmatchcount += 1
- ActionNames.append(ActionNLA.name)
- print("action list check")
- for action in ActionNames:
- BfoundAction = False
- for c in collection:
- if c.name == action:
- BfoundAction = True
- break
- if BfoundAction == False:
- added = collection.add() # This add at the end of the collection list
- added.name = action
- #print("finish...")
- return {'FINISHED'}
+
+#Added by [MGVS]
+bpy.types.Scene.udk_option_filename_src = EnumProperty(
+ name = "Filename",
+ description = "Sets the name for the files",
+ items = [ ('0', "From object", "Name will be taken from object name"),
+ ('1', "From Blend", "Name will be taken from .blend file name") ],
+ default = '0')
+
+bpy.types.Scene.udk_option_export_psk = BoolProperty(
+ name = "bool export psa",
+ description = "bool for exporting this psk format",
+ default = True)
+
+bpy.types.Scene.udk_option_export_psa = BoolProperty(
+ name = "bool export psa",
+ description = "bool for exporting this psa format",
+ default = True)
+
+bpy.types.Scene.udk_option_clamp_uv = BoolProperty(
+ name = "Clamp UV",
+ description = "Clamp UV co-ordinates to [0-1]",
+ default = False)
+
+bpy.types.Scene.udk_option_export = EnumProperty(
+ name = "Export",
+ description = "What to export",
+ items = [ ('0', "Mesh only", "Exports the PSK file for the skeletal mesh"),
+ ('1', "Animation only", "Export the PSA file for animations"),
+ ('2', "Mesh & Animation", "Export both PSK and PSA files") ],
+ default = '2')
+
+bpy.types.Scene.udk_option_verbose = BoolProperty(
+ name = "Verbose",
+ description = "Verbose console output",
+ default = False)
+
+bpy.types.Scene.udk_option_smoothing_groups = BoolProperty(
+ name = "Smooth Groups",
+ description = "Activate hard edges as smooth groups",
+ default = True)
+
+bpy.types.Scene.udk_option_triangulate = BoolProperty(
+ name = "Triangulate Mesh",
+ description = "Convert Quads to Triangles",
+ default = False)
+
+
+#===========================================================================
+# User interface
+#===========================================================================
+class Panel_UDKExport( bpy.types.Panel ):
+
+ bl_label = "UDK Export"
+ bl_idname = "OBJECT_PT_udk_tools"
+ #bl_space_type = "PROPERTIES"
+ #bl_region_type = "WINDOW"
+ #bl_context = "object"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+
+ #def draw_header(self, context):
+ # layout = self.layout
+ #obj = context.object
+ #layout.prop(obj, "select", text="")
+
+ #@classmethod
+ #def poll(cls, context):
+ # return context.active_object
+
+ def draw(self, context):
+ layout = self.layout
+ path = get_dst_path()
+
+ object_name = ""
+ #if context.object:
+ # object_name = context.object.name
+ if context.active_object:
+ object_name = context.active_object.name
+
+ layout.prop(context.scene, "udk_option_smoothing_groups")
+ layout.prop(context.scene, "udk_option_clamp_uv")
+ layout.prop(context.scene, "udk_option_verbose")
+
+ row = layout.row()
+ row.label(text="Active object: " + object_name)
+
+ layout.separator()
+
+ layout.prop(context.scene, "udk_option_filename_src")
+ row = layout.row()
+ row.label(text=path)
+
+ layout.separator()
+
+ layout.prop(context.scene, "udk_option_export")
+ layout.operator("object.udk_export")
+
+ layout.separator()
+
+ layout.operator("object.toggle_console")
+
+ layout.separator()
class ExportUDKAnimData(bpy.types.Operator):
- global exportmessage
+
'''Export Skeleton Mesh / Animation Data file(s)'''
bl_idname = "export_anim.udk" # this is important since its how bpy.ops.export.udk_anim_data is constructed
bl_label = "Export PSK/PSA"
@@ -1875,428 +1886,65 @@ class ExportUDKAnimData(bpy.types.Operator):
default="*.psk;*.psa",
options={'HIDDEN'},
)
- pskexportbool = BoolProperty(
- name="Export PSK",
- description="Export Skeletal Mesh",
- default= True,
- )
- psaexportbool = BoolProperty(
- name="Export PSA",
- description="Export Action Set (Animation Data)",
- default= True,
- )
- actionexportall = BoolProperty(
- name="All Actions",
- description="This will export all the actions that matches the " \
- "current armature",
- default=False,
- )
- ignoreactioncountexportbool = BoolProperty(
- name="Ignore Action Group Count",
- description="It will ignore action group count but as long it " \
- "matches the armature bone count to over ride the " \
- "animation data",
- default= False,
- )
- limituvbool = BoolProperty(
- name="Limit UV Co-ordinates",
- description="Limit UV co-ordinates to [0-1]",
- default= False,
- )
-
+ udk_option_smoothing_groups = bpy.types.Scene.udk_option_smoothing_groups
+ udk_option_clamp_uv = bpy.types.Scene.udk_option_clamp_uv
+ udk_option_verbose = bpy.types.Scene.udk_option_verbose
+ udk_option_filename_src = bpy.types.Scene.udk_option_filename_src
+ udk_option_export = bpy.types.Scene.udk_option_export
+
@classmethod
def poll(cls, context):
return context.active_object != None
def execute(self, context):
- #check if skeleton mesh is needed to be exported
- if (self.pskexportbool):
- bpy.context.scene.unrealexportpsk = True
- else:
- bpy.context.scene.unrealexportpsk = False
- #check if animation data is needed to be exported
- if (self.psaexportbool):
- bpy.context.scene.unrealexportpsa = True
- else:
- bpy.context.scene.unrealexportpsa = False
-
- if (self.actionexportall):
- bpy.context.scene.UEActionSetSettings = '1'#export one action set
- else:
- bpy.context.scene.UEActionSetSettings = '0'#export all action sets
-
- if(self.ignoreactioncountexportbool):
- bpy.context.scene.unrealignoreactionmatchcount = True
- else:
- bpy.context.scene.unrealignoreactionmatchcount = False
-
- if(self.limituvbool):
- bpy.types.Scene.limituv = True
- else:
- bpy.types.Scene.limituv = False
- write_data(self.filepath, context)
+ scene = bpy.context.scene
+ scene.udk_option_export_psk = (scene.udk_option_export == '0' or scene.udk_option_export == '2')
+ scene.udk_option_export_psa = (scene.udk_option_export == '1' or scene.udk_option_export == '2')
+
+ filepath = get_dst_path()
+
+ # cache settings
+ restore_frame = scene.frame_current
+
+ message = "Finish Export!"
+ try:
+ export(filepath)
+
+ except Error as err:
+ print(err.message)
+ message = err.message
+
+ # restore settings
+ scene.frame_set(restore_frame)
- self.report({'WARNING', 'INFO'}, exportmessage)
+ self.report({'WARNING', 'INFO'}, message)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
wm.fileselect_add(self)
- return {'RUNNING_MODAL'}
-
-class VIEW3D_PT_unrealtools_objectmode(bpy.types.Panel):
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_label = "Unreal Tools"
-
- @classmethod
- def poll(cls, context):
- return context.active_object
-
- def draw(self, context):
- layout = self.layout
- rd = context.scene
- layout.prop(rd, "unrealexport_settings",expand=True)
- layout.prop(rd, "UEActionSetSettings")
- layout.prop(rd, "unrealignoreactionmatchcount")
-
- #FPS #it use the real data from your scene
- layout.prop(rd.render, "fps")
- layout.prop(rd, "limituv")
- layout.operator(OBJECT_OT_UnrealExport.bl_idname)
-
- layout.prop(rd, "unrealdisplayactionsets")
-
- ArmatureSelect = None
- for obj in bpy.data.objects:
- if obj.type == 'ARMATURE' and obj.select == True:
- #print("Armature Name:",obj.name)
- ArmatureSelect = obj
- break
- #display armature actions list
- if ArmatureSelect != None and rd.unrealdisplayactionsets == True:
- layout.label(("Selected: "+ArmatureSelect.name))
- row = layout.row()
- row.template_list(obj, "myCollectionUEA", obj, "myCollectionUEA_index") # This show list for the collection
- col = row.column(align=True)
- col.operator("collection.add_remove_ueactions", icon="ZOOMIN", text="").set = "add" # This show a plus sign button
- col.operator("collection.add_remove_ueactions", icon="ZOOMOUT", text="").set = "remove" # This show a minus sign button
- col.operator("collection.add_remove_ueactions", icon="FILE_REFRESH", text="").set = "refresh" # This show a refresh sign button
-
- ##change name of Entry:
- if obj.myCollectionUEA:
- entry = obj.myCollectionUEA[obj.myCollectionUEA_index]
- layout.prop(entry, "name")
- layout.prop(entry, "mybool")
-
- layout.operator(OBJECT_OT_UTSelectedFaceSmooth.bl_idname)
- layout.operator(OBJECT_OT_UTRebuildMesh.bl_idname)
- layout.operator(OBJECT_OT_MeshClearWeights.bl_idname)
- layout.operator(OBJECT_OT_UTRebuildArmature.bl_idname)
- layout.operator(OBJECT_OT_DeleteActionSet.bl_idname)
- layout.operator(OBJECT_OT_ToggleConsle.bl_idname)
-
-
-
-class OBJECT_OT_UnrealExport(bpy.types.Operator):
- global exportmessage
- bl_idname = "export_mesh.udk" # XXX, name???
- bl_label = "Unreal Export"
- __doc__ = """Select export setting for .psk/.psa or both"""
-
- def invoke(self, context, event):
- 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 = os.path.splitext(bpy.data.filepath)[0] + ".psk"
- fs_callback(default_path, bpy.context)
- self.report({'ERROR'}, exportmessage)
- return{'FINISHED'}
-
-class OBJECT_OT_ToggleConsle(bpy.types.Operator):
- global exportmessage
- bl_idname = "object.toggleconsle" # XXX, name???
- bl_label = "Toggle Console"
- __doc__ = "Show or Hide Console"
-
- def invoke(self, context, event):
- bpy.ops.wm.console_toggle()
- return{'FINISHED'}
-
-class OBJECT_OT_UTSelectedFaceSmooth(bpy.types.Operator):
- bl_idname = "object.utselectfacesmooth" # XXX, name???
- bl_label = "Select Smooth faces"
- __doc__ = """It will only select smooth faces that is select mesh"""
-
- def invoke(self, context, event):
- print("----------------------------------------")
- print("Init Select Face(s):")
- bselected = False
- for obj in bpy.data.objects:
- if obj.type == 'MESH' and obj.select == True:
- smoothcount = 0
- flatcount = 0
- bpy.ops.object.mode_set(mode='OBJECT')#it need to go into object mode to able to select the faces
- for i in bpy.context.scene.objects: i.select = False #deselect all objects
- obj.select = True #set current object select
- bpy.context.scene.objects.active = obj #set active object
- mesh = bmesh.new();
- mesh.from_mesh(obj.data)
- for face in mesh.faces:
- print(dir(face))
- if face.smooth == True:
- face.select = True
- smoothcount += 1
- else:
- flatcount += 1
- face.select = False
- mesh.to_mesh(obj.data)
- bpy.context.scene.update()
- bpy.ops.object.mode_set(mode='EDIT')
- print("Select Smooth Count(s):",smoothcount," Flat Count(s):",flatcount)
- bselected = True
- break
- if bselected:
- print("Selected Face(s) Exectue!")
- self.report({'INFO'}, "Selected Face(s) Exectue!")
- else:
- print("Didn't select Mesh Object!")
- self.report({'INFO'}, "Didn't Select Mesh Object!")
- print("----------------------------------------")
- return{'FINISHED'}
-
-class OBJECT_OT_DeleteActionSet(bpy.types.Operator):
- bl_idname = "object.deleteactionset" # XXX, name???
- bl_label = "Delete Action Set"
- __doc__ = """It will remove the first top of the index of the action list. Reload file to remove it. It used for unable to delete action set. """
-
- def invoke(self, context, event):
- if len(bpy.data.actions) > 0:
- for action in bpy.data.actions:
- print("Action:",action.name)
- action.user_clear()
- break
- #bpy.data.actions.remove(act)
- print("finish")
- return{'FINISHED'}
-
-class OBJECT_OT_MeshClearWeights(bpy.types.Operator):
- bl_idname = "object.meshclearweights" # XXX, name???
- bl_label = "Mesh Clear Vertex Weights"
- __doc__ = """Clear selected mesh vertex group weights for the bones. Be sure you unparent the armature"""
-
- def invoke(self, context, event):
- for obj in bpy.data.objects:
- if obj.type == 'MESH' and obj.select == True:
- for vg in obj.vertex_groups:
- obj.vertex_groups.remove(vg)
- break
- return{'FINISHED'}
-
-class OBJECT_OT_UTRebuildArmature(bpy.types.Operator):
- bl_idname = "object.utrebuildarmature" # XXX, name???
- bl_label = "Rebuild Armature"
- __doc__ = """If mesh is deform when importing to unreal engine try this. It rebuild the bones one at the time by select one armature object scrape to raw setup build. Note the scale will be 1:1 for object mode. To keep from deforming"""
-
- def invoke(self, context, event):
- print("----------------------------------------")
- print("Init Rebuild Armature...")
- bselected = False
- for obj in bpy.data.objects:
- if obj.type == 'ARMATURE' and obj.select == True:
- currentbone = [] #select armature for roll copy
- print("Armature Name:",obj.name)
- objectname = "ArmatureDataPSK"
- meshname ="ArmatureObjectPSK"
- armdata = bpy.data.armatures.new(objectname)
- ob_new = bpy.data.objects.new(meshname, armdata)
- bpy.context.scene.objects.link(ob_new)
- bpy.ops.object.mode_set(mode='OBJECT')
- for i in bpy.context.scene.objects: i.select = False #deselect all objects
- ob_new.select = True
- bpy.context.scene.objects.active = obj
-
- bpy.ops.object.mode_set(mode='EDIT')
- for bone in obj.data.edit_bones:
- if bone.parent != None:
- currentbone.append([bone.name,bone.roll])
- else:
- currentbone.append([bone.name,bone.roll])
- bpy.ops.object.mode_set(mode='OBJECT')
- for i in bpy.context.scene.objects: i.select = False #deselect all objects
- bpy.context.scene.objects.active = ob_new
- bpy.ops.object.mode_set(mode='EDIT')
-
- for bone in obj.data.bones:
- bpy.ops.object.mode_set(mode='EDIT')
- newbone = ob_new.data.edit_bones.new(bone.name)
- newbone.head = bone.head_local
- newbone.tail = bone.tail_local
- for bonelist in currentbone:
- if bone.name == bonelist[0]:
- newbone.roll = bonelist[1]
- break
- if bone.parent != None:
- parentbone = ob_new.data.edit_bones[bone.parent.name]
- newbone.parent = parentbone
- print("Bone Count:",len(obj.data.bones))
- print("Hold Bone Count",len(currentbone))
- print("New Bone Count",len(ob_new.data.edit_bones))
- print("Rebuild Armture Finish:",ob_new.name)
- bpy.context.scene.update()
- bselected = True
- break
- if bselected:
- self.report({'INFO'}, "Rebuild Armature Finish!")
- else:
- self.report({'INFO'}, "Didn't Select Armature Object!")
- print("End of Rebuild Armature.")
- print("----------------------------------------")
- return{'FINISHED'}
-
-# rounded the vert locations to save a bit of blurb.. change the round value or remove for accuracy i suppose
-def rounded_tuple(tup):
- return tuple(round(value,4) for value in tup)
-
-def unpack_list(list_of_tuples):
- l = []
- for t in list_of_tuples:
- l.extend(t)
- return l
-
-class OBJECT_OT_UTRebuildMesh(bpy.types.Operator):
- bl_idname = "object.utrebuildmesh" # XXX, name???
- bl_label = "Rebuild Mesh"
- __doc__ = """It rebuild the mesh from scrape from the selected mesh object. Note the scale will be 1:1 for object mode. To keep from deforming"""
-
- def invoke(self, context, event):
- print("----------------------------------------")
- print("Init Mesh Bebuild...")
- bselected = False
- for obj in bpy.data.objects:
- if obj.type == 'MESH' and obj.select == True:
- for i in bpy.context.scene.objects: i.select = False #deselect all objects
- obj.select = True
- bpy.context.scene.objects.active = obj
- bpy.ops.object.mode_set(mode='OBJECT')
- me_ob = bpy.data.meshes.new(("Re_"+obj.name))
- mesh = obj.data
- faces = []
- verts = []
- smoothings = []
- uvfaces = []
- print("creating array build mesh...")
- mmesh = obj.to_mesh(bpy.context.scene,True,'PREVIEW')
- uv_layer = mmesh.tessface_uv_textures.active
- for face in mmesh.tessfaces:
- smoothings.append(face.use_smooth)#smooth or flat in boolean
- if uv_layer != None:#check if there texture data exist
- faceUV = uv_layer.data[face.index]
- uvs = []
- for uv in faceUV.uv:
- uvs.append((uv[0],uv[1]))
- uvfaces.append(uvs)
- print((face.vertices[:]))
- faces.extend([(face.vertices[0],face.vertices[1],face.vertices[2],face.vertices[3])])
- #vertex positions
- for vertex in mesh.vertices:
- verts.append(vertex.co.to_tuple())
- #vertices weight groups into array
- vertGroups = {} #array in strings
- for vgroup in obj.vertex_groups:
- vlist = []
- for v in mesh.vertices:
- for vg in v.groups:
- if vg.group == vgroup.index:
- vlist.append((v.index,vg.weight))
- #print((v.index,vg.weight))
- vertGroups[vgroup.name] = vlist
-
- print("creating mesh object...")
- #me_ob.from_pydata(verts, [], faces)
- me_ob.vertices.add(len(verts))
- me_ob.tessfaces.add(len(faces))
- me_ob.vertices.foreach_set("co", unpack_list(verts))
- me_ob.tessfaces.foreach_set("vertices_raw",unpack_list( faces))
- me_ob.tessfaces.foreach_set("use_smooth", smoothings)#smooth array from face
-
- #check if there is uv faces
- if len(uvfaces) > 0:
- uvtex = me_ob.tessface_uv_textures.new(name="retex")
- for i, face in enumerate(me_ob.tessfaces):
- blender_tface = uvtex.data[i] #face
- mfaceuv = uvfaces[i]
- if len(mfaceuv) == 3:
- blender_tface.uv1 = mfaceuv[0];
- blender_tface.uv2 = mfaceuv[1];
- blender_tface.uv3 = mfaceuv[2];
- if len(mfaceuv) == 4:
- blender_tface.uv1 = mfaceuv[0];
- blender_tface.uv2 = mfaceuv[1];
- blender_tface.uv3 = mfaceuv[2];
- blender_tface.uv4 = mfaceuv[3];
-
- me_ob.update()#need to update the information to able to see into the secne
- obmesh = bpy.data.objects.new(("Re_"+obj.name),me_ob)
- bpy.context.scene.update()
- #Build tmp materials
- materialname = "ReMaterial"
- for matcount in mesh.materials:
- matdata = bpy.data.materials.new(materialname)
- me_ob.materials.append(matdata)
- #assign face to material id
- for face in mesh.tessfaces:
- me_ob.faces[face.index].material_index = face.material_index
- #vertices weight groups
- for vgroup in vertGroups:
- group = obmesh.vertex_groups.new(vgroup)
- for v in vertGroups[vgroup]:
- group.add([v[0]], v[1], 'ADD')# group.add(array[vertex id],weight,add)
- bpy.context.scene.objects.link(obmesh)
- print("Mesh Material Count:",len(me_ob.materials))
- matcount = 0
- print("MATERIAL ID OREDER:")
- for mat in me_ob.materials:
- print("-Material:",mat.name,"INDEX:",matcount)
- matcount += 1
- print("Object Name:",obmesh.name)
- bpy.context.scene.update()
- bselected = True
- break
- if bselected:
- self.report({'INFO'}, "Rebuild Mesh Finish!")
- print("Finish Mesh Build...")
- else:
- self.report({'INFO'}, "Didn't Select Mesh Object!")
- print("Didn't Select Mesh Object!")
- print("----------------------------------------")
-
- return{'FINISHED'}
-
+ return {'RUNNING_MODAL'}
def menu_func(self, context):
- #bpy.context.scene.unrealexportpsk = True
- #bpy.context.scene.unrealexportpsa = True
default_path = os.path.splitext(bpy.data.filepath)[0] + ".psk"
self.layout.operator(ExportUDKAnimData.bl_idname, text="Skeleton Mesh / Animation Data (.psk/.psa)").filepath = default_path
-
+#===========================================================================
+# Entry
+#===========================================================================
def register():
- bpy.utils.register_module(__name__)
- bpy.types.INFO_MT_file_export.append(menu_func)
+ #print("REGISTER")
+ bpy.utils.register_module(__name__)
+ bpy.types.INFO_MT_file_export.append(menu_func)
def unregister():
- bpy.utils.unregister_module(__name__)
- bpy.types.INFO_MT_file_export.remove(menu_func)
-
+ #print("UNREGISTER")
+ bpy.utils.unregister_module(__name__)
+ bpy.types.INFO_MT_file_export.remove(menu_func)
+
if __name__ == "__main__":
- register()
+ #print("\n"*4)
+ print(header("UDK Export PSK/PSA Alpha 0.1", 'CENTER'))
+ register()
+
+#loader
+#filename = "D:/Projects/BlenderScripts/io_export_udk_psa_psk_alpha.py"
+#exec(compile(open(filename).read(), filename, 'exec')) \ No newline at end of file