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",
26 "warning": "may produce errors, fix in progress",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
28 "Scripts/Import-Export/Unreal_psk_psa",
29 "category": "Import-Export",
33 Version': '2.0' ported by Darknet
35 Unreal Tournament PSK file to Blender mesh converter V1.0
36 Author: D.M. Sturgeon (camg188 at the elYsium forum), ported by Darknet
37 Imports a *psk file to a new mesh
43 -Export Text Log From Current Location File (Bool )
49 # XXX Yuck! 'from foo import *' is really bad!
50 from mathutils
import *
52 from bpy
.props
import *
56 from bpy
.props
import *
58 bpy
.types
.Scene
.unrealbonesize
= FloatProperty(
60 description
="Bone Length from head to tail distance",
61 default
=1, min=0.001, max=1000
64 #output log in to txt file
69 from bpy_extras
.io_utils
import unpack_list
, unpack_face_list
88 self
.bindpos
= [0.0] * 3
89 self
.scale
= [0.0] * 3
92 self
.bindmat
= [None] * 3 # is this how you initialize a 2d-array
94 self
.bindmat
[i
] = [0.0] * 3
95 self
.origmat
= [None] * 3 #is this how you initialize a 2d-array
97 self
.origmat
[i
] = [0.0] * 3
100 self
.blenderbone
= None
103 print ("bone index: ", self
.bone_index
)
104 print ("name: ", self
.name
)
105 print ("bind position: ", self
.bindpos
)
106 print ("bind translation matrix: ", self
.bindmat
)
107 print ("parent: ", self
.parent
)
108 print ("parent index: ", self
.parent_index
)
109 print ("blenderbone: ", self
.blenderbone
)
111 def getheadpos(pbone
,bones
):
114 #pos = mathutils.Vector((x,y,z)) * pbone.origmat
115 pos
= pbone
.bindmat
.to_translation()
119 while tmp_bone.name != tmp_bone.parent.name:
120 pos = pos * tmp_bone.parent.bindmat
121 tmp_bone = tmp_bone.parent
130 def gettailpos(pbone
,bones
):
136 if bone
.parent
.name
== pbone
.name
:
139 childbonelist
.append(bone
)
143 for bone
in childbonelist
:
144 tmp_head
[0] += bone
.head
[0]
145 tmp_head
[1] += bone
.head
[1]
146 tmp_head
[2] += bone
.head
[2]
147 tmp_head
[0] /= len(childbonelist
)
148 tmp_head
[1] /= len(childbonelist
)
149 tmp_head
[2] /= len(childbonelist
)
153 tmp_len
+= (pbone
.head
[0] - pbone
.parent
.head
[0]) ** 2
154 tmp_len
+= (pbone
.head
[1] - pbone
.parent
.head
[1]) ** 2
155 tmp_len
+= (pbone
.head
[2] - pbone
.parent
.head
[2]) ** 2
156 tmp_len
= tmp_len
** 0.5 * 0.5
157 pos_tail
[0] = pbone
.head
[0] + tmp_len
* pbone
.bindmat
[0][0]
158 pos_tail
[1] = pbone
.head
[1] + tmp_len
* pbone
.bindmat
[1][0]
159 pos_tail
[2] = pbone
.head
[2] + tmp_len
* pbone
.bindmat
[2][0]
163 def pskimport(infile
,importmesh
,importbone
,bDebugLogPSK
,importmultiuvtextures
):
165 DEBUGLOG
= bDebugLogPSK
166 print ("--------------------------------------------------")
167 print ("---------SCRIPT EXECUTING PYTHON IMPORTER---------")
168 print ("--------------------------------------------------")
169 print (" DEBUG Log:",bDebugLogPSK
)
170 print ("Importing file: ", infile
)
172 pskfile
= open(infile
,'rb')
174 logpath
= infile
.replace(".psk", ".txt")
175 print("logpath:",logpath
)
176 logf
= open(logpath
,'w')
178 def printlog(strdata
):
182 objName
= infile
.split('\\')[-1].split('.')[0]
184 me_ob
= bpy
.data
.meshes
.new(objName
)
185 print("objName:",objName
)
186 printlog(("New Mesh = " + me_ob
.name
+ "\n"))
188 indata
= unpack('20s3i', pskfile
.read(32))
189 #not using the general header at this time
190 #==================================================================================================
192 #==================================================================================================
193 #read the PNTS0000 header
194 indata
= unpack('20s3i', pskfile
.read(32))
196 printlog(("Nbr of PNTS0000 records: " + str(recCount
) + "\n"))
200 while counter
< recCount
:
201 counter
= counter
+ 1
202 indata
= unpack('3f', pskfile
.read(12))
203 #print(indata[0], indata[1], indata[2])
204 verts
.extend([(indata
[0], indata
[1], indata
[2])])
205 verts2
.extend([(indata
[0], indata
[1], indata
[2])])
206 #print([(indata[0], indata[1], indata[2])])
207 printlog(str(indata
[0]) + "|" + str(indata
[1]) + "|" + str(indata
[2]) + "\n")
208 #Tmsh.vertices.append(NMesh.Vert(indata[0], indata[1], indata[2]))
210 #==================================================================================================
212 #==================================================================================================
213 #read the VTXW0000 header
214 indata
= unpack('20s3i', pskfile
.read(32))
216 printlog("Nbr of VTXW0000 records: " + str(recCount
)+ "\n")
219 #UVCoords record format = [index to PNTS, U coord, v coord]
220 printlog("[index to PNTS, U coord, v coord]\n");
221 while counter
< recCount
:
222 counter
= counter
+ 1
223 indata
= unpack('hhffhh', pskfile
.read(16))
224 UVCoords
.append([indata
[0], indata
[2], indata
[3]])
225 printlog(str(indata
[0]) + "|" + str(indata
[2]) + "|" + str(indata
[3]) + "\n")
226 #print('mat index %i', indata(4))
227 #print([indata[0], indata[2], indata[3]])
228 #print([indata[1], indata[2], indata[3]])
230 #==================================================================================================
232 #==================================================================================================
233 #read the FACE0000 header
234 indata
= unpack('20s3i', pskfile
.read(32))
236 printlog("Nbr of FACE0000 records: " + str(recCount
) + "\n")
237 #PSK FACE0000 fields: WdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp
238 #associate MatIdx to an image, associate SmthGrp to a material
244 #the psk values are: nWdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp
245 printlog("nWdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp \n")
246 while counter
< recCount
:
247 counter
= counter
+ 1
248 indata
= unpack('hhhbbi', pskfile
.read(12))
249 printlog(str(indata
[0]) + "|" + str(indata
[1]) + "|" + str(indata
[2]) + "|" + str(indata
[3]) + "|" +
250 str(indata
[4]) + "|" + str(indata
[5]) + "\n")
251 #indata[0] = index of UVCoords
252 #UVCoords[indata[0]]=[index to PNTS, U coord, v coord]
253 #UVCoords[indata[0]][0] = index to PNTS
254 PNTSA
= UVCoords
[indata
[2]][0]
255 PNTSB
= UVCoords
[indata
[1]][0]
256 PNTSC
= UVCoords
[indata
[0]][0]
257 #print(PNTSA, PNTSB, PNTSC) #face id vertex
258 #faces.extend([0, 1, 2, 0])
259 faces
.extend([(PNTSA
, PNTSB
, PNTSC
, 0)])
261 u0
= UVCoords
[indata
[2]][1]
262 v0
= UVCoords
[indata
[2]][2]
263 uv
.append([u0
, 1.0 - v0
])
264 u1
= UVCoords
[indata
[1]][1]
265 v1
= UVCoords
[indata
[1]][2]
266 uv
.append([u1
, 1.0 - v1
])
267 u2
= UVCoords
[indata
[0]][1]
268 v2
= UVCoords
[indata
[0]][2]
269 uv
.append([u2
, 1.0 - v2
])
270 faceuv
.append([uv
, indata
[3], indata
[4], indata
[5]])
272 #print("material:", indata[3])
273 #print("UV: ", u0, v0)
274 #update the uv var of the last item in the Tmsh.faces list
275 # which is the face just added above
276 ##Tmsh.faces[-1].uv = [(u0, v0), (u1, v1), (u2, v2)]
277 #print("smooth:",indata[5])
278 #collect a list of the smoothing groups
279 facesmooth
.append(indata
[5])
281 if SGlist
.count(indata
[5]) == 0:
282 SGlist
.append(indata
[5])
283 print("smooth:", indata
[5])
284 #assign a material index to the face
285 #Tmsh.faces[-1].materialIndex = SGlist.index(indata[5])
286 printlog("Using Materials to represent PSK Smoothing Groups...\n")
291 #==================================================================================================
293 #==================================================================================================
295 #read the MATT0000 header
296 indata
= unpack('20s3i', pskfile
.read(32))
298 printlog("Nbr of MATT0000 records: " + str(recCount
) + "\n" )
299 printlog(" - Not importing any material data now. PSKs are texture wrapped! \n")
302 while counter
< recCount
:
303 counter
= counter
+ 1
304 indata
= unpack('64s6i', pskfile
.read(88))
306 print("Material", counter
)
307 print("Mat name %s", indata
[0])
310 #==================================================================================================
312 #==================================================================================================
313 #read the REFSKEL0 header
314 indata
= unpack('20s3i', pskfile
.read(32))
316 printlog( "Nbr of REFSKEL0 records: " + str(recCount
) + "\n")
317 #REFSKEL0 fields - Name|Flgs|NumChld|PrntIdx|Qw|Qx|Qy|Qz|LocX|LocY|LocZ|Lngth|XSize|YSize|ZSize
324 #==================================================================================================
326 #==================================================================================================
328 print ("---PRASE--BONES---")
329 printlog("Name|Flgs|NumChld|PrntIdx|Qx|Qy|Qz|Qw|LocX|LocY|LocZ|Lngth|XSize|YSize|ZSize\n")
330 while counter
< recCount
:
331 indata
= unpack('64s3i11f', pskfile
.read(120))
332 #print( "DATA",str(indata))
336 createbone
= md5_bone()
337 #temp_name = indata[0][:30]
338 temp_name
= indata
[0]
339 temp_name
= bytes
.decode(temp_name
)
340 temp_name
= temp_name
.lstrip(" ")
341 temp_name
= temp_name
.rstrip(" ")
342 temp_name
= temp_name
.strip()
343 temp_name
= temp_name
.strip( bytes
.decode(b
'\x00'))
344 printlog(temp_name
+ "|" + str(indata
[1]) + "|" + str(indata
[2]) + "|" + str(indata
[3]) + "|" +
345 str(indata
[4]) + "|" + str(indata
[5]) + "|" + str(indata
[6]) + "|" + str(indata
[7]) + "|" +
346 str(indata
[8]) + "|" + str(indata
[9]) + "|" + str(indata
[10]) + "|" + str(indata
[11]) + "|" +
347 str(indata
[12]) + "|" + str(indata
[13]) + "|" + str(indata
[14]) + "\n")
348 createbone
.name
= temp_name
349 createbone
.bone_index
= counter
350 createbone
.parent_index
= indata
[3]
351 createbone
.bindpos
[0] = indata
[8]
352 createbone
.bindpos
[1] = indata
[9]
353 createbone
.bindpos
[2] = indata
[10]
354 createbone
.scale
[0] = indata
[12]
355 createbone
.scale
[1] = indata
[13]
356 createbone
.scale
[2] = indata
[14]
358 bni_dict
[createbone
.name
] = createbone
.bone_index
361 if (counter
== 0):#main parent
362 createbone
.bindmat
= mathutils
.Quaternion((indata
[7], -indata
[4], -indata
[5], -indata
[6])).to_matrix()
363 createbone
.origmat
= mathutils
.Quaternion((indata
[7], -indata
[4], -indata
[5], -indata
[6])).to_matrix()
365 createbone
.bindmat
= mathutils
.Quaternion((indata
[7], -indata
[4], -indata
[5], -indata
[6])).to_matrix()
366 createbone
.origmat
= mathutils
.Quaternion((indata
[7], -indata
[4], -indata
[5], -indata
[6])).to_matrix()
368 createbone
.bindmat
= mathutils
.Matrix
.Translation(mathutils
.Vector((indata
[8], indata
[9], indata
[10]))) * \
369 createbone
.bindmat
.to_4x4()
371 md5_bones
.append(createbone
)
372 counter
= counter
+ 1
373 bnstr
= (str(indata
[0]))
376 for pbone
in md5_bones
:
377 pbone
.parent
= md5_bones
[pbone
.parent_index
]
379 for pbone
in md5_bones
:
380 if pbone
.name
!= pbone
.parent
.name
:
381 pbone
.bindmat
= pbone
.parent
.bindmat
* pbone
.bindmat
383 #print(pbone.bindmat)
386 pbone
.bindmat
= pbone
.bindmat
388 for pbone
in md5_bones
:
389 pbone
.head
= getheadpos(pbone
, md5_bones
)
391 for pbone
in md5_bones
:
392 pbone
.tail
= gettailpos(pbone
, md5_bones
)
394 for pbone
in md5_bones
:
395 pbone
.parent
= md5_bones
[pbone
.parent_index
].name
399 temp_name
= armbone
[0][:30]
400 #print ("BONE NAME: ", len(temp_name))
401 temp_name
=str((temp_name
))
402 #temp_name = temp_name[1]
403 #print ("BONE NAME: ", temp_name)
405 print ("-------------------------")
406 print ("----Creating--Armature---")
407 print ("-------------------------")
409 #================================================================================================
410 #Check armature if exist if so create or update or remove all and addnew bone
411 #================================================================================================
412 #bpy.ops.object.mode_set(mode='OBJECT')
413 meshname
="ArmObject"
414 objectname
= "armaturedata"
415 # arm = None # UNUSED
417 obj
= bpy
.data
.objects
.get(meshname
)
421 armdata
= bpy
.data
.armatures
.new(objectname
)
422 ob_new
= bpy
.data
.objects
.new(meshname
, armdata
)
423 #ob_new = bpy.data.objects.new(meshname, 'ARMATURE')
424 #ob_new.data = armdata
425 bpy
.context
.collection
.objects
.link(ob_new
)
426 #bpy.ops.object.mode_set(mode='OBJECT')
427 for i
in bpy
.context
.scene
.objects
:
428 i
.select_set(False) #deselect all objects
429 ob_new
.select_set(True)
430 #set current armature to edit the bone
431 bpy
.context
.view_layer
.objects
.active
= ob_new
432 #set mode to able to edit the bone
433 if bpy
.ops
.object.mode_set
.poll():
434 bpy
.ops
.object.mode_set(mode
='EDIT')
436 #newbone = ob_new.data.edit_bones.new('test')
438 print("creating bone(s)")
439 bpy
.ops
.object.mode_set(mode
='OBJECT')
440 for bone
in md5_bones
:
442 bpy
.ops
.object.mode_set(mode
='EDIT')#Go to edit mode for the bones
443 newbone
= ob_new
.data
.edit_bones
.new(bone
.name
)
445 #print("DRI:", dir(newbone))
447 #note bone location is set in the real space or global not local
448 bonesize
= bpy
.types
.Scene
.unrealbonesize
449 if bone
.name
!= bone
.parent
:
450 pos_x
= bone
.bindpos
[0]
451 pos_y
= bone
.bindpos
[1]
452 pos_z
= bone
.bindpos
[2]
453 #print("LINKING:" , bone.parent ,"j")
454 parentbone
= ob_new
.data
.edit_bones
[bone
.parent
]
455 newbone
.parent
= parentbone
456 rotmatrix
= bone
.bindmat
457 newbone
.head
.x
= bone
.head
[0]
458 newbone
.head
.y
= bone
.head
[1]
459 newbone
.head
.z
= bone
.head
[2]
460 newbone
.tail
.x
= bone
.tail
[0]
461 newbone
.tail
.y
= bone
.tail
[1]
462 newbone
.tail
.z
= bone
.tail
[2]
464 vecp
= parentbone
.tail
- parentbone
.head
465 vecc
= newbone
.tail
- newbone
.head
468 if vecp
.dot(vecc
) > -0.8:
469 newbone
.roll
= parentbone
.roll
471 newbone
.roll
= - parentbone
.roll
473 rotmatrix
= bone
.bindmat
474 newbone
.head
.x
= bone
.head
[0]
475 newbone
.head
.y
= bone
.head
[1]
476 newbone
.head
.z
= bone
.head
[2]
477 newbone
.tail
.x
= bone
.tail
[0]
478 newbone
.tail
.y
= bone
.tail
[1]
479 newbone
.tail
.z
= bone
.tail
[2]
480 newbone
.roll
= math
.radians(90.0)
482 vec = newbone.tail - newbone.head
484 newbone.roll = math.radians(90.0)
486 newbone.roll = math.radians(-90.0)
488 bpy
.context
.scene
.update()
490 #==================================================================================================
492 #==================================================================================================
494 for x
in range(len(Bns
)):
495 #change the overall darkness of each material in a range between 0.1 and 0.9
496 tmpVal
= ((float(x
) + 1.0) / (len(Bns
)) * 0.7) + 0.1
497 tmpVal
= int(tmpVal
* 256)
498 tmpCol
= [tmpVal
, tmpVal
, tmpVal
, 0]
499 #Change the color of each material slightly
515 #Add the material to the mesh
516 VtxCol
.append(tmpCol
)
518 #==================================================================================================
520 #==================================================================================================
521 #read the RAWW0000 header
522 indata
= unpack('20s3i', pskfile
.read(32))
524 printlog("Nbr of RAWW0000 records: " + str(recCount
) +"\n")
525 #RAWW0000 fields: Weight|PntIdx|BoneIdx
528 while counter
< recCount
:
529 counter
= counter
+ 1
530 indata
= unpack('fii', pskfile
.read(12))
531 RWghts
.append([indata
[1], indata
[2], indata
[0]])
532 #print("weight:", [indata[1], indata[2], indata[0]])
533 #RWghts fields = PntIdx|BoneIdx|Weight
535 printlog("Vertex point and groups count =" + str(len(RWghts
)) + "\n")
536 printlog("PntIdx|BoneIdx|Weight")
538 printlog(str(vg
[0]) + "|" + str(vg
[1]) + "|" + str(vg
[2]) + "\n")
542 #set the Vertex Colors of the faces
543 #face.v[n] = RWghts[0]
544 #RWghts[1] = index of VtxCol
546 for x in range(len(Tmsh.faces)):
547 for y in range(len(Tmsh.faces[x].v)):
548 #find v in RWghts[n][0]
549 findVal = Tmsh.faces[x].v[y].index
551 while findVal != RWghts[n][0]:
553 TmpCol = VtxCol[RWghts[n][1]]
554 #check if a vertex has more than one influence
555 if n != len(RWghts) - 1:
556 if RWghts[n][0] == RWghts[n + 1][0]:
557 #if there is more than one influence, use the one with the greater influence
558 #for simplicity only 2 influences are checked, 2nd and 3rd influences are usually very small
559 if RWghts[n][2] < RWghts[n + 1][2]:
560 TmpCol = VtxCol[RWghts[n + 1][1]]
561 Tmsh.faces[x].col.append(NMesh.Col(TmpCol[0], TmpCol[1], TmpCol[2], 0))
565 #==================================================================================================
567 #==================================================================================================
568 print("vertex:", len(verts
), "faces:", len(faces
))
569 print("vertex2:", len(verts2
))
570 me_ob
.vertices
.add(len(verts2
))
571 me_ob
.tessfaces
.add(len(faces
))
572 me_ob
.vertices
.foreach_set("co", unpack_list(verts2
))
573 me_ob
.tessfaces
.foreach_set("vertices_raw", unpack_list( faces
))
575 for face
in me_ob
.tessfaces
:
576 face
.use_smooth
= facesmooth
[face
.index
]
579 Material setup coding.
580 First the mesh has to be create first to get the uv texture setup working.
581 -Create material(s) list in the psk pack data from the list.(to do list)
582 -Append the material to the from create the mesh object.
584 -face loop for uv assign and assign material index
586 bpy
.ops
.object.mode_set(mode
='OBJECT')
587 #===================================================================================================
589 #===================================================================================================
590 print ("-------------------------")
591 print ("----Creating--Materials--")
592 print ("-------------------------")
593 materialname
= "pskmat"
596 for matcount
in range(materialcount
):
597 #if texturedata is not None:
598 matdata
= bpy
.data
.materials
.new(materialname
+ str(matcount
))
599 #mtex = matdata.texture_slots.new()
600 #mtex.texture = texture[matcount].data
601 #print(type(texture[matcount].data))
604 #for texno in range(len( bpy.data.textures)):
605 #print((bpy.data.textures[texno].name))
606 #print(dir(bpy.data.textures[texno]))
607 #matdata.active_texture = bpy.data.textures[matcount - 1]
608 #matdata.texture_coords = 'UV'
609 #matdata.active_texture = texturedata
610 materials
.append(matdata
)
612 for material
in materials
:
613 #add material to the mesh list of materials
614 me_ob
.materials
.append(material
)
615 #===================================================================================================
617 #===================================================================================================
618 print ("-------------------------")
619 print ("-- Creating UV Texture --")
620 print ("-------------------------")
622 # texturename = "text1" # UNUSED
624 for countm
in range(materialcount
):
625 psktexname
= "psk" + str(countm
)
626 me_ob
.uv_textures
.new(name
=psktexname
)
628 print("INIT UV TEXTURE...")
630 #for mattexcount in materials:
631 #print("MATERIAL ID:", _matcount)
633 for uv
in me_ob
.tessface_uv_textures
: # uv texture
634 print("UV TEXTURE ID:",_textcount
)
636 for face
in me_ob
.tessfaces
:# face, uv
638 if faceuv
[face
.index
][1] == _textcount
: #if face index and texture index matches assign it
639 mfaceuv
= faceuv
[face
.index
] #face index
640 _uv1
= mfaceuv
[0][0] #(0,0)
641 uv
.data
[face
.index
].uv1
= mathutils
.Vector((_uv1
[0], _uv1
[1])) #set them
642 _uv2
= mfaceuv
[0][1] #(0,0)
643 uv
.data
[face
.index
].uv2
= mathutils
.Vector((_uv2
[0], _uv2
[1])) #set them
644 _uv3
= mfaceuv
[0][2] #(0,0)
645 uv
.data
[face
.index
].uv3
= mathutils
.Vector((_uv3
[0], _uv3
[1])) #set them
646 else: #if not match zero them
647 uv
.data
[face
.index
].uv1
= mathutils
.Vector((0, 0)) #zero them
648 uv
.data
[face
.index
].uv2
= mathutils
.Vector((0, 0)) #zero them
649 uv
.data
[face
.index
].uv3
= mathutils
.Vector((0, 0)) #zero them
653 print("END UV TEXTURE...")
655 print("UV TEXTURE LEN:", len(texture
))
656 #for tex in me_ob.uv_textures:
657 #print("mesh tex:", dir(tex))
660 #for face in me_ob.faces:
663 #===================================================================================================
665 #===================================================================================================
666 obmesh
= bpy
.data
.objects
.new(objName
,me_ob
)
667 #===================================================================================================
668 #Mesh Vertex Group bone weight
669 #===================================================================================================
670 print("---- building bone weight mesh ----")
671 #print(dir(ob_new.data.bones))
672 #create bone vertex group #deal with bone id for index number
673 for bone
in ob_new
.data
.bones
:
674 #print("names:", bone.name, ":", dir(bone))
675 #print("names:", bone.name)
676 group
= obmesh
.vertex_groups
.new(name
=bone
.name
)
678 for vgroup
in obmesh
.vertex_groups
:
679 #print(vgroup.name, ":", vgroup.index)
682 if vgp
[1] == bni_dict
[vgroup
.name
]:
685 vgroup
.add([vgp
[0]], vgp
[2], 'ADD')
687 #check if there is a material to set to
688 if len(materials
) > 0:
689 obmesh
.active_material
= materials
[0] #material setup tmp
690 print("---- adding mesh to the scene ----")
692 bpy
.ops
.object.mode_set(mode
='OBJECT')
693 #bpy.ops.object.select_pattern(extend=True, pattern=obmesh.name, case_sensitive=True)
694 #bpy.ops.object.select_pattern(extend=True, pattern=ob_new.name, case_sensitive=True)
696 #bpy.ops.object.select_name(name=str(obmesh.name))
697 #bpy.ops.object.select_name(name=str(ob_new.name))
698 #bpy.context.scene.objects.active = ob_new
700 bpy
.context
.collection
.objects
.link(obmesh
)
701 bpy
.context
.scene
.update()
702 obmesh
.select_set(False)
703 ob_new
.select_set(False)
704 obmesh
.select_set(True)
705 ob_new
.select_set(True)
706 bpy
.ops
.object.parent_set(type="ARMATURE")
708 print ("PSK2Blender completed")
709 #End of def pskimport#########################
711 def getInputFilenamepsk(self
, filename
, importmesh
, importbone
, bDebugLogPSK
, importmultiuvtextures
):
712 checktype
= filename
.split('\\')[-1].split('.')[1]
713 print ("------------",filename
)
714 if checktype
.lower() != 'psk':
715 print (" Selected file = ", filename
)
716 raise (IOError, "The selected input file is not a *.psk file")
717 #self.report({'INFO'}, ("Selected file:"+ filename))
719 pskimport(filename
, importmesh
, importbone
, bDebugLogPSK
, importmultiuvtextures
)
721 def getInputFilenamepsa(self
, filename
, context
):
722 checktype
= filename
.split('\\')[-1].split('.')[1]
723 if checktype
.lower() != 'psa':
724 print (" Selected file = ", filename
)
725 raise (IOError, "The selected input file is not a *.psa file")
726 #self.report({'INFO'}, ("Selected file:" + filename))
728 psaimport(filename
,context
)
730 class IMPORT_OT_psk(bpy
.types
.Operator
):
731 '''Load a skeleton mesh psk File'''
732 bl_idname
= "import_scene.psk"
733 bl_label
= "Import PSK"
734 bl_space_type
= "PROPERTIES"
735 bl_region_type
= "WINDOW"
736 bl_options
= {'UNDO'}
738 # List of operator properties, the attributes will be assigned
739 # to the class instance from the operator settings before calling.
740 filepath
= StringProperty(
743 filter_glob
= StringProperty(
747 importmesh
= BoolProperty(
749 description
="Import mesh only. (not yet build.)",
752 importbone
= BoolProperty(
754 description
="Import bones only. Current not working yet",
757 importmultiuvtextures
= BoolProperty(
758 name
="Single UV Texture(s)",
759 description
="Single or Multi uv textures",
762 bDebugLogPSK
= BoolProperty(
763 name
="Debug Log.txt",
764 description
="Log the output of raw format. It will save in "
765 "current file dir. Note this just for testing",
768 unrealbonesize
= FloatProperty(
770 description
="Bone Length from head to tail distance",
776 def execute(self
, context
):
777 bpy
.types
.Scene
.unrealbonesize
= self
.unrealbonesize
778 getInputFilenamepsk(self
, self
.filepath
, self
.importmesh
, self
.importbone
, self
.bDebugLogPSK
,
779 self
.importmultiuvtextures
)
782 def invoke(self
, context
, event
):
783 wm
= context
.window_manager
784 wm
.fileselect_add(self
)
785 return {'RUNNING_MODAL'}
796 def psaimport(filename
,context
):
797 print ("--------------------------------------------------")
798 print ("---------SCRIPT EXECUTING PYTHON IMPORTER---------")
799 print ("--------------------------------------------------")
800 print ("Importing file: ", filename
)
801 psafile
= open(filename
,'rb')
804 logpath
= filename
.replace(".psa", ".txt")
805 print("logpath:", logpath
)
806 logf
= open(logpath
, 'w')
807 def printlog(strdata
):
810 def printlogplus(name
, data
):
812 logf
.write(str(name
) + '\n')
813 if isinstance(data
, bytes
):
814 logf
.write(str(bytes
.decode(data
).strip(bytes
.decode(b
'\x00'))))
816 logf
.write(str(data
))
819 printlog('-----------Log File------------\n')
821 indata
= unpack('20s3i', psafile
.read(32))
822 printlogplus('ChunkID', indata
[0])
823 printlogplus('TypeFlag', indata
[1])
824 printlogplus('DataSize', indata
[2])
825 printlogplus('DataCount', indata
[3])
827 indata
= unpack('20s3i', psafile
.read(32))
828 printlogplus('ChunkID', indata
[0])
829 printlogplus('TypeFlag', indata
[1])
830 printlogplus('DataSize', indata
[2])
831 printlogplus('DataCount', indata
[3])
833 BoneIndex2NamePairMap
= {}
834 BoneNotFoundList
= []
835 printlog("Name|Flgs|NumChld|PrntIdx|Qx|Qy|Qz|Qw|LocX|LocY|LocZ|Length|XSize|YSize|ZSize\n")
839 while counter
< recCount
:
840 indata
= unpack('64s3i11f', psafile
.read(120))
841 #printlogplus('bone', indata[0])
842 bonename
= str(bytes
.decode(indata
[0]).strip(bytes
.decode(b
'\x00')))
843 if bonename
in bpy
.data
.armatures
['armaturedata'].bones
.keys():
844 BoneIndex2NamePairMap
[counter
] = bonename
845 print('find bone', bonename
)
848 print('can not find the bone:', bonename
)
849 BoneNotFoundList
.append(counter
)
853 print('no bone was match so skip import!')
857 indata
= unpack('20s3i', psafile
.read(32))
858 printlogplus('ChunkID', indata
[0])
859 printlogplus('TypeFlag', indata
[1])
860 printlogplus('DataSize', indata
[2])
861 printlogplus('DataCount', indata
[3])
867 while counter
< recCount
:
868 indata
= unpack('64s64s4i3f3i', psafile
.read(64 + 64 + 4 * 4 + 3 * 4 + 3 * 4))
869 printlogplus('Name', indata
[0])
870 printlogplus('Group', indata
[1])
871 printlogplus('totalbones', indata
[2])
872 printlogplus('NumRawFrames', indata
[-1])
873 Name
= str(bytes
.decode(indata
[0]).strip(bytes
.decode(b
'\x00')))
874 Group
= str(bytes
.decode(indata
[1]).strip(bytes
.decode(b
'\x00')))
875 totalbones
= indata
[2]
876 NumRawFrames
= indata
[-1]
878 Raw_Key_Nums
+= indata
[2] * indata
[-1]
879 Action_List
.append((Name
,Group
,totalbones
,NumRawFrames
))
885 indata
= unpack('20s3i', psafile
.read(32))
886 printlogplus('ChunkID', indata
[0])
887 printlogplus('TypeFlag', indata
[1])
888 printlogplus('DataSize', indata
[2])
889 printlogplus('DataCount', indata
[3])
890 if(Raw_Key_Nums
!= indata
[3]):
891 print('error! Raw_Key_Nums Inconsistent')
894 recCount
= Raw_Key_Nums
896 while counter
< recCount
:
897 indata
= unpack('3f4f1f', psafile
.read(3 * 4 + 4 * 4 + 4))
898 pos
= mathutils
.Vector((indata
[0], indata
[1], indata
[2]))
899 quat
= mathutils
.Quaternion((indata
[6], indata
[3], indata
[4], indata
[5]))
901 Raw_Key_List
.append((pos
, quat
, time
))
903 #Scale keys Header,Scale keys Data,Curve keys Header,Curve keys Data
904 curFilePos
= psafile
.tell()
906 endFilePos
= psafile
.tell()
907 if curFilePos
== endFilePos
:
908 print('no Scale keys,Curve keys')
910 #build the animation line
911 if bpy
.ops
.object.mode_set
.poll():
912 bpy
.ops
.object.mode_set(mode
='OBJECT', toggle
=False)
914 NeededBoneMatrix
= {}
915 ARMATURE_OBJ
= 'ArmObject'
916 ARMATURE_DATA
= 'armaturedata'
917 if bpy
.context
.scene
.udk_importarmatureselect
:
918 if len(bpy
.context
.scene
.udkas_list
) > 0:
919 print("CHECKING ARMATURE...")
920 #for bone in bpy.data.objects[ARMATURE_OBJ].pose.bones:
921 #for objd in bpy.data.objects:
922 #print("NAME:", objd.name, " TYPE:", objd.type)
923 #if objd.type == 'ARMARURE':
925 armature_list
= bpy
.context
.scene
.udkas_list
#armature list array
926 armature_idx
= bpy
.context
.scene
.udkimportarmature_list_idx
#armature index selected
927 ARMATURE_OBJ
= bpy
.data
.objects
[armature_list
[armature_idx
]].name
#object armature
928 ARMATURE_DATA
= bpy
.data
.objects
[armature_list
[armature_idx
]].data
.name
#object data
930 for bone
in bpy
.data
.armatures
[ARMATURE_DATA
].bones
:
932 ori_matrix
= bone
.matrix
933 matrix
= bone
.matrix_local
.to_3x3()
934 bone_rest_matrix
= Matrix(matrix
)
935 #bone_rest_matrix = bone.matrix_local.to_3x3()
936 #bone_rest_matrix = bone.matrix_local.to_quaternion().conjugated().to_matrix()
937 bone_rest_matrix_inv
= Matrix(bone_rest_matrix
)
938 bone_rest_matrix_inv
.invert()
939 bone_rest_matrix_inv
.resize_4x4()
940 bone_rest_matrix
.resize_4x4()
941 NeededBoneMatrix
[name
] = (bone_rest_matrix
,bone_rest_matrix_inv
,ori_matrix
)
943 #build tmp pose bone tree
945 for bone
in bpy
.data
.objects
[ARMATURE_OBJ
].pose
.bones
:
946 _psa_bone
= psa_bone()
947 _psa_bone
.name
= bone
.name
948 _psa_bone
.Transform
= bone
.matrix
949 if bone
.parent
is not None:
950 _psa_bone
.parent
= psa_bones
[bone
.parent
.name
]
952 _psa_bone
.parent
= None
953 psa_bones
[bone
.name
] = _psa_bone
957 for raw_action
in Action_List
:
959 Group
= raw_action
[1]
960 Totalbones
= raw_action
[2]
961 NumRawFrames
= raw_action
[3]
962 context
.scene
.update()
963 object = bpy
.data
.objects
['ArmObject']
964 object.animation_data_create()
965 action
= bpy
.data
.actions
.new(name
=Name
)
966 object.animation_data
.action
= action
967 for i
in range(NumRawFrames
):
968 context
.scene
.frame_set(i
+ 1)
969 pose_bones
= object.pose
.bones
970 for j
in range(Totalbones
):
971 if j
not in BoneNotFoundList
:
972 bName
= BoneIndex2NamePairMap
[j
]
973 pbone
= psa_bones
[bName
]
974 pos
= Raw_Key_List
[raw_key_index
][0]
975 quat
= Raw_Key_List
[raw_key_index
][1]
978 if pbone
.parent
is not None:
979 quat
= quat
.conjugated()
980 mat
= Matrix
.Translation(pos
) * quat
.to_matrix().to_4x4()
981 mat
= pose_bones
[bName
].parent
.matrix
* mat
982 #mat = pbone.parent.Transform * mat
984 mat
= pbone
.Transform
* Matrix
.Translation(pos
) * quat
.to_matrix().to_4x4()
986 pose_bones
[bName
].matrix
= mat
987 pbone
.Transform
= mat
992 for bone
in pose_bones
:
993 bone
.matrix
= psa_bones
[bone
.name
].Transform
994 bone
.keyframe_insert("rotation_quaternion")
995 bone
.keyframe_insert("location")
997 def whirlSingleBone(pose_bone
,quat
):
998 bpy
.context
.scene
.update()
999 #record child's matrix and origin rotate
1000 hymat
= Quaternion((0.707, -0.707, 0, 0)).inverted().to_matrix().to_4x4()
1002 childrens
= pose_bone
.children
1003 for child
in childrens
:
1004 armmat
= bpy
.data
.armatures
['armaturedata'].bones
[child
.name
].matrix
.copy().to_4x4()
1005 cmat
= child
.matrix
.copy() * armmat
.inverted() * hymat
.inverted()
1006 pos
= cmat
.to_translation()
1007 rotmat
= cmat
.to_3x3()
1008 children_infos
[child
] = (armmat
, pos
, rotmat
)
1010 #whirl this bone by quat
1011 pose_bone
.matrix
*= quat
.to_matrix().to_4x4()
1012 pose_bone
.keyframe_insert("location")
1013 pose_bone
.keyframe_insert("rotation_quaternion")
1014 bpy
.context
.scene
.update()
1015 #set back children bon to original position
1016 #reverse whirl child bone by quat.inverse()
1018 for child
in childrens
:
1019 armmat
= children_infos
[child
][0]
1020 pos
= children_infos
[child
][1]
1021 rotmat
= children_infos
[child
][2]
1023 child
.matrix
= Matrix
.Translation(pos
) * rotmat
.to_4x4() * hymat
* armmat
1024 child
.keyframe_insert("location")
1025 child
.keyframe_insert("rotation_quaternion")
1027 for bone
in pose_bones
:
1028 if bone
.parent
is not None:
1029 whirlSingleBone(bone
,Quaternion((0.707, 0, 0, -0.707)))
1031 bone
.rotation_quaternion
*= Quaternion((0.707, -0.707, 0, 0)) * Quaternion((0.707, 0, 0, -0.707))
1032 bone
.keyframe_insert("rotation_quaternion")
1036 context
.scene
.frame_set(0)
1040 class IMPORT_OT_psa(bpy
.types
.Operator
):
1041 '''Load a skeleton anim psa File'''
1042 bl_idname
= "import_scene.psa"
1043 bl_label
= "Import PSA"
1044 bl_space_type
= "PROPERTIES"
1045 bl_region_type
= "WINDOW"
1047 filepath
: StringProperty(
1048 subtype
='FILE_PATH',
1050 filter_glob
: StringProperty(
1055 def execute(self
, context
):
1056 getInputFilenamepsa(self
,self
.filepath
,context
)
1059 def invoke(self
, context
, event
):
1060 wm
= context
.window_manager
1061 wm
.fileselect_add(self
)
1062 return {'RUNNING_MODAL'}
1064 class IMPORT_OT_psa(bpy
.types
.Operator
):
1065 '''Load a skeleton anim psa File'''
1066 bl_idname
= "import_scene.psa"
1067 bl_label
= "Import PSA"
1068 bl_space_type
= "PROPERTIES"
1069 bl_region_type
= "WINDOW"
1071 filepath
= StringProperty(
1072 subtype
='FILE_PATH',
1074 filter_glob
= StringProperty(
1079 def execute(self
, context
):
1080 getInputFilenamepsa(self
,self
.filepath
,context
)
1083 def invoke(self
, context
, event
):
1084 wm
= context
.window_manager
1085 wm
.fileselect_add(self
)
1086 return {'RUNNING_MODAL'}
1088 bpy
.types
.Scene
.udk_importpsk
= StringProperty(
1089 name
= "Import .psk",
1090 description
= "Skeleton mesh file path for psk",
1092 bpy
.types
.Scene
.udk_importpsa
= StringProperty(
1093 name
= "Import .psa",
1094 description
= "Animation Data to Action Set(s) file path for psa",
1096 bpy
.types
.Scene
.udk_importarmatureselect
= BoolProperty(
1097 name
= "Armature Selected",
1098 description
= "Select Armature to Import psa animation data",
1101 class Panel_UDKImport(bpy
.types
.Panel
):
1102 bl_label
= "UDK Import"
1103 bl_idname
= "OBJECT_PT_udk_import"
1104 bl_space_type
= "VIEW_3D"
1105 bl_region_type
= "TOOLS"
1106 bl_category
= "File I/O"
1107 bl_context
= "objectmode"
1109 filepath
= StringProperty(
1110 subtype
='FILE_PATH',
1114 #def poll(cls, context):
1115 # return context.active_object
1117 def draw(self
, context
):
1118 layout
= self
.layout
1119 layout
.operator(OBJECT_OT_PSKPath
.bl_idname
)
1121 layout
.prop(context
.scene
, "udk_importarmatureselect")
1122 if bpy
.context
.scene
.udk_importarmatureselect
:
1123 layout
.operator(OBJECT_OT_UDKImportArmature
.bl_idname
)
1124 layout
.template_list("UI_UL_list", "udkimportarmature_list", context
.scene
, "udkimportarmature_list",
1125 context
.scene
, "udkimportarmature_list_idx", rows
=5)
1126 layout
.operator(OBJECT_OT_PSAPath
.bl_idname
)
1128 class OBJECT_OT_PSKPath(bpy
.types
.Operator
):
1129 """Select .psk file path to import for skeleton mesh"""
1130 bl_idname
= "object.pskpath"
1131 bl_label
= "Import PSK Path"
1133 filepath
= StringProperty(
1134 subtype
='FILE_PATH',
1136 filter_glob
= StringProperty(
1140 importmesh
= BoolProperty(
1142 description
="Import mesh only. (not yet build.)",
1145 importbone
= BoolProperty(
1147 description
="Import bones only. Current not working yet",
1150 importmultiuvtextures
= BoolProperty(
1151 name
="Single UV Texture(s)",
1152 description
="Single or Multi uv textures",
1155 bDebugLogPSK
= BoolProperty(
1156 name
="Debug Log.txt",
1157 description
="Log the output of raw format. It will save in " \
1158 "current file dir. Note this just for testing",
1161 unrealbonesize
= FloatProperty(
1163 description
="Bone Length from head to tail distance",
1169 def execute(self
, context
):
1170 #context.scene.importpskpath = self.properties.filepath
1171 bpy
.types
.Scene
.unrealbonesize
= self
.unrealbonesize
1172 getInputFilenamepsk(self
, self
.filepath
, self
.importmesh
, self
.importbone
, self
.bDebugLogPSK
,
1173 self
.importmultiuvtextures
)
1176 def invoke(self
, context
, event
):
1177 #bpy.context.window_manager.fileselect_add(self)
1178 wm
= context
.window_manager
1179 wm
.fileselect_add(self
)
1180 return {'RUNNING_MODAL'}
1182 class UDKImportArmaturePG(bpy
.types
.PropertyGroup
):
1183 #boolean = BoolProperty(default=False)
1184 string
= StringProperty()
1185 bexport
= BoolProperty(default
=False, name
="Export", options
={"HIDDEN"},
1186 description
= "This will be ignore when exported")
1187 bselect
= BoolProperty(default
=False, name
="Select", options
={"HIDDEN"},
1188 description
= "This will be ignore when exported")
1189 otype
= StringProperty(name
="Type",description
= "This will be ignore when exported")
1191 bpy
.utils
.register_class(UDKImportArmaturePG
)
1192 bpy
.types
.Scene
.udkimportarmature_list
= CollectionProperty(type=UDKImportArmaturePG
)
1193 bpy
.types
.Scene
.udkimportarmature_list_idx
= IntProperty()
1195 class OBJECT_OT_PSAPath(bpy
.types
.Operator
):
1196 """Select .psa file path to import for animation data"""
1197 bl_idname
= "object.psapath"
1198 bl_label
= "Import PSA Path"
1200 filepath
= StringProperty(name
="PSA File Path", description
="Filepath used for importing the PSA file",
1201 maxlen
=1024, default
="")
1202 filter_glob
= StringProperty(
1207 def execute(self
, context
):
1208 #context.scene.importpsapath = self.properties.filepath
1209 getInputFilenamepsa(self
,self
.filepath
,context
)
1212 def invoke(self
, context
, event
):
1213 bpy
.context
.window_manager
.fileselect_add(self
)
1214 return {'RUNNING_MODAL'}
1216 class OBJECT_OT_UDKImportArmature(bpy
.types
.Operator
):
1217 """This will update the filter of the mesh and armature"""
1218 bl_idname
= "object.udkimportarmature"
1219 bl_label
= "Update Armature"
1221 def execute(self
, context
):
1222 my_objlist
= bpy
.context
.scene
.udkimportarmature_list
1224 for objarm
in bpy
.context
.scene
.objects
:#list and filter only mesh and armature
1225 if objarm
.type == 'ARMATURE':
1226 objectl
.append(objarm
)
1227 for _objd
in objectl
:#check if list has in udk list
1229 for _obj
in my_objlist
:
1230 if _obj
.name
== _objd
.name
and _obj
.otype
== _objd
.type:
1231 _obj
.bselect
= _objd
.select
1234 if bfound_obj
== False:
1235 #print("ADD ARMATURE...")
1236 my_item
= my_objlist
.add()
1237 my_item
.name
= _objd
.name
1238 my_item
.bselect
= _objd
.select
1239 my_item
.otype
= _objd
.type
1241 for _udkobj
in my_objlist
:
1243 for _objd
in bpy
.context
.scene
.objects
: #check if there no existing object from sense to remove it
1244 if _udkobj
.name
== _objd
.name
and _udkobj
.otype
== _objd
.type:
1247 if bfound_objv
== False:
1248 removeobject
.append(_udkobj
)
1249 #print("remove check...")
1250 for _item
in removeobject
: #loop remove object from udk list object
1252 for _obj
in my_objlist
:
1253 if _obj
.name
== _item
.name
and _obj
.otype
== _item
.otype
:
1254 my_objlist
.remove(count
)
1259 class OBJECT_OT_UDKImportA(bpy
.types
.Operator
):
1260 """This will update the filter of the mesh and armature"""
1261 bl_idname
= "object.udkimporta"
1262 bl_label
= "Update Armature"
1264 def execute(self
, context
):
1265 for objd
in bpy
.data
.objects
:
1266 print("NAME:",objd
.name
," TYPE:",objd
.type)
1267 if objd
.type == "ARMATURE":
1269 print((objd
.data
.name
))
1272 def menu_func(self
, context
):
1273 self
.layout
.operator(IMPORT_OT_psk
.bl_idname
, text
="Skeleton Mesh (.psk)")
1274 self
.layout
.operator(IMPORT_OT_psa
.bl_idname
, text
="Skeleton Anim (.psa)")
1277 bpy
.utils
.register_module(__name__
)
1278 bpy
.types
.TOPBAR_MT_file_import
.append(menu_func
)
1281 bpy
.utils
.unregister_module(__name__
)
1282 bpy
.types
.TOPBAR_MT_file_import
.remove(menu_func
)
1284 if __name__
== "__main__":
1287 #note this only read the data and will not be place in the scene
1288 #getInputFilename('C:\\blenderfiles\\BotA.psk')
1289 #getInputFilename('C:\\blenderfiles\\AA.PSK')