1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
20 "name": "Import Unreal Skeleton Mesh (.psk)/Animation Set (psa)",
21 "author": "Darknet, flufy3d, camg188",
23 "blender": (2, 64, 0),
24 "location": "File > Import > Skeleton Mesh (.psk)/Animation Set (psa)",
25 "description": "Import Skeleleton Mesh/Animation Data",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
28 "Scripts/Import-Export/Unreal_psk_psa",
29 "tracker_url": "https://projects.blender.org/tracker/index.php?"\
30 "func=detail&aid=21366",
31 "category": "Import-Export"}
34 Version': '2.0' ported by Darknet
36 Unreal Tournament PSK file to Blender mesh converter V1.0
37 Author: D.M. Sturgeon (camg188 at the elYsium forum), ported by Darknet
38 Imports a *psk file to a new mesh
44 -Export Text Log From Current Location File (Bool )
50 # XXX Yuck! 'from foo import *' is really bad!
51 from mathutils
import *
53 from bpy
.props
import *
57 from bpy
.props
import *
59 bpy
.types
.Scene
.unrealbonesize
= FloatProperty(
61 description
="Bone Length from head to tail distance",
62 default
=1, min=0.001, max=1000
65 #output log in to txt file
70 from bpy_extras
.io_utils
import unpack_list
, unpack_face_list
89 self
.bindpos
= [0.0] * 3
90 self
.scale
= [0.0] * 3
93 self
.bindmat
= [None] * 3 # is this how you initilize a 2d-array
95 self
.bindmat
[i
] = [0.0] * 3
96 self
.origmat
= [None] * 3 #is this how you initilize a 2d-array
98 self
.origmat
[i
] = [0.0] * 3
100 self
.parent_index
= 0
101 self
.blenderbone
= None
104 print ("bone index: ", self
.bone_index
)
105 print ("name: ", self
.name
)
106 print ("bind position: ", self
.bindpos
)
107 print ("bind translation matrix: ", self
.bindmat
)
108 print ("parent: ", self
.parent
)
109 print ("parent index: ", self
.parent_index
)
110 print ("blenderbone: ", self
.blenderbone
)
112 def getheadpos(pbone
,bones
):
115 #pos = mathutils.Vector((x,y,z)) * pbone.origmat
116 pos
= pbone
.bindmat
.to_translation()
120 while tmp_bone.name != tmp_bone.parent.name:
121 pos = pos * tmp_bone.parent.bindmat
122 tmp_bone = tmp_bone.parent
131 def gettailpos(pbone
,bones
):
137 if bone
.parent
.name
== pbone
.name
:
140 childbonelist
.append(bone
)
144 for bone
in childbonelist
:
145 tmp_head
[0] += bone
.head
[0]
146 tmp_head
[1] += bone
.head
[1]
147 tmp_head
[2] += bone
.head
[2]
148 tmp_head
[0] /= len(childbonelist
)
149 tmp_head
[1] /= len(childbonelist
)
150 tmp_head
[2] /= len(childbonelist
)
154 tmp_len
+= (pbone
.head
[0] - pbone
.parent
.head
[0]) ** 2
155 tmp_len
+= (pbone
.head
[1] - pbone
.parent
.head
[1]) ** 2
156 tmp_len
+= (pbone
.head
[2] - pbone
.parent
.head
[2]) ** 2
157 tmp_len
= tmp_len
** 0.5 * 0.5
158 pos_tail
[0] = pbone
.head
[0] + tmp_len
* pbone
.bindmat
[0][0]
159 pos_tail
[1] = pbone
.head
[1] + tmp_len
* pbone
.bindmat
[1][0]
160 pos_tail
[2] = pbone
.head
[2] + tmp_len
* pbone
.bindmat
[2][0]
164 def pskimport(infile
,importmesh
,importbone
,bDebugLogPSK
,importmultiuvtextures
):
166 DEBUGLOG
= bDebugLogPSK
167 print ("--------------------------------------------------")
168 print ("---------SCRIPT EXECUTING PYTHON IMPORTER---------")
169 print ("--------------------------------------------------")
170 print (" DEBUG Log:",bDebugLogPSK
)
171 print ("Importing file: ", infile
)
173 pskfile
= open(infile
,'rb')
175 logpath
= infile
.replace(".psk", ".txt")
176 print("logpath:",logpath
)
177 logf
= open(logpath
,'w')
179 def printlog(strdata
):
183 objName
= infile
.split('\\')[-1].split('.')[0]
185 me_ob
= bpy
.data
.meshes
.new(objName
)
186 print("objName:",objName
)
187 printlog(("New Mesh = " + me_ob
.name
+ "\n"))
189 indata
= unpack('20s3i', pskfile
.read(32))
190 #not using the general header at this time
191 #==================================================================================================
193 #==================================================================================================
194 #read the PNTS0000 header
195 indata
= unpack('20s3i', pskfile
.read(32))
197 printlog(("Nbr of PNTS0000 records: " + str(recCount
) + "\n"))
201 while counter
< recCount
:
202 counter
= counter
+ 1
203 indata
= unpack('3f', pskfile
.read(12))
204 #print(indata[0], indata[1], indata[2])
205 verts
.extend([(indata
[0], indata
[1], indata
[2])])
206 verts2
.extend([(indata
[0], indata
[1], indata
[2])])
207 #print([(indata[0], indata[1], indata[2])])
208 printlog(str(indata
[0]) + "|" + str(indata
[1]) + "|" + str(indata
[2]) + "\n")
209 #Tmsh.vertices.append(NMesh.Vert(indata[0], indata[1], indata[2]))
211 #==================================================================================================
213 #==================================================================================================
214 #read the VTXW0000 header
215 indata
= unpack('20s3i', pskfile
.read(32))
217 printlog("Nbr of VTXW0000 records: " + str(recCount
)+ "\n")
220 #UVCoords record format = [index to PNTS, U coord, v coord]
221 printlog("[index to PNTS, U coord, v coord]\n");
222 while counter
< recCount
:
223 counter
= counter
+ 1
224 indata
= unpack('hhffhh', pskfile
.read(16))
225 UVCoords
.append([indata
[0], indata
[2], indata
[3]])
226 printlog(str(indata
[0]) + "|" + str(indata
[2]) + "|" + str(indata
[3]) + "\n")
227 #print('mat index %i', indata(4))
228 #print([indata[0], indata[2], indata[3]])
229 #print([indata[1], indata[2], indata[3]])
231 #==================================================================================================
233 #==================================================================================================
234 #read the FACE0000 header
235 indata
= unpack('20s3i', pskfile
.read(32))
237 printlog("Nbr of FACE0000 records: " + str(recCount
) + "\n")
238 #PSK FACE0000 fields: WdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp
239 #associate MatIdx to an image, associate SmthGrp to a material
245 #the psk values are: nWdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp
246 printlog("nWdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp \n")
247 while counter
< recCount
:
248 counter
= counter
+ 1
249 indata
= unpack('hhhbbi', pskfile
.read(12))
250 printlog(str(indata
[0]) + "|" + str(indata
[1]) + "|" + str(indata
[2]) + "|" + str(indata
[3]) + "|" +
251 str(indata
[4]) + "|" + str(indata
[5]) + "\n")
252 #indata[0] = index of UVCoords
253 #UVCoords[indata[0]]=[index to PNTS, U coord, v coord]
254 #UVCoords[indata[0]][0] = index to PNTS
255 PNTSA
= UVCoords
[indata
[2]][0]
256 PNTSB
= UVCoords
[indata
[1]][0]
257 PNTSC
= UVCoords
[indata
[0]][0]
258 #print(PNTSA, PNTSB, PNTSC) #face id vertex
259 #faces.extend([0, 1, 2, 0])
260 faces
.extend([(PNTSA
, PNTSB
, PNTSC
, 0)])
262 u0
= UVCoords
[indata
[2]][1]
263 v0
= UVCoords
[indata
[2]][2]
264 uv
.append([u0
, 1.0 - v0
])
265 u1
= UVCoords
[indata
[1]][1]
266 v1
= UVCoords
[indata
[1]][2]
267 uv
.append([u1
, 1.0 - v1
])
268 u2
= UVCoords
[indata
[0]][1]
269 v2
= UVCoords
[indata
[0]][2]
270 uv
.append([u2
, 1.0 - v2
])
271 faceuv
.append([uv
, indata
[3], indata
[4], indata
[5]])
273 #print("material:", indata[3])
274 #print("UV: ", u0, v0)
275 #update the uv var of the last item in the Tmsh.faces list
276 # which is the face just added above
277 ##Tmsh.faces[-1].uv = [(u0, v0), (u1, v1), (u2, v2)]
278 #print("smooth:",indata[5])
279 #collect a list of the smoothing groups
280 facesmooth
.append(indata
[5])
282 if SGlist
.count(indata
[5]) == 0:
283 SGlist
.append(indata
[5])
284 print("smooth:", indata
[5])
285 #assign a material index to the face
286 #Tmsh.faces[-1].materialIndex = SGlist.index(indata[5])
287 printlog("Using Materials to represent PSK Smoothing Groups...\n")
292 #==================================================================================================
294 #==================================================================================================
296 #read the MATT0000 header
297 indata
= unpack('20s3i', pskfile
.read(32))
299 printlog("Nbr of MATT0000 records: " + str(recCount
) + "\n" )
300 printlog(" - Not importing any material data now. PSKs are texture wrapped! \n")
303 while counter
< recCount
:
304 counter
= counter
+ 1
305 indata
= unpack('64s6i', pskfile
.read(88))
307 print("Material", counter
)
308 print("Mat name %s", indata
[0])
311 #==================================================================================================
313 #==================================================================================================
314 #read the REFSKEL0 header
315 indata
= unpack('20s3i', pskfile
.read(32))
317 printlog( "Nbr of REFSKEL0 records: " + str(recCount
) + "\n")
318 #REFSKEL0 fields - Name|Flgs|NumChld|PrntIdx|Qw|Qx|Qy|Qz|LocX|LocY|LocZ|Lngth|XSize|YSize|ZSize
325 #==================================================================================================
327 #==================================================================================================
329 print ("---PRASE--BONES---")
330 printlog("Name|Flgs|NumChld|PrntIdx|Qx|Qy|Qz|Qw|LocX|LocY|LocZ|Lngth|XSize|YSize|ZSize\n")
331 while counter
< recCount
:
332 indata
= unpack('64s3i11f', pskfile
.read(120))
333 #print( "DATA",str(indata))
337 createbone
= md5_bone()
338 #temp_name = indata[0][:30]
339 temp_name
= indata
[0]
340 temp_name
= bytes
.decode(temp_name
)
341 temp_name
= temp_name
.lstrip(" ")
342 temp_name
= temp_name
.rstrip(" ")
343 temp_name
= temp_name
.strip()
344 temp_name
= temp_name
.strip( bytes
.decode(b
'\x00'))
345 printlog(temp_name
+ "|" + str(indata
[1]) + "|" + str(indata
[2]) + "|" + str(indata
[3]) + "|" +
346 str(indata
[4]) + "|" + str(indata
[5]) + "|" + str(indata
[6]) + "|" + str(indata
[7]) + "|" +
347 str(indata
[8]) + "|" + str(indata
[9]) + "|" + str(indata
[10]) + "|" + str(indata
[11]) + "|" +
348 str(indata
[12]) + "|" + str(indata
[13]) + "|" + str(indata
[14]) + "\n")
349 createbone
.name
= temp_name
350 createbone
.bone_index
= counter
351 createbone
.parent_index
= indata
[3]
352 createbone
.bindpos
[0] = indata
[8]
353 createbone
.bindpos
[1] = indata
[9]
354 createbone
.bindpos
[2] = indata
[10]
355 createbone
.scale
[0] = indata
[12]
356 createbone
.scale
[1] = indata
[13]
357 createbone
.scale
[2] = indata
[14]
359 bni_dict
[createbone
.name
] = createbone
.bone_index
362 if (counter
== 0):#main parent
363 createbone
.bindmat
= mathutils
.Quaternion((indata
[7], -indata
[4], -indata
[5], -indata
[6])).to_matrix()
364 createbone
.origmat
= mathutils
.Quaternion((indata
[7], -indata
[4], -indata
[5], -indata
[6])).to_matrix()
366 createbone
.bindmat
= mathutils
.Quaternion((indata
[7], -indata
[4], -indata
[5], -indata
[6])).to_matrix()
367 createbone
.origmat
= mathutils
.Quaternion((indata
[7], -indata
[4], -indata
[5], -indata
[6])).to_matrix()
369 createbone
.bindmat
= mathutils
.Matrix
.Translation(mathutils
.Vector((indata
[8], indata
[9], indata
[10]))) * \
370 createbone
.bindmat
.to_4x4()
372 md5_bones
.append(createbone
)
373 counter
= counter
+ 1
374 bnstr
= (str(indata
[0]))
377 for pbone
in md5_bones
:
378 pbone
.parent
= md5_bones
[pbone
.parent_index
]
380 for pbone
in md5_bones
:
381 if pbone
.name
!= pbone
.parent
.name
:
382 pbone
.bindmat
= pbone
.parent
.bindmat
* pbone
.bindmat
384 #print(pbone.bindmat)
387 pbone
.bindmat
= pbone
.bindmat
389 for pbone
in md5_bones
:
390 pbone
.head
= getheadpos(pbone
, md5_bones
)
392 for pbone
in md5_bones
:
393 pbone
.tail
= gettailpos(pbone
, md5_bones
)
395 for pbone
in md5_bones
:
396 pbone
.parent
= md5_bones
[pbone
.parent_index
].name
400 temp_name
= armbone
[0][:30]
401 #print ("BONE NAME: ", len(temp_name))
402 temp_name
=str((temp_name
))
403 #temp_name = temp_name[1]
404 #print ("BONE NAME: ", temp_name)
406 print ("-------------------------")
407 print ("----Creating--Armature---")
408 print ("-------------------------")
410 #================================================================================================
411 #Check armature if exist if so create or update or remove all and addnew bone
412 #================================================================================================
413 #bpy.ops.object.mode_set(mode='OBJECT')
414 meshname
="ArmObject"
415 objectname
= "armaturedata"
416 # arm = None # UNUSED
418 obj
= bpy
.data
.objects
.get(meshname
)
422 armdata
= bpy
.data
.armatures
.new(objectname
)
423 ob_new
= bpy
.data
.objects
.new(meshname
, armdata
)
424 #ob_new = bpy.data.objects.new(meshname, 'ARMATURE')
425 #ob_new.data = armdata
426 bpy
.context
.scene
.objects
.link(ob_new
)
427 #bpy.ops.object.mode_set(mode='OBJECT')
428 for i
in bpy
.context
.scene
.objects
:
429 i
.select
= False #deselect all objects
431 #set current armature to edit the bone
432 bpy
.context
.scene
.objects
.active
= ob_new
433 #set mode to able to edit the bone
434 if bpy
.ops
.object.mode_set
.poll():
435 bpy
.ops
.object.mode_set(mode
='EDIT')
437 #newbone = ob_new.data.edit_bones.new('test')
439 print("creating bone(s)")
440 bpy
.ops
.object.mode_set(mode
='OBJECT')
441 for bone
in md5_bones
:
443 bpy
.ops
.object.mode_set(mode
='EDIT')#Go to edit mode for the bones
444 newbone
= ob_new
.data
.edit_bones
.new(bone
.name
)
446 #print("DRI:", dir(newbone))
448 #note bone location is set in the real space or global not local
449 bonesize
= bpy
.types
.Scene
.unrealbonesize
450 if bone
.name
!= bone
.parent
:
451 pos_x
= bone
.bindpos
[0]
452 pos_y
= bone
.bindpos
[1]
453 pos_z
= bone
.bindpos
[2]
454 #print("LINKING:" , bone.parent ,"j")
455 parentbone
= ob_new
.data
.edit_bones
[bone
.parent
]
456 newbone
.parent
= parentbone
457 rotmatrix
= bone
.bindmat
458 newbone
.head
.x
= bone
.head
[0]
459 newbone
.head
.y
= bone
.head
[1]
460 newbone
.head
.z
= bone
.head
[2]
461 newbone
.tail
.x
= bone
.tail
[0]
462 newbone
.tail
.y
= bone
.tail
[1]
463 newbone
.tail
.z
= bone
.tail
[2]
465 vecp
= parentbone
.tail
- parentbone
.head
466 vecc
= newbone
.tail
- newbone
.head
469 if vecp
.dot(vecc
) > -0.8:
470 newbone
.roll
= parentbone
.roll
472 newbone
.roll
= - parentbone
.roll
474 rotmatrix
= bone
.bindmat
475 newbone
.head
.x
= bone
.head
[0]
476 newbone
.head
.y
= bone
.head
[1]
477 newbone
.head
.z
= bone
.head
[2]
478 newbone
.tail
.x
= bone
.tail
[0]
479 newbone
.tail
.y
= bone
.tail
[1]
480 newbone
.tail
.z
= bone
.tail
[2]
481 newbone
.roll
= math
.radians(90.0)
483 vec = newbone.tail - newbone.head
485 newbone.roll = math.radians(90.0)
487 newbone.roll = math.radians(-90.0)
489 bpy
.context
.scene
.update()
491 #==================================================================================================
493 #==================================================================================================
495 for x
in range(len(Bns
)):
496 #change the overall darkness of each material in a range between 0.1 and 0.9
497 tmpVal
= ((float(x
) + 1.0) / (len(Bns
)) * 0.7) + 0.1
498 tmpVal
= int(tmpVal
* 256)
499 tmpCol
= [tmpVal
, tmpVal
, tmpVal
, 0]
500 #Change the color of each material slightly
516 #Add the material to the mesh
517 VtxCol
.append(tmpCol
)
519 #==================================================================================================
521 #==================================================================================================
522 #read the RAWW0000 header
523 indata
= unpack('20s3i', pskfile
.read(32))
525 printlog("Nbr of RAWW0000 records: " + str(recCount
) +"\n")
526 #RAWW0000 fields: Weight|PntIdx|BoneIdx
529 while counter
< recCount
:
530 counter
= counter
+ 1
531 indata
= unpack('fii', pskfile
.read(12))
532 RWghts
.append([indata
[1], indata
[2], indata
[0]])
533 #print("weight:", [indata[1], indata[2], indata[0]])
534 #RWghts fields = PntIdx|BoneIdx|Weight
536 printlog("Vertex point and groups count =" + str(len(RWghts
)) + "\n")
537 printlog("PntIdx|BoneIdx|Weight")
539 printlog(str(vg
[0]) + "|" + str(vg
[1]) + "|" + str(vg
[2]) + "\n")
543 #set the Vertex Colors of the faces
544 #face.v[n] = RWghts[0]
545 #RWghts[1] = index of VtxCol
547 for x in range(len(Tmsh.faces)):
548 for y in range(len(Tmsh.faces[x].v)):
549 #find v in RWghts[n][0]
550 findVal = Tmsh.faces[x].v[y].index
552 while findVal != RWghts[n][0]:
554 TmpCol = VtxCol[RWghts[n][1]]
555 #check if a vertex has more than one influence
556 if n != len(RWghts) - 1:
557 if RWghts[n][0] == RWghts[n + 1][0]:
558 #if there is more than one influence, use the one with the greater influence
559 #for simplicity only 2 influences are checked, 2nd and 3rd influences are usually very small
560 if RWghts[n][2] < RWghts[n + 1][2]:
561 TmpCol = VtxCol[RWghts[n + 1][1]]
562 Tmsh.faces[x].col.append(NMesh.Col(TmpCol[0], TmpCol[1], TmpCol[2], 0))
566 #==================================================================================================
568 #==================================================================================================
569 print("vertex:", len(verts
), "faces:", len(faces
))
570 print("vertex2:", len(verts2
))
571 me_ob
.vertices
.add(len(verts2
))
572 me_ob
.tessfaces
.add(len(faces
))
573 me_ob
.vertices
.foreach_set("co", unpack_list(verts2
))
574 me_ob
.tessfaces
.foreach_set("vertices_raw", unpack_list( faces
))
576 for face
in me_ob
.tessfaces
:
577 face
.use_smooth
= facesmooth
[face
.index
]
580 Material setup coding.
581 First the mesh has to be create first to get the uv texture setup working.
582 -Create material(s) list in the psk pack data from the list.(to do list)
583 -Append the material to the from create the mesh object.
585 -face loop for uv assign and assign material index
587 bpy
.ops
.object.mode_set(mode
='OBJECT')
588 #===================================================================================================
590 #===================================================================================================
591 print ("-------------------------")
592 print ("----Creating--Materials--")
593 print ("-------------------------")
594 materialname
= "pskmat"
597 for matcount
in range(materialcount
):
598 #if texturedata != None:
599 matdata
= bpy
.data
.materials
.new(materialname
+ str(matcount
))
600 #mtex = matdata.texture_slots.new()
601 #mtex.texture = texture[matcount].data
602 #print(type(texture[matcount].data))
605 #for texno in range(len( bpy.data.textures)):
606 #print((bpy.data.textures[texno].name))
607 #print(dir(bpy.data.textures[texno]))
608 #matdata.active_texture = bpy.data.textures[matcount - 1]
609 #matdata.texture_coords = 'UV'
610 #matdata.active_texture = texturedata
611 materials
.append(matdata
)
613 for material
in materials
:
614 #add material to the mesh list of materials
615 me_ob
.materials
.append(material
)
616 #===================================================================================================
618 #===================================================================================================
619 print ("-------------------------")
620 print ("-- Creating UV Texture --")
621 print ("-------------------------")
623 # texturename = "text1" # UNUSED
625 for countm
in range(materialcount
):
626 psktexname
= "psk" + str(countm
)
627 me_ob
.uv_textures
.new(name
=psktexname
)
629 print("INIT UV TEXTURE...")
631 #for mattexcount in materials:
632 #print("MATERAIL ID:", _matcount)
634 for uv
in me_ob
.tessface_uv_textures
: # uv texture
635 print("UV TEXTURE ID:",_textcount
)
637 for face
in me_ob
.tessfaces
:# face, uv
639 if faceuv
[face
.index
][1] == _textcount
: #if face index and texture index matches assign it
640 mfaceuv
= faceuv
[face
.index
] #face index
641 _uv1
= mfaceuv
[0][0] #(0,0)
642 uv
.data
[face
.index
].uv1
= mathutils
.Vector((_uv1
[0], _uv1
[1])) #set them
643 _uv2
= mfaceuv
[0][1] #(0,0)
644 uv
.data
[face
.index
].uv2
= mathutils
.Vector((_uv2
[0], _uv2
[1])) #set them
645 _uv3
= mfaceuv
[0][2] #(0,0)
646 uv
.data
[face
.index
].uv3
= mathutils
.Vector((_uv3
[0], _uv3
[1])) #set them
647 else: #if not match zero them
648 uv
.data
[face
.index
].uv1
= mathutils
.Vector((0, 0)) #zero them
649 uv
.data
[face
.index
].uv2
= mathutils
.Vector((0, 0)) #zero them
650 uv
.data
[face
.index
].uv3
= mathutils
.Vector((0, 0)) #zero them
654 print("END UV TEXTURE...")
656 print("UV TEXTURE LEN:", len(texture
))
657 #for tex in me_ob.uv_textures:
658 #print("mesh tex:", dir(tex))
661 #for face in me_ob.faces:
664 #===================================================================================================
666 #===================================================================================================
667 obmesh
= bpy
.data
.objects
.new(objName
,me_ob
)
668 #===================================================================================================
669 #Mesh Vertex Group bone weight
670 #===================================================================================================
671 print("---- building bone weight mesh ----")
672 #print(dir(ob_new.data.bones))
673 #create bone vertex group #deal with bone id for index number
674 for bone
in ob_new
.data
.bones
:
675 #print("names:", bone.name, ":", dir(bone))
676 #print("names:", bone.name)
677 group
= obmesh
.vertex_groups
.new(bone
.name
)
679 for vgroup
in obmesh
.vertex_groups
:
680 #print(vgroup.name, ":", vgroup.index)
683 if vgp
[1] == bni_dict
[vgroup
.name
]:
686 vgroup
.add([vgp
[0]], vgp
[2], 'ADD')
688 #check if there is a material to set to
689 if len(materials
) > 0:
690 obmesh
.active_material
= materials
[0] #material setup tmp
691 print("---- adding mesh to the scene ----")
693 bpy
.ops
.object.mode_set(mode
='OBJECT')
694 #bpy.ops.object.select_pattern(extend=True, pattern=obmesh.name, case_sensitive=True)
695 #bpy.ops.object.select_pattern(extend=True, pattern=ob_new.name, case_sensitive=True)
697 #bpy.ops.object.select_name(name=str(obmesh.name))
698 #bpy.ops.object.select_name(name=str(ob_new.name))
699 #bpy.context.scene.objects.active = ob_new
701 bpy
.context
.scene
.objects
.link(obmesh
)
702 bpy
.context
.scene
.update()
703 obmesh
.select
= False
704 ob_new
.select
= False
707 bpy
.ops
.object.parent_set(type="ARMATURE")
709 print ("PSK2Blender completed")
710 #End of def pskimport#########################
712 def getInputFilenamepsk(self
, filename
, importmesh
, importbone
, bDebugLogPSK
, importmultiuvtextures
):
713 checktype
= filename
.split('\\')[-1].split('.')[1]
714 print ("------------",filename
)
715 if checktype
.lower() != 'psk':
716 print (" Selected file = ", filename
)
717 raise (IOError, "The selected input file is not a *.psk file")
718 #self.report({'INFO'}, ("Selected file:"+ filename))
720 pskimport(filename
, importmesh
, importbone
, bDebugLogPSK
, importmultiuvtextures
)
722 def getInputFilenamepsa(self
, filename
, context
):
723 checktype
= filename
.split('\\')[-1].split('.')[1]
724 if checktype
.lower() != 'psa':
725 print (" Selected file = ", filename
)
726 raise (IOError, "The selected input file is not a *.psa file")
727 #self.report({'INFO'}, ("Selected file:" + filename))
729 psaimport(filename
,context
)
731 class IMPORT_OT_psk(bpy
.types
.Operator
):
732 '''Load a skeleton mesh psk File'''
733 bl_idname
= "import_scene.psk"
734 bl_label
= "Import PSK"
735 bl_space_type
= "PROPERTIES"
736 bl_region_type
= "WINDOW"
737 bl_options
= {'UNDO'}
739 # List of operator properties, the attributes will be assigned
740 # to the class instance from the operator settings before calling.
741 filepath
= StringProperty(
744 filter_glob
= StringProperty(
748 importmesh
= BoolProperty(
750 description
="Import mesh only. (not yet build.)",
753 importbone
= BoolProperty(
755 description
="Import bones only. Current not working yet",
758 importmultiuvtextures
= BoolProperty(
759 name
="Single UV Texture(s)",
760 description
="Single or Multi uv textures",
763 bDebugLogPSK
= BoolProperty(
764 name
="Debug Log.txt",
765 description
="Log the output of raw format. It will save in "
766 "current file dir. Note this just for testing",
769 unrealbonesize
= FloatProperty(
771 description
="Bone Length from head to tail distance",
777 def execute(self
, context
):
778 bpy
.types
.Scene
.unrealbonesize
= self
.unrealbonesize
779 getInputFilenamepsk(self
, self
.filepath
, self
.importmesh
, self
.importbone
, self
.bDebugLogPSK
,
780 self
.importmultiuvtextures
)
783 def invoke(self
, context
, event
):
784 wm
= context
.window_manager
785 wm
.fileselect_add(self
)
786 return {'RUNNING_MODAL'}
797 def psaimport(filename
,context
):
798 print ("--------------------------------------------------")
799 print ("---------SCRIPT EXECUTING PYTHON IMPORTER---------")
800 print ("--------------------------------------------------")
801 print ("Importing file: ", filename
)
802 psafile
= open(filename
,'rb')
805 logpath
= filename
.replace(".psa", ".txt")
806 print("logpath:", logpath
)
807 logf
= open(logpath
, 'w')
808 def printlog(strdata
):
811 def printlogplus(name
, data
):
813 logf
.write(str(name
) + '\n')
814 if isinstance(data
, bytes
):
815 logf
.write(str(bytes
.decode(data
).strip(bytes
.decode(b
'\x00'))))
817 logf
.write(str(data
))
820 printlog('-----------Log File------------\n')
822 indata
= unpack('20s3i', psafile
.read(32))
823 printlogplus('ChunkID', indata
[0])
824 printlogplus('TypeFlag', indata
[1])
825 printlogplus('DataSize', indata
[2])
826 printlogplus('DataCount', indata
[3])
828 indata
= unpack('20s3i', psafile
.read(32))
829 printlogplus('ChunkID', indata
[0])
830 printlogplus('TypeFlag', indata
[1])
831 printlogplus('DataSize', indata
[2])
832 printlogplus('DataCount', indata
[3])
834 BoneIndex2NamePairMap
= {}
835 BoneNotFoundList
= []
836 printlog("Name|Flgs|NumChld|PrntIdx|Qx|Qy|Qz|Qw|LocX|LocY|LocZ|Length|XSize|YSize|ZSize\n")
840 while counter
< recCount
:
841 indata
= unpack('64s3i11f', psafile
.read(120))
842 #printlogplus('bone', indata[0])
843 bonename
= str(bytes
.decode(indata
[0]).strip(bytes
.decode(b
'\x00')))
844 if bonename
in bpy
.data
.armatures
['armaturedata'].bones
.keys():
845 BoneIndex2NamePairMap
[counter
] = bonename
846 print('find bone', bonename
)
849 print('can not find the bone:', bonename
)
850 BoneNotFoundList
.append(counter
)
854 print('no bone was match so skip import!')
858 indata
= unpack('20s3i', psafile
.read(32))
859 printlogplus('ChunkID', indata
[0])
860 printlogplus('TypeFlag', indata
[1])
861 printlogplus('DataSize', indata
[2])
862 printlogplus('DataCount', indata
[3])
868 while counter
< recCount
:
869 indata
= unpack('64s64s4i3f3i', psafile
.read(64 + 64 + 4 * 4 + 3 * 4 + 3 * 4))
870 printlogplus('Name', indata
[0])
871 printlogplus('Group', indata
[1])
872 printlogplus('totalbones', indata
[2])
873 printlogplus('NumRawFrames', indata
[-1])
874 Name
= str(bytes
.decode(indata
[0]).strip(bytes
.decode(b
'\x00')))
875 Group
= str(bytes
.decode(indata
[1]).strip(bytes
.decode(b
'\x00')))
876 totalbones
= indata
[2]
877 NumRawFrames
= indata
[-1]
879 Raw_Key_Nums
+= indata
[2] * indata
[-1]
880 Action_List
.append((Name
,Group
,totalbones
,NumRawFrames
))
886 indata
= unpack('20s3i', psafile
.read(32))
887 printlogplus('ChunkID', indata
[0])
888 printlogplus('TypeFlag', indata
[1])
889 printlogplus('DataSize', indata
[2])
890 printlogplus('DataCount', indata
[3])
891 if(Raw_Key_Nums
!= indata
[3]):
892 print('error! Raw_Key_Nums Inconsistent')
895 recCount
= Raw_Key_Nums
897 while counter
< recCount
:
898 indata
= unpack('3f4f1f', psafile
.read(3 * 4 + 4 * 4 + 4))
899 pos
= mathutils
.Vector((indata
[0], indata
[1], indata
[2]))
900 quat
= mathutils
.Quaternion((indata
[6], indata
[3], indata
[4], indata
[5]))
902 Raw_Key_List
.append((pos
, quat
, time
))
904 #Scale keys Header,Scale keys Data,Curve keys Header,Curve keys Data
905 curFilePos
= psafile
.tell()
907 endFilePos
= psafile
.tell()
908 if curFilePos
== endFilePos
:
909 print('no Scale keys,Curve keys')
911 #build the animation line
912 if bpy
.ops
.object.mode_set
.poll():
913 bpy
.ops
.object.mode_set(mode
='OBJECT', toggle
=False)
915 NeededBoneMatrix
= {}
916 ARMATURE_OBJ
= 'ArmObject'
917 ARMATURE_DATA
= 'armaturedata'
918 if bpy
.context
.scene
.udk_importarmatureselect
:
919 if len(bpy
.context
.scene
.udkas_list
) > 0:
920 print("CHECKING ARMATURE...")
921 #for bone in bpy.data.objects[ARMATURE_OBJ].pose.bones:
922 #for objd in bpy.data.objects:
923 #print("NAME:", objd.name, " TYPE:", objd.type)
924 #if objd.type == 'ARMARURE':
926 armature_list
= bpy
.context
.scene
.udkas_list
#armature list array
927 armature_idx
= bpy
.context
.scene
.udkimportarmature_list_idx
#armature index selected
928 ARMATURE_OBJ
= bpy
.data
.objects
[armature_list
[armature_idx
]].name
#object armature
929 ARMATURE_DATA
= bpy
.data
.objects
[armature_list
[armature_idx
]].data
.name
#object data
931 for bone
in bpy
.data
.armatures
[ARMATURE_DATA
].bones
:
933 ori_matrix
= bone
.matrix
934 matrix
= bone
.matrix_local
.to_3x3()
935 bone_rest_matrix
= Matrix(matrix
)
936 #bone_rest_matrix = bone.matrix_local.to_3x3()
937 #bone_rest_matrix = bone.matrix_local.to_quaternion().conjugated().to_matrix()
938 bone_rest_matrix_inv
= Matrix(bone_rest_matrix
)
939 bone_rest_matrix_inv
.invert()
940 bone_rest_matrix_inv
.resize_4x4()
941 bone_rest_matrix
.resize_4x4()
942 NeededBoneMatrix
[name
] = (bone_rest_matrix
,bone_rest_matrix_inv
,ori_matrix
)
944 #build tmp pose bone tree
946 for bone
in bpy
.data
.objects
[ARMATURE_OBJ
].pose
.bones
:
947 _psa_bone
= psa_bone()
948 _psa_bone
.name
= bone
.name
949 _psa_bone
.Transform
= bone
.matrix
950 if bone
.parent
!= None:
951 _psa_bone
.parent
= psa_bones
[bone
.parent
.name
]
953 _psa_bone
.parent
= None
954 psa_bones
[bone
.name
] = _psa_bone
958 for raw_action
in Action_List
:
960 Group
= raw_action
[1]
961 Totalbones
= raw_action
[2]
962 NumRawFrames
= raw_action
[3]
963 context
.scene
.update()
964 object = bpy
.data
.objects
['ArmObject']
965 object.animation_data_create()
966 action
= bpy
.data
.actions
.new(name
=Name
)
967 object.animation_data
.action
= action
968 for i
in range(NumRawFrames
):
969 context
.scene
.frame_set(i
+ 1)
970 pose_bones
= object.pose
.bones
971 for j
in range(Totalbones
):
972 if j
not in BoneNotFoundList
:
973 bName
= BoneIndex2NamePairMap
[j
]
974 pbone
= psa_bones
[bName
]
975 pos
= Raw_Key_List
[raw_key_index
][0]
976 quat
= Raw_Key_List
[raw_key_index
][1]
979 if pbone
.parent
!= None:
980 quat
= quat
.conjugated()
981 mat
= Matrix
.Translation(pos
) * quat
.to_matrix().to_4x4()
982 mat
= pose_bones
[bName
].parent
.matrix
* mat
983 #mat = pbone.parent.Transform * mat
985 mat
= pbone
.Transform
* Matrix
.Translation(pos
) * quat
.to_matrix().to_4x4()
987 pose_bones
[bName
].matrix
= mat
988 pbone
.Transform
= mat
993 for bone
in pose_bones
:
994 bone
.matrix
= psa_bones
[bone
.name
].Transform
995 bone
.keyframe_insert("rotation_quaternion")
996 bone
.keyframe_insert("location")
998 def whirlSingleBone(pose_bone
,quat
):
999 bpy
.context
.scene
.update()
1000 #record child's matrix and origin rotate
1001 hymat
= Quaternion((0.707, -0.707, 0, 0)).inverted().to_matrix().to_4x4()
1003 childrens
= pose_bone
.children
1004 for child
in childrens
:
1005 armmat
= bpy
.data
.armatures
['armaturedata'].bones
[child
.name
].matrix
.copy().to_4x4()
1006 cmat
= child
.matrix
.copy() * armmat
.inverted() * hymat
.inverted()
1007 pos
= cmat
.to_translation()
1008 rotmat
= cmat
.to_3x3()
1009 children_infos
[child
] = (armmat
, pos
, rotmat
)
1011 #whirl this bone by quat
1012 pose_bone
.matrix
*= quat
.to_matrix().to_4x4()
1013 pose_bone
.keyframe_insert("location")
1014 pose_bone
.keyframe_insert("rotation_quaternion")
1015 bpy
.context
.scene
.update()
1016 #set back children bon to original position
1017 #reverse whirl child bone by quat.inverse()
1019 for child
in childrens
:
1020 armmat
= children_infos
[child
][0]
1021 pos
= children_infos
[child
][1]
1022 rotmat
= children_infos
[child
][2]
1024 child
.matrix
= Matrix
.Translation(pos
) * rotmat
.to_4x4() * hymat
* armmat
1025 child
.keyframe_insert("location")
1026 child
.keyframe_insert("rotation_quaternion")
1028 for bone
in pose_bones
:
1029 if bone
.parent
!= None:
1030 whirlSingleBone(bone
,Quaternion((0.707, 0, 0, -0.707)))
1032 bone
.rotation_quaternion
*= Quaternion((0.707, -0.707, 0, 0)) * Quaternion((0.707, 0, 0, -0.707))
1033 bone
.keyframe_insert("rotation_quaternion")
1037 context
.scene
.frame_set(0)
1041 class IMPORT_OT_psa(bpy
.types
.Operator
):
1042 '''Load a skeleton anim psa File'''
1043 bl_idname
= "import_scene.psa"
1044 bl_label
= "Import PSA"
1045 bl_space_type
= "PROPERTIES"
1046 bl_region_type
= "WINDOW"
1048 filepath
= StringProperty(
1049 subtype
='FILE_PATH',
1051 filter_glob
= StringProperty(
1056 def execute(self
, context
):
1057 getInputFilenamepsa(self
,self
.filepath
,context
)
1060 def invoke(self
, context
, event
):
1061 wm
= context
.window_manager
1062 wm
.fileselect_add(self
)
1063 return {'RUNNING_MODAL'}
1065 class IMPORT_OT_psa(bpy
.types
.Operator
):
1066 '''Load a skeleton anim psa File'''
1067 bl_idname
= "import_scene.psa"
1068 bl_label
= "Import PSA"
1069 bl_space_type
= "PROPERTIES"
1070 bl_region_type
= "WINDOW"
1072 filepath
= StringProperty(
1073 subtype
='FILE_PATH',
1075 filter_glob
= StringProperty(
1080 def execute(self
, context
):
1081 getInputFilenamepsa(self
,self
.filepath
,context
)
1084 def invoke(self
, context
, event
):
1085 wm
= context
.window_manager
1086 wm
.fileselect_add(self
)
1087 return {'RUNNING_MODAL'}
1089 bpy
.types
.Scene
.udk_importpsk
= StringProperty(
1090 name
= "Import .psk",
1091 description
= "Skeleton mesh file path for psk",
1093 bpy
.types
.Scene
.udk_importpsa
= StringProperty(
1094 name
= "Import .psa",
1095 description
= "Animation Data to Action Set(s) file path for psa",
1097 bpy
.types
.Scene
.udk_importarmatureselect
= BoolProperty(
1098 name
= "Armature Selected",
1099 description
= "Select Armature to Import psa animation data",
1102 class Panel_UDKImport(bpy
.types
.Panel
):
1103 bl_label
= "UDK Import"
1104 bl_idname
= "OBJECT_PT_udk_import"
1105 bl_space_type
= "VIEW_3D"
1106 bl_region_type
= "TOOLS"
1108 filepath
= StringProperty(
1109 subtype
='FILE_PATH',
1113 #def poll(cls, context):
1114 # return context.active_object
1116 def draw(self
, context
):
1117 layout
= self
.layout
1118 layout
.operator(OBJECT_OT_PSKPath
.bl_idname
)
1120 layout
.prop(context
.scene
, "udk_importarmatureselect")
1121 if bpy
.context
.scene
.udk_importarmatureselect
:
1122 layout
.operator(OBJECT_OT_UDKImportArmature
.bl_idname
)
1123 layout
.template_list("UI_UL_list", "udkimportarmature_list", context
.scene
, "udkimportarmature_list",
1124 context
.scene
, "udkimportarmature_list_idx", rows
=5)
1125 layout
.operator(OBJECT_OT_PSAPath
.bl_idname
)
1127 class OBJECT_OT_PSKPath(bpy
.types
.Operator
):
1128 """Select .psk file path to import for skeleton mesh"""
1129 bl_idname
= "object.pskpath"
1130 bl_label
= "Import PSK Path"
1132 filepath
= StringProperty(
1133 subtype
='FILE_PATH',
1135 filter_glob
= StringProperty(
1139 importmesh
= BoolProperty(
1141 description
="Import mesh only. (not yet build.)",
1144 importbone
= BoolProperty(
1146 description
="Import bones only. Current not working yet",
1149 importmultiuvtextures
= BoolProperty(
1150 name
="Single UV Texture(s)",
1151 description
="Single or Multi uv textures",
1154 bDebugLogPSK
= BoolProperty(
1155 name
="Debug Log.txt",
1156 description
="Log the output of raw format. It will save in " \
1157 "current file dir. Note this just for testing",
1160 unrealbonesize
= FloatProperty(
1162 description
="Bone Length from head to tail distance",
1168 def execute(self
, context
):
1169 #context.scene.importpskpath = self.properties.filepath
1170 bpy
.types
.Scene
.unrealbonesize
= self
.unrealbonesize
1171 getInputFilenamepsk(self
, self
.filepath
, self
.importmesh
, self
.importbone
, self
.bDebugLogPSK
,
1172 self
.importmultiuvtextures
)
1175 def invoke(self
, context
, event
):
1176 #bpy.context.window_manager.fileselect_add(self)
1177 wm
= context
.window_manager
1178 wm
.fileselect_add(self
)
1179 return {'RUNNING_MODAL'}
1181 class UDKImportArmaturePG(bpy
.types
.PropertyGroup
):
1182 #boolean = BoolProperty(default=False)
1183 string
= StringProperty()
1184 bexport
= BoolProperty(default
=False, name
="Export", options
={"HIDDEN"},
1185 description
= "This will be ignore when exported")
1186 bselect
= BoolProperty(default
=False, name
="Select", options
={"HIDDEN"},
1187 description
= "This will be ignore when exported")
1188 otype
= StringProperty(name
="Type",description
= "This will be ignore when exported")
1190 bpy
.utils
.register_class(UDKImportArmaturePG
)
1191 bpy
.types
.Scene
.udkimportarmature_list
= CollectionProperty(type=UDKImportArmaturePG
)
1192 bpy
.types
.Scene
.udkimportarmature_list_idx
= IntProperty()
1194 class OBJECT_OT_PSAPath(bpy
.types
.Operator
):
1195 """Select .psa file path to import for animation data"""
1196 bl_idname
= "object.psapath"
1197 bl_label
= "Import PSA Path"
1199 filepath
= StringProperty(name
="PSA File Path", description
="Filepath used for importing the PSA file",
1200 maxlen
=1024, default
="")
1201 filter_glob
= StringProperty(
1206 def execute(self
, context
):
1207 #context.scene.importpsapath = self.properties.filepath
1208 getInputFilenamepsa(self
,self
.filepath
,context
)
1211 def invoke(self
, context
, event
):
1212 bpy
.context
.window_manager
.fileselect_add(self
)
1213 return {'RUNNING_MODAL'}
1215 class OBJECT_OT_UDKImportArmature(bpy
.types
.Operator
):
1216 """This will update the filter of the mesh and armature"""
1217 bl_idname
= "object.udkimportarmature"
1218 bl_label
= "Update Armature"
1220 def execute(self
, context
):
1221 my_objlist
= bpy
.context
.scene
.udkimportarmature_list
1223 for objarm
in bpy
.context
.scene
.objects
:#list and filter only mesh and armature
1224 if objarm
.type == 'ARMATURE':
1225 objectl
.append(objarm
)
1226 for _objd
in objectl
:#check if list has in udk list
1228 for _obj
in my_objlist
:
1229 if _obj
.name
== _objd
.name
and _obj
.otype
== _objd
.type:
1230 _obj
.bselect
= _objd
.select
1233 if bfound_obj
== False:
1234 #print("ADD ARMATURE...")
1235 my_item
= my_objlist
.add()
1236 my_item
.name
= _objd
.name
1237 my_item
.bselect
= _objd
.select
1238 my_item
.otype
= _objd
.type
1240 for _udkobj
in my_objlist
:
1242 for _objd
in bpy
.context
.scene
.objects
: #check if there no existing object from sense to remove it
1243 if _udkobj
.name
== _objd
.name
and _udkobj
.otype
== _objd
.type:
1246 if bfound_objv
== False:
1247 removeobject
.append(_udkobj
)
1248 #print("remove check...")
1249 for _item
in removeobject
: #loop remove object from udk list object
1251 for _obj
in my_objlist
:
1252 if _obj
.name
== _item
.name
and _obj
.otype
== _item
.otype
:
1253 my_objlist
.remove(count
)
1258 class OBJECT_OT_UDKImportA(bpy
.types
.Operator
):
1259 """This will update the filter of the mesh and armature"""
1260 bl_idname
= "object.udkimporta"
1261 bl_label
= "Update Armature"
1263 def execute(self
, context
):
1264 for objd
in bpy
.data
.objects
:
1265 print("NAME:",objd
.name
," TYPE:",objd
.type)
1266 if objd
.type == "ARMATURE":
1268 print((objd
.data
.name
))
1271 def menu_func(self
, context
):
1272 self
.layout
.operator(IMPORT_OT_psk
.bl_idname
, text
="Skeleton Mesh (.psk)")
1273 self
.layout
.operator(IMPORT_OT_psa
.bl_idname
, text
="Skeleton Anim (.psa)")
1276 bpy
.utils
.register_module(__name__
)
1277 bpy
.types
.INFO_MT_file_import
.append(menu_func
)
1280 bpy
.utils
.unregister_module(__name__
)
1281 bpy
.types
.INFO_MT_file_import
.remove(menu_func
)
1283 if __name__
== "__main__":
1286 #note this only read the data and will not be place in the scene
1287 #getInputFilename('C:\\blenderfiles\\BotA.psk')
1288 #getInputFilename('C:\\blenderfiles\\AA.PSK')