Newer
Older
CoDEmanX
committed
bpy.ops.object.mode_set(mode='OBJECT')
CoDEmanX
committed
bpy.context.scene.udk_option_triangulate = True
CoDEmanX
committed
verbose("Triangulated mesh")
CoDEmanX
committed
me_ob.data = me_ob.to_mesh(bpy.context.scene, True, 'PREVIEW') # write data object
bpy.context.scene.update()
return me_ob
# copy mesh data and then merge them into one object
def meshmerge(selectedobjects):
bpy.ops.object.mode_set(mode='OBJECT') # object mode and not edit mode
cloneobjects = [] # object holder for copying object data
if len(selectedobjects) > 1:
print("selectedobjects:", len(selectedobjects)) # print select object
count = 0 # reset count
for count in range(len(selectedobjects)):
# print("Index:",count)
if selectedobjects[count] is not None:
me_da = selectedobjects[count].data.copy() # copy data
me_ob = selectedobjects[count].copy() # copy object
# note two copy two types else it will use the current data or mesh
me_ob.data = me_da # assign the data
bpy.context.scene.objects.link(me_ob) # link the object to the scene #current object location
print("Index:", count, "clone object", me_ob.name) # print clone object
cloneobjects.append(me_ob) # add object to the array
for i in bpy.data.objects:
i.select = False # deselect all objects
count = 0 # reset count
# begin merging the mesh together as one
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() # join object together
if len(cloneobjects) > 1:
bpy.types.Scene.udk_copy_merge = True
return cloneobjects[0]
CoDEmanX
committed
# sort the mesh center top list and not center at the last array.
# Base on order while select to merge mesh to make them center.
def sortmesh(selectmesh):
print("MESH SORTING...")
centermesh = []
notcentermesh = []
for countm in range(len(selectmesh)):
# if object are center add here
if selectmesh[countm].location.x == 0 and \
selectmesh[countm].location.y == 0 and \
selectmesh[countm].location.z == 0:
centermesh.append(selectmesh[countm])
else: # if not add here for not center
notcentermesh.append(selectmesh[countm])
selectmesh = []
# add mesh object in order for merge object
for countm in range(len(centermesh)):
selectmesh.append(centermesh[countm])
for countm in range(len(notcentermesh)):
selectmesh.append(notcentermesh[countm])
if len(selectmesh) == 1: # if there one mesh just do some here
return selectmesh[0] # return object mesh
else:
return meshmerge(selectmesh) # return merge object mesh
# ===========================================================================
# 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
setmesh = mesh
mesh = triangulate_mesh(mesh)
if bpy.types.Scene.udk_copy_merge is True:
bpy.context.scene.objects.unlink(setmesh)
# print("FACES----:",len(mesh.data.tessfaces))
verbose("Working mesh object: {}".format(mesh.name))
CoDEmanX
committed
# collect a list of the material names
print("Materials...")
CoDEmanX
committed
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] is not None:
if slot.material.texture_slots[0].texture.image.filepath is not 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
CoDEmanX
committed
# object_mat = mesh.materials[0]
# object_material_index = mesh.active_material_index
# FIXME ^ this is redundant due to "= face.material_index" in face loop
wedges = ObjMap()
points = ObjMap() # vertex
points_linked = {}
CoDEmanX
committed
discarded_face_count = 0
smoothgroup_list = parse_smooth_groups(mesh.data)
CoDEmanX
committed
print("{} faces".format(len(mesh.data.tessfaces)))
CoDEmanX
committed
print("Smooth groups active:", bpy.context.scene.udk_option_smoothing_groups)
CoDEmanX
committed
for face in mesh.data.tessfaces:
CoDEmanX
committed
smoothgroup_id = 0x80000000
CoDEmanX
committed
for smooth_group in smoothgroup_list:
if smooth_group.contains_face(face):
smoothgroup_id = smooth_group.id
break
# modified by VendorX
object_material_index = face.material_index
CoDEmanX
committed
if len(face.vertices) != 3:
raise Error("Non-triangular face (%i)" % len(face.vertices))
CoDEmanX
committed
# RG - apparently blender sometimes has problems when you do quad to triangle
# conversion, and ends up creating faces that have only TWO points -
CoDEmanX
committed
# 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
CoDEmanX
committed
if not is_1d_face(face, mesh.data):
CoDEmanX
committed
wedge_list = []
vect_list = []
CoDEmanX
committed
# get or create the current material
psk.GetMatByIndex(object_material_index)
face_index = face.index
has_uv = False
face_uv = None
CoDEmanX
committed
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]))
CoDEmanX
committed
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]
CoDEmanX
committed
# 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]
CoDEmanX
committed
# clamp UV coords if udk_option_clight_uv is True
if bpy.context.scene.udk_option_clight_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
CoDEmanX
committed
# 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))
CoDEmanX
committed
# Transform position for export
# vpos = vert.co * object_material_index
# should fixed this!!
vpos = mesh.matrix_local * vert.co
if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
# print("OK!")
vpos.x = vpos.x * bpy.context.scene.udk_option_scale
vpos.y = vpos.y * bpy.context.scene.udk_option_scale
vpos.z = vpos.z * bpy.context.scene.udk_option_scale
# print("scale pos:", vpos)
# 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
CoDEmanX
committed
if lPoint in points_linked:
if not(p in points_linked[lPoint]):
points_linked[lPoint].append(p)
else:
points_linked[lPoint] = [p]
CoDEmanX
committed
# 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)
CoDEmanX
committed
# print results
# print("result PointIndex={}, U={:.6f}, V={:.6f}, wedge_index={}".format(
# w.PointIndex,
# w.U,
# w.V,
# index_wedge))
CoDEmanX
committed
# END for i in range(3)
# Determine face vertex order
CoDEmanX
committed
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
# 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]
CoDEmanX
committed
mesh.data.vertices[dindex0].select = True
mesh.data.vertices[dindex1].select = True
mesh.data.vertices[dindex2].select = True
CoDEmanX
committed
raise Error("Normal coplanar with face! points: %s, %s, %s" % (str(mesh.data.vertices[dindex0].co),
str(mesh.data.vertices[dindex1].co),
str(mesh.data.vertices[dindex2].co)))
CoDEmanX
committed
face.select = True
if face.use_smooth is True:
tri.SmoothingGroups = 1
else:
tri.SmoothingGroups = 0
tri.MatIndex = object_material_index
if bpy.context.scene.udk_option_smoothing_groups:
tri.SmoothingGroups = smoothgroup_id
CoDEmanX
committed
psk.AddFace(tri)
# END if not is_1d_face(current_face, mesh.data)
else:
discarded_face_count += 1
CoDEmanX
committed
# END face in mesh.data.faces
CoDEmanX
committed
print("{} points".format(len(points.dict)))
CoDEmanX
committed
for point in points.items():
psk.AddPoint(point)
CoDEmanX
committed
if len(points.dict) > 32767:
raise Error("Mesh vertex limit exceeded! {} > 32767".format(len(points.dict)))
CoDEmanX
committed
print("{} wedges".format(len(wedges.dict)))
CoDEmanX
committed
for wedge in wedges.items():
psk.AddWedge(wedge)
CoDEmanX
committed
# 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))
CoDEmanX
committed
# 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
CoDEmanX
committed
# [print(x, len(points_linked[x])) for x in points_linked]
# print("pointsindex length ",len(points_linked))
# vertex group
CoDEmanX
committed
# all vertex groups of the mesh (obj)...
for obj_vertex_group in mesh.vertex_groups:
CoDEmanX
committed
# 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))
CoDEmanX
committed
vertex_list = []
CoDEmanX
committed
# 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
if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
vpos.x = vpos.x * bpy.context.scene.udk_option_scale
vpos.y = vpos.y * bpy.context.scene.udk_option_scale
vpos.z = vpos.z * bpy.context.scene.udk_option_scale
p.Point.X = vpos.x
p.Point.Y = vpos.y
p.Point.Z = vpos.z
# print(p)
# print(len(points_linked[p]))
try: # check if point doesn't give error
point_index = points.get(point) # point index
v_item = (point_index, vertex_weight)
except Exception: # if get error ignore them # not safe I think
CoDEmanX
committed
# 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
CoDEmanX
committed
# remove the temporary triangulated mesh
if bpy.context.scene.udk_option_triangulate is 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):
CoDEmanX
committed
print(header("ARMATURE", 'RIGHT'))
verbose("Armature object: {} Armature data: {}".format(armature.name, armature.data.name))
CoDEmanX
committed
# generate a list of root bone candidates
root_candidates = [b for b in armature.data.bones if b.parent is None and b.use_deform is True]
CoDEmanX
committed
# 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.")
CoDEmanX
committed
if len(root_candidates) > 1:
raise Error("Ambiguous root for UDK. More than one root bone is using deform.")
CoDEmanX
committed
# prep for bone collection
udk_root_bone = root_candidates[0]
udk_bones = []
BoneUtil.static_bone_id = 0 # replaces global
CoDEmanX
committed
# 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)
CoDEmanX
committed
# final validation
if len(udk_bones) < 3:
raise Error("Less than three bones may crash UDK (legacy issue?)")
CoDEmanX
committed
# 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=""):
CoDEmanX
committed
status = "Ok"
bones.append(bone)
if not bone.use_deform:
status = "No effect"
CoDEmanX
committed
# calc parented bone transform
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())
# udk_option_scale bones here?
if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
translation.x = translation.x * bpy.context.scene.udk_option_scale
translation.y = translation.y * bpy.context.scene.udk_option_scale
translation.z = translation.z * bpy.context.scene.udk_option_scale
bone_id = BoneUtil.static_bone_id # ALT VERS
BoneUtil.static_bone_id += 1 # ALT VERS
CoDEmanX
committed
child_count = len(bone.children)
CoDEmanX
committed
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))
CoDEmanX
committed
# 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:
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?
CoDEmanX
committed
print("{:<3} {:<48} {:<20}".format(bone_id, indent + bone.name, status))
CoDEmanX
committed
# bone.matrix_local
# recursively dump child bones
CoDEmanX
committed
for child_bone in bone.children:
recurse_bone(child_bone, bones, psk, psa, bone_id, parent_matrix, " " + indent)
# 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):
CoDEmanX
committed
print(header("ANIMATION", 'RIGHT'))
CoDEmanX
committed
context = bpy.context
anim_rate = context.scene.render.fps
CoDEmanX
committed
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)\n".format(len(actions_to_export)))
# if animation data was not create for the armature it will skip the exporting action set(s)
if armature.animation_data is None:
print("None Actions Set! skipping...")
return
restoreAction = armature.animation_data.action # Q: is animation_data always valid?
# we already do this in export_proxy, but we'll do it here too for now
restoreFrame = context.scene.frame_current
raw_frame_index = 0 # used to set FirstRawFrame, seperating actions in the raw keyframe array
CoDEmanX
committed
# action loop...
for action in actions_to_export:
CoDEmanX
committed
# 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
# note if loop all actions that is not armature it will override and will break armature animation
armature.animation_data.action = action
context.scene.update()
CoDEmanX
committed
# 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)
CoDEmanX
committed
# 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
CoDEmanX
committed
print("{}, frames {} to {} ({} frames)".format(action.name, start_frame, end_frame, frame_count))
CoDEmanX
committed
# removed: bone lookup table
CoDEmanX
committed
# 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)
# 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))
CoDEmanX
committed
# NOTE: posebone.bone references the obj/edit bone
# REMOVED: unique_bone_indexes is redundant?
CoDEmanX
committed
# frame loop...
for i in range(frame_count):
CoDEmanX
committed
frame = scene_range[i]
CoDEmanX
committed
# verbose("FRAME {}".format(i), i) # test loop sampling
CoDEmanX
committed
# advance to frame (automatically updates the pose)
context.scene.frame_set(frame)
CoDEmanX
committed
# compute the key for each bone
for bone_data in ordered_bones:
CoDEmanX
committed
bone_index = bone_data[0]
pose_bone = bone_data[1]
pose_bone_matrix = mathutils.Matrix(pose_bone.matrix)
CoDEmanX
committed
if pose_bone.parent is not None:
pose_bone_parent_matrix = mathutils.Matrix(pose_bone.parent.matrix)
pose_bone_matrix = pose_bone_parent_matrix.inverted() * pose_bone_matrix
CoDEmanX
committed
head = pose_bone_matrix.to_translation()
quat = pose_bone_matrix.to_quaternion().normalized()
CoDEmanX
committed
if pose_bone.parent is not None:
quat = make_fquat(quat)
else:
quat = make_fquat_default(quat)
CoDEmanX
committed
# scale animation position here?
if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
head.x = head.x * bpy.context.scene.udk_option_scale
head.y = head.y * bpy.context.scene.udk_option_scale
head.z = head.z * bpy.context.scene.udk_option_scale
vkey = VQuatAnimKey()
vkey.Position.X = head.x
vkey.Position.Y = head.y
vkey.Position.Z = head.z
vkey.Orientation = quat
CoDEmanX
committed
# frame delta = 1.0 / fps
vkey.Time = 1.0 / anim_rate # according to C++ header this is "disregarded"
CoDEmanX
committed
psa.AddRawKey(vkey)
CoDEmanX
committed
# END for bone_data in ordered_bones
raw_frame_index += 1
CoDEmanX
committed
# END for i in range(frame_count)
CoDEmanX
committed
# REMOVED len(unique_bone_indexes)
anim.TotalBones = len(ordered_bones)
# frame_count/anim.AnimRate makes more sense, but this is what actually works in UDK
anim.TrackTime = float(frame_count)
verbose("anim.TotalBones={}, anim.TrackTime={}".format(anim.TotalBones, anim.TrackTime))
CoDEmanX
committed
psa.AddAnimation(anim)
CoDEmanX
committed
# 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 = []
CoDEmanX
committed
for action in bpy.data.actions:
if bpy.context.scene.udk_option_selectanimations: # check if needed to select actions set for exporting it
print("Action Set is selected!")
bready = False
for actionlist in bpy.context.scene.udkas_list: # list the action set from the list
if actionlist.name == action.name and actionlist.bmatch is True and actionlist.bexport is True:
bready = True
print("Added Action Set:", action.name)
break
if bready is False: # don't export it
print("Skipping Action Set:", action.name)
continue
verbose(" + {}".format(action.name)) # action set name
actions_to_export.append(action) # add to the action array
CoDEmanX
committed
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))
CoDEmanX
committed
context = bpy.context
active_object = context.active_object
armature = None
mesh = None
CoDEmanX
committed
# TODO:
# this could be more intuitive
# bpy.ops.object.mode_set(mode='OBJECT')
CoDEmanX
committed
if bpy.context.scene.udk_option_selectobjects: # if checked select object true do list object on export
print("select mode:")
if len(bpy.context.scene.udkArm_list) > 0:
print("Armature Name:", bpy.context.scene.udkArm_list[bpy.context.scene.udkArm_list_idx].name)
for obj in bpy.context.scene.objects:
if obj.name == bpy.context.scene.udkArm_list[bpy.context.scene.udkArm_list_idx].name:
armature = obj
break
else:
raise Error("There is no Armature in the list!")
meshselected = []
# parented_meshes = [obj for obj in armature.children if obj.type == 'MESH']
meshes = [obj for obj in bpy.context.scene.objects if obj.type == 'MESH']
for obj in meshes:
# print(dir(obj))
bexportmesh = False
# print("PARENT MESH:",obj.name)
for udkmeshlist in bpy.context.scene.udkmesh_list:
if obj.name == udkmeshlist.name and udkmeshlist.bexport is True:
bexportmesh = True
break
if bexportmesh is True:
print("Mesh Name:", obj.name, " < SELECT TO EXPORT!")
meshselected.append(obj)
print("MESH COUNT:", len(meshselected))
# try the active object
if active_object and active_object.type == 'MESH' and len(meshselected) == 0:
if active_object.parent == armature:
mesh = active_object
else:
raise Error("The selected mesh is not parented to the armature")
CoDEmanX
committed
# otherwise, expect a single mesh parented to the armature (other object types are ignored)
else:
print("Number of meshes:", len(meshes))
print("Number of meshes (selected):", len(meshes))
if len(meshes) == 1:
mesh = meshes[0]
CoDEmanX
committed
if len(meshselected) >= 1:
mesh = sortmesh(meshselected)
else:
raise Error("More than one mesh(s) parented to armature. Select object(s)!")
else:
raise Error("No mesh parented to armature")
else: # if not check for select function from the list work the code here
print("normal mode:")
# try the active object
if active_object and active_object.type == 'ARMATURE':
armature = active_object
bpy.ops.object.mode_set(mode='OBJECT')
# otherwise, try for a single armature in the scene
else:
# bpy.ops.object.mode_set(mode='OBJECT')
all_armatures = [obj for obj in bpy.context.scene.objects if obj.type == 'ARMATURE']
CoDEmanX
committed
if len(all_armatures) == 1: # if armature has one scene just assign it
armature = all_armatures[0]
elif len(all_armatures) > 1: # if there more armature then find the select armature
barmselect = False
for _armobj in all_armatures:
if _armobj.select:
armature = _armobj
barmselect = True
break
if barmselect is False:
raise Error("Please select an armatures in the scene")
else:
raise Error("No armatures in scene")
CoDEmanX
committed
verbose("Found armature: {}".format(armature.name))
CoDEmanX
committed
meshselected = []
parented_meshes = [obj for obj in armature.children if obj.type == 'MESH']
CoDEmanX
committed
if len(armature.children) == 0:
raise Error("The selected Armature has no mesh parented to the Armature Object!")
CoDEmanX
committed
for obj in armature.children:
# print(dir(obj))
if obj.type == 'MESH' and obj.select is True:
meshselected.append(obj)
# try the active object
if active_object and active_object.type == 'MESH' and len(meshselected) == 0:
if active_object.parent == armature:
mesh = active_object
else:
raise Error("The selected mesh is not parented to the armature")
CoDEmanX
committed
# otherwise, expect a single mesh parented to the armature (other object types are ignored)
else:
print("Number of meshes:", len(parented_meshes))
print("Number of meshes (selected):", len(meshselected))
if len(parented_meshes) == 1:
mesh = parented_meshes[0]
CoDEmanX
committed
elif len(parented_meshes) > 1:
if len(meshselected) >= 1:
mesh = sortmesh(meshselected)
else:
raise Error("More than one mesh(s) parented to armature. Select object(s)!")
else:
raise Error("No mesh parented to armature")
CoDEmanX
committed
verbose("Found mesh: {}".format(mesh.name))
if mesh is None or armature is None:
raise Error("Check Mesh and Armature are list!")
CoDEmanX
committed
"""
if len(armature.pose.bones) == len(mesh.vertex_groups):
print("Armature and Mesh Vertex Groups matches Ok!")
else:
raise Error("Armature bones:" + str(len(armature.pose.bones)) +
" Mesh Vertex Groups:" + str(len(mesh.vertex_groups)) +" doesn't match!")
"""
# this will check if object need to be rebuild
if bpy.context.scene.udk_option_rebuildobjects:
# print("INIT... REBUILDING...")
print("REBUILDING ARMATURE...")
# if deform mesh
# rebuild the armature to raw. If there IK constraint it will ignore it
armature = rebuildarmature(armature)
print("REBUILDING MESH...")
mesh = rebuildmesh(mesh) # rebuild the mesh to raw data format.
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 = []
CoDEmanX
committed
for group in mesh.vertex_groups:
CoDEmanX
committed
groups.append(group)
verbose(" " + group.name)
CoDEmanX
committed
return groups
CoDEmanX
committed
# ===========================================================================
# ===========================================================================
def export(filepath):
print(header("Export", 'RIGHT'))
bpy.types.Scene.udk_copy_merge = False # in case fail to export set this to default
t = time.clock()
context = bpy.context
CoDEmanX
committed
print("Blender Version {}.{}.{}".format(bpy.app.version[0], bpy.app.version[1], bpy.app.version[2]))
print("Filepath: {}".format(filepath))
CoDEmanX
committed
verbose("PSK={}, PSA={}".format(context.scene.udk_option_export_psk, context.scene.udk_option_export_psa))
CoDEmanX
committed
# find armature and mesh
# [change this to implement alternative methods; raise Error() if not found]
udk_armature, udk_mesh = find_armature_and_mesh()
CoDEmanX
committed
# 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)")
CoDEmanX
committed
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)")
CoDEmanX
committed
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)")
CoDEmanX
committed
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)")
CoDEmanX
committed
# prep
psk = PSKFile()
psa = PSAFile()
CoDEmanX
committed
# step 1
parse_mesh(udk_mesh, psk)
CoDEmanX
committed
# step 2
udk_bones = parse_armature(udk_armature, psk, psa)
CoDEmanX
committed
# step 3
if context.scene.udk_option_export_psa is True:
actions = collate_actions()
parse_animation(udk_armature, udk_bones, actions, psa)
CoDEmanX
committed
# write files
print(header("Exporting", 'CENTER'))
CoDEmanX
committed
psk_filename = filepath + '.psk'
psa_filename = filepath + '.psa'
CoDEmanX
committed
if context.scene.udk_option_export_psk is True:
print("Skeletal mesh data...")
psk.PrintOut()
CoDEmanX
committed
file = open(psk_filename, "wb")
file.write(psk.dump())
CoDEmanX
committed
file.close()
print("Exported: " + psk_filename)
print()
CoDEmanX
committed
if context.scene.udk_option_export_psa is True:
print("Animation data...")
if not psa.IsEmpty():
psa.PrintOut()
CoDEmanX
committed
file = open(psa_filename, "wb")
file.write(psa.dump())
CoDEmanX
committed
file.close()
print("Exported: " + psa_filename)
else:
print("No Animation (.psa file) to export")
print()
CoDEmanX
committed
# if objects are rebuild do the unlink
if bpy.context.scene.udk_option_rebuildobjects:
print("Unlinking Objects")
print("Armature Object Name:", udk_armature.name) # display object name
bpy.context.scene.objects.unlink(udk_armature) # remove armature from the scene
print("Mesh Object Name:", udk_mesh.name) # display object name
bpy.context.scene.objects.unlink(udk_mesh) # remove mesh from the scene
print("Export completed in {:.2f} seconds".format((time.clock() - t)))
# ===========================================================================
# Operator
# ===========================================================================
class Operator_UDKExport(Operator):
Sebastian Nell
committed
"""Export to UDK"""
bl_idname = "object.udk_export"
bl_label = "Export now"
CoDEmanX
committed
def execute(self, context):
print("\n" * 8)
scene = bpy.context.scene
CoDEmanX
committed
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')
CoDEmanX
committed
filepath = get_dst_path()
CoDEmanX
committed
# cache settings
restore_frame = scene.frame_current
message = "Object(s) exported to: {}".format(filepath)
try:
export(filepath)
except Error as err:
print(err.message)
message = err.message
CoDEmanX
committed
# restore settings
scene.frame_set(restore_frame)
CoDEmanX
committed
def draw(self, context):
self.layout.label(text="Export Finished")
try:
context.window_manager.popup_menu(draw, title=message, icon="INFO")
except:
pass
self.report({'INFO'}, message)
CoDEmanX
committed
# restore settings
scene.frame_set(restore_frame)
CoDEmanX
committed
return {'FINISHED'}
# ===========================================================================
# Operator
# ===========================================================================
class Operator_ToggleConsole(Operator):
Sebastian Nell
committed
"""Show or hide the console"""
bl_idname = "object.toggle_console"
bl_label = "Toggle console"
CoDEmanX
committed
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':