1
2
3 """
4 Name: 'NetImmerse/Gamebryo (.nif & .kf & .egm)'
5 Blender: 245
6 Group: 'Import'
7 Tip: 'Import NIF File Format (.nif & .kf & .egm)'
8 """
9
10
11 __author__ = "The NifTools team, http://niftools.sourceforge.net/"
12 __url__ = ("blender", "elysiun", "http://niftools.sourceforge.net/")
13 __bpydoc__ = """\
14 This script imports Netimmerse and Gamebryo .NIF files to Blender.
15 """
16
17 import Blender
18 from Blender.Mathutils import *
19
20 from nif_common import NifImportExport
21 from nif_common import NifConfig
22 from nif_common import NifFormat
23 from nif_common import EgmFormat
24 from nif_common import __version__
25
26 from itertools import izip
27 import logging
28 import math
29 import operator
30
31 import pyffi.utils.quickhull
32 import pyffi.spells.nif
33 import pyffi.spells.nif.fix
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
70 """A simple custom exception class for import errors."""
71 pass
72
74 """A class which bundles the main import function along with all helper
75 functions and data shared between these functions."""
76
77
78 BONE_CORRECTION_MATRICES = (
79 Matrix([ 0.0,-1.0, 0.0],[ 1.0, 0.0, 0.0],[ 0.0, 0.0, 1.0]),
80 Matrix([ 1.0, 0.0, 0.0],[ 0.0, 1.0, 0.0],[ 0.0, 0.0, 1.0]),
81 Matrix([ 1.0, 0.0, 0.0],[ 0.0, 0.0, 1.0],[ 0.0,-1.0, 0.0]),
82 Matrix([ 0.0, 1.0, 0.0],[-1.0, 0.0, 0.0],[ 0.0, 0.0, 1.0]),
83 Matrix([-1.0, 0.0, 0.0],[ 0.0,-1.0, 0.0],[ 0.0, 0.0, 1.0]),
84 Matrix([ 1.0, 0.0, 0.0],[ 0.0, 0.0,-1.0],[ 0.0, 1.0, 0.0]) )
85
86 IDENTITY44 = Matrix( [ 1.0, 0.0, 0.0, 0.0],
87 [ 0.0, 1.0, 0.0, 0.0],
88 [ 0.0, 0.0, 1.0, 0.0],
89 [ 0.0, 0.0, 0.0, 1.0] )
90
91 R2D = 3.14159265358979/180.0
92
94 """Main import function: open file and import all trees."""
95
96
97 self.msg_progress("Initializing", progbar = 0)
98
99
100 for name, value in config.iteritems():
101 setattr(self, name, value)
102
103
104 self.logger = logging.getLogger("niftools.blender.import")
105
106
107 self.filename = self.IMPORT_FILE[:]
108 self.filepath = Blender.sys.dirname(self.filename)
109 self.filebase, self.fileext = Blender.sys.splitext(
110 Blender.sys.basename(self.filename))
111
112
113 self.textures = {}
114
115
116 self.materials = {}
117
118
119 self.names = {}
120
121
122 self.blocks = {}
123
124
125
126
127 self.bonesExtraMatrix = {}
128
129
130
131 self.armatures = {}
132
133
134
135
136
137 self.bonePriorities = {}
138
139
140
141
142 self.havokObjects = {}
143
144
145 self.scene = Blender.Scene.GetCurrent()
146
147
148
149
150 self.selectedObjects = [ob for ob in self.scene.objects.selected]
151
152
153 try:
154
155
156 if self.IMPORT_SKELETON == 2:
157 if len(self.selectedObjects) != 1 or self.selectedObjects[0].getType() != 'Armature':
158 raise NifImportError("You must select exactly one armature in 'Import Geometry Only + Parent To Selected Armature' mode.")
159
160
161 self.logger.info("Importing %s" % self.filename)
162 niffile = open(self.filename, "rb")
163 data = NifFormat.Data()
164 try:
165
166 data.inspect(niffile)
167 self.version = data.version
168 if self.version >= 0:
169
170 self.logger.info("NIF file version: 0x%08X" % self.version)
171 self.msg_progress("Reading file")
172 data.read(niffile)
173 root_blocks = data.roots
174 elif self.version == -1:
175 raise NifImportError("Unsupported NIF version.")
176 else:
177 raise NifImportError("Not a NIF file.")
178 finally:
179
180 niffile.close()
181
182 if self.IMPORT_KEYFRAMEFILE:
183
184 self.logger.info("Importing %s" % self.IMPORT_KEYFRAMEFILE)
185 kffile = open(self.IMPORT_KEYFRAMEFILE, "rb")
186 kfdata = NifFormat.Data()
187 try:
188
189 kfdata.inspect(kffile)
190 self.kfversion = kfdata.version
191 if self.kfversion >= 0:
192
193 self.logger.info("KF file version: 0x%08X" % self.kfversion)
194 self.msg_progress("Reading keyframe file")
195 kfdata.read(kffile)
196 kf_root_blocks = kfdata.roots
197 elif self.kfversion == -1:
198 raise NifImportError("Unsupported KF version.")
199 else:
200 raise NifImportError("Not a KF file.")
201 finally:
202
203 kffile.close()
204 else:
205 kf_root_blocks = []
206
207 if self.IMPORT_EGMFILE:
208
209 self.logger.info("Importing %s" % self.IMPORT_EGMFILE)
210 egmfile = open(self.IMPORT_EGMFILE, "rb")
211 self.egmdata = EgmFormat.Data()
212 try:
213
214 self.egmdata.inspect(egmfile)
215 if self.egmdata.version >= 0:
216
217 self.logger.info("EGM file version: %03i"
218 % self.egmdata.version)
219 self.msg_progress("Reading FaceGen egm file")
220 self.egmdata.read(egmfile)
221
222 self.egmdata.apply_scale(self.IMPORT_SCALE_CORRECTION)
223 elif self.egmdata.version == -1:
224 raise NifImportError("Unsupported EGM version.")
225 else:
226 raise NifImportError("Not an EGM file.")
227 finally:
228
229 egmfile.close()
230 else:
231 self.egmdata = None
232
233 self.msg_progress("Importing data")
234
235 if self.IMPORT_ANIMATION:
236 self.fps = self.getFramesPerSecond(root_blocks + kf_root_blocks)
237 self.scene.getRenderingContext().fps = self.fps
238
239
240 for block in root_blocks:
241 root = block
242
243
244 for b in (b for b in block.tree()
245 if isinstance(b, NifFormat.NiGeometry)
246 and b.isSkin()):
247
248
249 if root in [c for c in b.skinInstance.skeletonRoot.children]:
250
251 b.skinInstance.data.setTransform(
252 root.getTransform()
253 * b.skinInstance.data.getTransform())
254 b.skinInstance.skeletonRoot = root
255
256
257 if self.IMPORT_SKELETON == 1:
258 nonbip_children = (child for child in root.children
259 if child.name[:6] != 'Bip01 ')
260 for child in nonbip_children:
261 root.removeChild(child)
262
263 self.logger.debug("Root block: %s" % root.getGlobalDisplay())
264
265 if self.IMPORT_ANIMATION:
266 for kf_root in kf_root_blocks:
267 self.importKfRoot(kf_root, root)
268
269 self.importRoot(root)
270 except NifImportError, e:
271 self.logger.exception('NifImportError: %s' % e)
272 Blender.Draw.PupMenu('ERROR%t|' + str(e))
273 raise
274 finally:
275
276 self.msg_progress("Finished", progbar = 1)
277
278 self.scene.update(1)
279
280
281 self.root_blocks = root_blocks
282
283
285 """Main import function."""
286
287
288
289 if isinstance(root_block,
290 (NifFormat.NiSequence,
291 NifFormat.NiSequenceStreamHelper)):
292 raise NifImportError("direct .kf import not supported")
293
294
295
296
297
298
299 data = NifFormat.Data()
300 data.roots = [root_block]
301 if self.IMPORT_MERGESKELETONROOTS:
302 pyffi.spells.nif.fix.SpellMergeSkeletonRoots(data=data).recurse()
303 if self.IMPORT_SENDGEOMETRIESTOBINDPOS:
304 pyffi.spells.nif.fix.SpellSendGeometriesToBindPosition(data=data).recurse()
305 if self.IMPORT_SENDDETACHEDGEOMETRIESTONODEPOS:
306 pyffi.spells.nif.fix.SpellSendDetachedGeometriesToNodePosition(data=data).recurse()
307 if self.IMPORT_SENDBONESTOBINDPOS:
308 pyffi.spells.nif.fix.SpellSendBonesToBindPosition(data=data).recurse()
309 if self.IMPORT_APPLYSKINDEFORM:
310 for niBlock in root_block.tree(unique=True):
311 if not isinstance(niBlock, NifFormat.NiGeometry):
312 continue
313 if not niBlock.isSkin():
314 continue
315 self.logger.info('Applying skin deformation on geometry %s'
316 % niBlock.name)
317 vertices, normals = niBlock.getSkinDeformation()
318 for vold, vnew in izip(niBlock.data.vertices, vertices):
319 vold.x = vnew.x
320 vold.y = vnew.y
321 vold.z = vnew.z
322
323
324
325 root_block._parent = None
326
327
328
329 self.set_parents(root_block)
330
331
332 toaster = pyffi.spells.nif.NifToaster()
333 toaster.scale = self.IMPORT_SCALE_CORRECTION
334 pyffi.spells.nif.fix.SpellScale(data=data, toaster=toaster).recurse()
335
336
337 self.markArmaturesBones(root_block)
338
339
340 if self.IMPORT_ANIMATION:
341 self.importTextkey(root_block)
342
343
344 if self.is_armature_root(root_block):
345
346 self.logger.debug("%s is an armature root" % root_block.name)
347 b_obj = self.importBranch(root_block)
348 elif self.is_grouping_node(root_block):
349
350 self.logger.debug("%s is a grouping node" % root_block.name)
351 b_obj = self.importBranch(root_block)
352 elif isinstance(root_block, NifFormat.NiTriBasedGeom):
353
354 b_obj = self.importBranch(root_block)
355 elif isinstance(root_block, NifFormat.NiNode):
356
357
358 if root_block.collisionObject:
359 bhk_body = root_block.collisionObject.body
360 if not isinstance(bhk_body, NifFormat.bhkRigidBody):
361 self.logger.warning("Unsupported collision structure under node %s" % root_block.name)
362 self.importBhkShape(bhk_body)
363
364 for child in root_block.children:
365 b_obj = self.importBranch(child)
366 elif isinstance(root_block, NifFormat.NiCamera):
367 self.logger.warning('Skipped NiCamera root')
368 elif isinstance(root_block, NifFormat.NiPhysXProp):
369 self.logger.warning('Skipped NiPhysXProp root')
370 else:
371 self.logger.warning(
372 "Skipped unsupported root block type '%s' (corrupted nif?)."
373 % root_block.__class__)
374
375
376 if self.bonesExtraMatrix:
377 self.storeBonesExtraMatrix()
378
379
380 if self.names:
381 self.storeNames()
382
383
384
385 for hkbody in self.havokObjects:
386 self.importHavokConstraints(hkbody)
387
388
389 if self.IMPORT_SKELETON == 1:
390
391
392 for b_child_obj in self.selectedObjects:
393 if b_child_obj.getType() == "Mesh":
394 for oldgroupname in b_child_obj.data.getVertGroupNames():
395 newgroupname = self.get_bone_name_for_blender(oldgroupname)
396 if oldgroupname != newgroupname:
397 self.logger.info(
398 "%s: renaming vertex group %s to %s"
399 % (b_child_obj, oldgroupname, newgroupname))
400 b_child_obj.data.renameVertGroup(
401 oldgroupname, newgroupname)
402
403 b_obj.makeParentDeform(self.selectedObjects)
404
406 """Read the content of the current NIF tree branch to Blender
407 recursively."""
408 self.msg_progress("Importing data")
409 if niBlock:
410 if isinstance(niBlock, NifFormat.NiTriBasedGeom) \
411 and self.IMPORT_SKELETON == 0:
412
413
414
415 self.logger.debug("Building mesh in importBranch")
416 return self.importMesh(niBlock)
417 elif isinstance(niBlock, NifFormat.NiNode):
418 children = niBlock.children
419 bbox = self.find_extra(niBlock, NifFormat.BSBound)
420 if children or niBlock.collisionObject or bbox or self.IMPORT_EXTRANODES:
421
422
423 if self.is_armature_root(niBlock):
424
425
426 if self.IMPORT_SKELETON != 2:
427 b_obj = self.importArmature(niBlock)
428 else:
429 b_obj = self.selectedObjects[0]
430 self.logger.info("Merging nif tree '%s' with armature '%s'"
431 % (niBlock.name, b_obj.name))
432 if niBlock.name != b_obj.name:
433 self.logger.warning(
434 "Taking nif block '%s' as armature '%s' but names do not match"
435 % (niBlock.name, b_obj.name))
436
437 self.importArmatureBranch(b_obj, niBlock, niBlock)
438 else:
439
440 geom_group = self.is_grouping_node(niBlock)
441
442
443 if self.IMPORT_ANIMATION:
444 for child in geom_group:
445 if self.find_controller(
446 child, NifFormat.NiGeomMorpherController):
447 geom_group.remove(child)
448
449 if (not geom_group
450 or not self.IMPORT_COMBINESHAPES
451 or len(geom_group) > 16):
452
453
454
455 b_obj = self.importEmpty(niBlock)
456 geom_group = []
457 else:
458
459 self.logger.info(
460 "joining geometries %s to single object '%s'"
461 %([child.name for child in geom_group],
462 niBlock.name))
463 b_obj = None
464 for child in geom_group:
465 b_obj = self.importMesh(child,
466 group_mesh = b_obj,
467 applytransform = True)
468 b_obj.name = self.importName(niBlock, 22)
469
470 if isinstance(niBlock, NifFormat.RootCollisionNode):
471 b_obj.setDrawType(
472 Blender.Object.DrawTypes['BOUNDBOX'])
473 b_obj.setDrawMode(32)
474 b_obj.rbShapeBoundType = \
475 Blender.Object.RBShapes['POLYHEDERON']
476
477 b_mesh = b_obj.getData(mesh=True)
478 numverts = len(b_mesh.verts)
479
480 numdel = b_mesh.remDoubles(0.005)
481 if numdel:
482 self.logger.info('Removed %i duplicate vertices (out of %i) from collision mesh' % (numdel, numverts))
483
484 b_children_list = []
485 children = [ child for child in niBlock.children
486 if child not in geom_group ]
487 for child in children:
488 b_child_obj = self.importBranch(child)
489 if b_child_obj:
490 b_children_list.append(b_child_obj)
491 b_obj.makeParent(b_children_list)
492
493
494 if self.IMPORT_SKELETON != 1:
495
496 if niBlock.collisionObject:
497 bhk_body = niBlock.collisionObject.body
498 if not isinstance(bhk_body, NifFormat.bhkRigidBody):
499 self.logger.warning("Unsupported collision structure under node %s" % niBlock.name)
500 collision_objs = self.importBhkShape(bhk_body)
501
502 b_obj.makeParent(collision_objs)
503
504
505 if bbox:
506 b_obj.makeParent([self.importBSBound(bbox)])
507
508
509 if isinstance(niBlock, NifFormat.NiBillboardNode):
510
511 for obj in self.scene.objects:
512 if obj.getType() == "Camera":
513 break
514 else:
515 raise NifImportError("""Scene needs camera for billboard node (add a camera and try again)""")
516
517
518 b_obj.constraints.append(
519 Blender.Constraint.Type.TRACKTO)
520 self.logger.warning("""Constraint for billboard node on %s added but target not set due to transform bug in Blender. Set target to Camera manually.""" % b_obj)
521 constr = b_obj.constraints[-1]
522 constr[Blender.Constraint.Settings.TRACK] = Blender.Constraint.Settings.TRACKZ
523 constr[Blender.Constraint.Settings.UP] = Blender.Constraint.Settings.UPY
524
525
526
527
528
529
530 b_obj.setMatrix(self.importMatrix(niBlock))
531
532
533 if self.IMPORT_ANIMATION:
534 self.set_animation(niBlock, b_obj)
535
536 self.importTextkey(niBlock)
537
538 return b_obj
539
540 return None
541
544 """Reads the content of the current NIF tree branch to Blender
545 recursively, as meshes parented to a given armature or parented
546 to the closest bone in the armature. Note that
547 niArmature must have been imported previously as an armature, along
548 with all its bones. This function only imports meshes and armature
549 ninodes.
550
551 If this imports a mesh, then it returns the a nif block and a mesh
552 object. The nif block is the one relative to which the mesh transform
553 is calculated (so the mesh should be parented to the blender object
554 corresponding to this block by the caller). It corresponds either to
555 a Blender bone or to a Blender armature."""
556
557 if not niBlock:
558 return None, None
559
560
561
562 if self.is_bone(niBlock):
563 branch_parent = niBlock
564
565 else:
566 branch_parent = self.get_closest_bone(niBlock, skelroot = niArmature)
567
568
569 if not branch_parent:
570 branch_parent = niArmature
571
572 if isinstance(niBlock, NifFormat.NiTriBasedGeom) \
573 and self.IMPORT_SKELETON != 1:
574
575 self.logger.debug("Building mesh %s in importArmatureBranch" %
576 niBlock.name)
577
578 return branch_parent, self.importMesh(niBlock,
579 group_mesh = group_mesh,
580 applytransform = True,
581 relative_to = branch_parent)
582
583 elif self.is_armature_root(niBlock) and niBlock != niArmature:
584
585 fb_arm = self.importArmature(niBlock)
586
587 self.importArmatureBranch(fb_arm, niBlock, niBlock)
588 return branch_parent, fb_arm
589
590
591 elif isinstance(niBlock, NifFormat.NiNode):
592
593 if niBlock.children:
594
595 node_name = niBlock.name
596 geom_group = self.is_grouping_node(niBlock)
597 geom_other = [ child for child in niBlock.children
598 if not child in geom_group ]
599 b_objects = []
600
601 if (geom_group
602 and self.IMPORT_COMBINESHAPES
603 and self.IMPORT_SKELETON != 1):
604 self.logger.info(
605 "joining geometries %s to single object '%s'"
606 %([child.name for child in geom_group], node_name))
607 b_mesh = None
608 for child in geom_group:
609 b_mesh_branch_parent, b_mesh = self.importArmatureBranch(
610 b_armature, niArmature, child, group_mesh = b_mesh)
611 assert(b_mesh_branch_parent == branch_parent)
612 if b_mesh:
613 b_mesh.name = self.importName(niBlock)
614 b_objects.append((niBlock, branch_parent, b_mesh))
615
616 for child in geom_other:
617 b_obj_branch_parent, b_obj = self.importArmatureBranch(
618 b_armature, niArmature, child, group_mesh = None)
619 if b_obj:
620 b_objects.append((child, b_obj_branch_parent, b_obj))
621
622 for child, b_obj_branch_parent, b_obj in b_objects:
623
624
625 if b_obj_branch_parent is not niArmature:
626
627
628
629 b_par_bone_name = self.names[branch_parent]
630 b_par_bone = b_armature.data.bones[b_par_bone_name]
631
632
633
634
635
636 b_obj_matrix = Blender.Mathutils.Matrix(
637 b_obj.getMatrix())
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662 extra = Blender.Mathutils.Matrix(
663 self.bonesExtraMatrix[branch_parent])
664 extra.invert()
665 b_obj_matrix = b_obj_matrix * extra
666
667
668
669 b_obj_matrix[3][1] -= b_par_bone.length
670
671 b_obj.setMatrix(b_obj_matrix)
672
673
674 b_armature.makeParentBone(
675 [b_obj], self.names[b_obj_branch_parent])
676 else:
677
678
679
680
681
682 b_armature.makeParentDeform([b_obj])
683
684
685 if self.IMPORT_SKELETON != 1:
686
687 if isinstance(niBlock.collisionObject,
688 NifFormat.bhkNiCollisionObject):
689
690
691 bhk_body = niBlock.collisionObject.body
692 if not isinstance(bhk_body, NifFormat.bhkRigidBody):
693 self.logger.warning(
694 "Unsupported collision structure under node %s"
695 % niBlock.name)
696 collision_objs = self.importBhkShape(bhk_body)
697
698
699 if niBlock != branch_parent:
700 self.logger.warning("Collision object has non-bone parent, this is not supported and transform errors may result")
701 b_par_bone_name = self.names[branch_parent]
702 b_par_bone = b_armature.data.bones[b_par_bone_name]
703 for b_obj in collision_objs:
704
705
706
707 b_obj_matrix = b_obj.getMatrix()
708
709
710 extra = Blender.Mathutils.Matrix(
711 self.bonesExtraMatrix[branch_parent])
712 extra.invert()
713 b_obj_matrix = b_obj_matrix * extra
714 b_obj_matrix[3][1] -= b_par_bone.length
715
716 b_obj.setMatrix(b_obj_matrix)
717
718 b_armature.makeParentBone(
719 [b_obj], self.names[niBlock])
720
721
722
723
724
725 return None, None
726
727 - def importName(self, niBlock, max_length=22, postfix=""):
728 """Get unique name for an object, preserving existing names.
729 The maximum name length defaults to 22, since this is the
730 maximum for Blender objects. Bone names can reach length 32.
731
732 @param niBlock: A named nif block.
733 @type niBlock: C{NiObjectNET}
734 @param max_length: The maximum length of the name.
735 @type max_length: C{int}
736 @param postfix: Extra string to append to the name.
737 @type postfix: C{str}
738 """
739 if niBlock in self.names:
740 if not postfix:
741 return self.names[niBlock]
742 else:
743
744
745 if self.names[niBlock].endswith(postfix):
746 return self.names[niBlock]
747
748 self.logger.debug("Importing name for %s block from %s%s" % (niBlock.__class__.__name__, niBlock.name, postfix))
749
750
751 uniqueInt = 0
752
753 niBlock.name = niBlock.name.strip("\x00")
754 niName = niBlock.name
755
756 if not niName:
757 if isinstance(niBlock, NifFormat.RootCollisionNode):
758 niName = "collision"
759 else:
760 niName = "noname"
761 for uniqueInt in xrange(-1, 100):
762
763 if uniqueInt == -1:
764 if max_length - len(postfix) - 1 <= 0:
765 raise ValueError("Name postfix '%s' too long." % postfix)
766 shortName = niName[:max_length-len(postfix)-1] + postfix
767 else:
768 if max_length - len(postfix) - 4 <= 0:
769 raise ValueError("Name postfix '%s' too long." % postfix)
770 shortName = ('%s.%02d%s'
771 % (niName[:max_length-len(postfix)-4],
772 uniqueInt,
773 postfix))
774
775 shortName = self.get_bone_name_for_blender(shortName)
776
777 try:
778 Blender.Object.Get(shortName)
779 except ValueError:
780
781 pass
782 else:
783
784 continue
785 try:
786 Blender.Material.Get(shortName)
787 except NameError:
788
789 break
790 else:
791 raise RuntimeError("Ran out of names.")
792
793
794 self.names[niBlock] = shortName
795
796 self.blocks[shortName] = niBlock
797 self.logger.debug("Selected unique name %s" % shortName)
798 return shortName
799
801 """Retrieves a niBlock's transform matrix as a Mathutil.Matrix."""
802 return Matrix(*niBlock.getTransform(relative_to).asList())
803
805 """Decompose Blender transform matrix as a scale, rotation matrix, and
806 translation vector."""
807
808 b_scale_rot = m.rotationPart()
809 b_scale_rot_T = Matrix(b_scale_rot)
810 b_scale_rot_T.transpose()
811 b_scale_rot_2 = b_scale_rot * b_scale_rot_T
812 b_scale = Vector(b_scale_rot_2[0][0] ** 0.5,\
813 b_scale_rot_2[1][1] ** 0.5,\
814 b_scale_rot_2[2][2] ** 0.5)
815
816 if (b_scale_rot.determinant() < 0): b_scale.negate()
817
818 if (abs(b_scale[0]-b_scale[1]) >= self.EPSILON
819 or abs(b_scale[1]-b_scale[2]) >= self.EPSILON):
820 self.logger.warn(
821 "Corrupt rotation matrix in nif: geometry errors may result.")
822 b_scale = b_scale[0]
823
824 b_rot = b_scale_rot * (1.0/b_scale)
825
826 b_trans = m.translationPart()
827
828 return b_scale, b_rot, b_trans
829
831 """Creates and returns a grouping empty."""
832 shortName = self.importName(niBlock, 22)
833 b_empty = self.scene.objects.new("Empty", shortName)
834 b_empty.properties['longName'] = niBlock.name
835 return b_empty
836
838 """Scans an armature hierarchy, and returns a whole armature.
839 This is done outside the normal node tree scan to allow for positioning
840 of the bones before skins are attached."""
841 armature_name = self.importName(niArmature,22)
842
843 b_armatureData = Blender.Armature.Armature()
844 b_armatureData.name = armature_name
845 b_armatureData.drawAxes = True
846 b_armatureData.envelopes = False
847 b_armatureData.vertexGroups = True
848 b_armatureData.drawType = Blender.Armature.STICK
849 b_armature = self.scene.objects.new(b_armatureData, armature_name)
850
851
852 b_armatureData.makeEditable()
853 niChildBones = [child for child in niArmature.children
854 if self.is_bone(child)]
855 for niBone in niChildBones:
856 self.importBone(
857 niBone, b_armature, b_armatureData, niArmature)
858 b_armatureData.update()
859
860
861
862 if self.IMPORT_ANIMATION:
863
864 action = Blender.Armature.NLA.NewAction()
865 action.setActive(b_armature)
866
867
868 self.msg_progress('Importing Animations')
869 for bone_name, b_posebone in b_armature.getPose().bones.items():
870
871 self.msg_progress('Animation: %s' % bone_name)
872 self.logger.debug('Importing animation for bone %s' % bone_name)
873 niBone = self.blocks[bone_name]
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888 bone_bm = self.importMatrix(niBone)
889 niBone_bind_scale, niBone_bind_rot, niBone_bind_trans = self.decompose_srt(bone_bm)
890 niBone_bind_rot_inv = Matrix(niBone_bind_rot)
891 niBone_bind_rot_inv.invert()
892 niBone_bind_quat_inv = niBone_bind_rot_inv.toQuat()
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914 extra_matrix_scale, extra_matrix_rot, extra_matrix_trans = self.decompose_srt(self.bonesExtraMatrix[niBone])
915 extra_matrix_quat = extra_matrix_rot.toQuat()
916 extra_matrix_rot_inv = Matrix(extra_matrix_rot)
917 extra_matrix_rot_inv.invert()
918 extra_matrix_quat_inv = extra_matrix_rot_inv.toQuat()
919
920
921
922
923
924
925 kfc = self.find_controller(niBone,
926 NifFormat.NiKeyframeController)
927
928 kfd = kfc.data if kfc else None
929
930 kfi = kfc.interpolator if kfc else None
931
932
933
934 if isinstance(kfi, NifFormat.NiTransformInterpolator):
935 kfd = kfi.data
936
937 kfi = None
938
939
940 if isinstance(kfi, NifFormat.NiBSplineInterpolator):
941 times = list(kfi.getTimes())
942 translations = list(kfi.getTranslations())
943 scales = list(kfi.getScales())
944 rotations = list(kfi.getRotations())
945
946
947
948
949 if translations:
950 scale_keys_dict = {}
951 rot_keys_dict = {}
952
953
954
955 scales = None
956
957
958 if rotations:
959 self.logger.debug('Rotation keys...(bspline quaternions)')
960 for time, quat in izip(times, rotations):
961 frame = 1 + int(time * self.fps + 0.5)
962 quat = Blender.Mathutils.Quaternion(
963 [quat[0], quat[1], quat[2], quat[3]])
964
965
966
967 quatVal = CrossQuats(niBone_bind_quat_inv, quat)
968 rot = CrossQuats(CrossQuats(extra_matrix_quat_inv, quatVal), extra_matrix_quat)
969 b_posebone.quat = rot
970 b_posebone.insertKey(b_armature, frame,
971 [Blender.Object.Pose.ROT])
972
973 if translations:
974 rot_keys_dict[frame] = Blender.Mathutils.Quaternion(rot)
975
976
977 if translations:
978 self.logger.debug('Translation keys...(bspline)')
979 for time, translation in izip(times, translations):
980
981 frame = 1 + int(time * self.fps + 0.5)
982 trans = Blender.Mathutils.Vector(*translation)
983 locVal = (trans - niBone_bind_trans) * niBone_bind_rot_inv * (1.0/niBone_bind_scale)
984
985
986 if rot_keys_dict:
987 try:
988 rot = rot_keys_dict[frame].toMatrix()
989 except KeyError:
990
991 ipo = action.getChannelIpo(bone_name)
992 quat = Blender.Mathutils.Quaternion()
993 quat.x = ipo.getCurve('QuatX').evaluate(frame)
994 quat.y = ipo.getCurve('QuatY').evaluate(frame)
995 quat.z = ipo.getCurve('QuatZ').evaluate(frame)
996 quat.w = ipo.getCurve('QuatW').evaluate(frame)
997 rot = quat.toMatrix()
998 else:
999 rot = Blender.Mathutils.Matrix([1.0,0.0,0.0],
1000 [0.0,1.0,0.0],
1001 [0.0,0.0,1.0])
1002
1003 if scale_keys_dict:
1004 try:
1005 sizeVal = scale_keys_dict[frame]
1006 except KeyError:
1007 ipo = action.getChannelIpo(bone_name)
1008 if ipo.getCurve('SizeX'):
1009 sizeVal = ipo.getCurve('SizeX').evaluate(frame)
1010 else:
1011 sizeVal = 1.0
1012 else:
1013 sizeVal = 1.0
1014 size = Blender.Mathutils.Matrix([sizeVal, 0.0, 0.0],
1015 [0.0, sizeVal, 0.0],
1016 [0.0, 0.0, sizeVal])
1017
1018 loc = (extra_matrix_trans * size * rot + locVal - extra_matrix_trans) * extra_matrix_rot_inv * (1.0/extra_matrix_scale)
1019 b_posebone.loc = loc
1020 b_posebone.insertKey(b_armature, frame, [Blender.Object.Pose.LOC])
1021
1022
1023 if translations:
1024 del scale_keys_dict
1025 del rot_keys_dict
1026
1027
1028 elif isinstance(kfd, NifFormat.NiKeyframeData):
1029
1030 translations = kfd.translations
1031 scales = kfd.scales
1032
1033
1034
1035 if translations:
1036 scale_keys_dict = {}
1037 rot_keys_dict = {}
1038
1039
1040
1041 if scales.keys:
1042 self.logger.debug('Scale keys...')
1043 for scaleKey in scales.keys:
1044
1045 frame = 1 + int(scaleKey.time * self.fps + 0.5)
1046 sizeVal = scaleKey.value
1047 size = sizeVal / niBone_bind_scale
1048 b_posebone.size = Blender.Mathutils.Vector(size, size, size)
1049 b_posebone.insertKey(b_armature, frame, [Blender.Object.Pose.SIZE])
1050
1051 if translations:
1052 scale_keys_dict[frame] = size
1053
1054
1055 rotationType = kfd.rotationType
1056
1057
1058 if rotationType == 4:
1059
1060 if kfd.xyzRotations[0].keys:
1061 self.logger.debug('Rotation keys...(euler)')
1062 for xkey, ykey, zkey in izip(kfd.xyzRotations[0].keys,
1063 kfd.xyzRotations[1].keys,
1064 kfd.xyzRotations[2].keys):
1065
1066
1067
1068 if (abs(xkey.time - ykey.time) > self.EPSILON
1069 or abs(xkey.time - zkey.time) > self.EPSILON):
1070 self.logger.warn(
1071 "xyz key times do not correspond, "
1072 "animation may not be correctly imported")
1073 frame = 1 + int(xkey.time * self.fps + 0.5)
1074 euler = Blender.Mathutils.Euler(
1075 [xkey.value*180.0/math.pi,
1076 ykey.value*180.0/math.pi,
1077 zkey.value*180.0/math.pi])
1078 quat = euler.toQuat()
1079
1080
1081
1082
1083 quatVal = CrossQuats(niBone_bind_quat_inv, quat)
1084 rot = CrossQuats(CrossQuats(extra_matrix_quat_inv, quatVal), extra_matrix_quat)
1085 b_posebone.quat = rot
1086 b_posebone.insertKey(b_armature, frame, [Blender.Object.Pose.ROT])
1087
1088 if translations:
1089 rot_keys_dict[frame] = Blender.Mathutils.Quaternion(rot)
1090
1091
1092 else:
1093
1094 if kfd.quaternionKeys:
1095 self.logger.debug('Rotation keys...(quaternions)')
1096 quaternionKeys = kfd.quaternionKeys
1097 for key in quaternionKeys:
1098 frame = 1 + int(key.time * self.fps + 0.5)
1099 keyVal = key.value
1100 quat = Blender.Mathutils.Quaternion([keyVal.w, keyVal.x, keyVal.y, keyVal.z])
1101
1102
1103
1104 quatVal = CrossQuats(niBone_bind_quat_inv, quat)
1105 rot = CrossQuats(CrossQuats(extra_matrix_quat_inv, quatVal), extra_matrix_quat)
1106 b_posebone.quat = rot
1107 b_posebone.insertKey(b_armature, frame,
1108 [Blender.Object.Pose.ROT])
1109
1110 if translations:
1111 rot_keys_dict[frame] = Blender.Mathutils.Quaternion(rot)
1112
1113
1114
1115
1116
1117
1118 if translations.keys:
1119 self.logger.debug('Translation keys...')
1120 for key in translations.keys:
1121
1122 frame = 1 + int(key.time * self.fps + 0.5)
1123 keyVal = key.value
1124 trans = Blender.Mathutils.Vector(keyVal.x, keyVal.y, keyVal.z)
1125 locVal = (trans - niBone_bind_trans) * niBone_bind_rot_inv * (1.0/niBone_bind_scale)
1126
1127
1128 if rot_keys_dict:
1129 try:
1130 rot = rot_keys_dict[frame].toMatrix()
1131 except KeyError:
1132
1133 ipo = action.getChannelIpo(bone_name)
1134 quat = Blender.Mathutils.Quaternion()
1135 quat.x = ipo.getCurve('QuatX').evaluate(frame)
1136 quat.y = ipo.getCurve('QuatY').evaluate(frame)
1137 quat.z = ipo.getCurve('QuatZ').evaluate(frame)
1138 quat.w = ipo.getCurve('QuatW').evaluate(frame)
1139 rot = quat.toMatrix()
1140 else:
1141 rot = Blender.Mathutils.Matrix([1.0,0.0,0.0],
1142 [0.0,1.0,0.0],
1143 [0.0,0.0,1.0])
1144
1145 if scale_keys_dict:
1146 try:
1147 sizeVal = scale_keys_dict[frame]
1148 except KeyError:
1149 ipo = action.getChannelIpo(bone_name)
1150 if ipo.getCurve('SizeX'):
1151 sizeVal = ipo.getCurve('SizeX').evaluate(frame)
1152 else:
1153 sizeVal = 1.0
1154 else:
1155 sizeVal = 1.0
1156 size = Blender.Mathutils.Matrix([sizeVal, 0.0, 0.0],
1157 [0.0, sizeVal, 0.0],
1158 [0.0, 0.0, sizeVal])
1159
1160 loc = (extra_matrix_trans * size * rot + locVal - extra_matrix_trans) * extra_matrix_rot_inv * (1.0/extra_matrix_scale)
1161 b_posebone.loc = loc
1162 b_posebone.insertKey(b_armature, frame, [Blender.Object.Pose.LOC])
1163 if translations:
1164 del scale_keys_dict
1165 del rot_keys_dict
1166
1167 if kfc:
1168 try:
1169 ipo = action.getChannelIpo(bone_name)
1170 except ValueError:
1171
1172 pass
1173 else:
1174 for b_curve in ipo:
1175 b_curve.extend = self.get_extend_from_flags(kfc.flags)
1176
1177
1178
1179 for bone_name, b_posebone in b_armature.getPose().bones.items():
1180
1181 niBone = self.blocks[bone_name]
1182
1183 if niBone in self.bonePriorities:
1184 constr = b_posebone.constraints.append(
1185 Blender.Constraint.Type.NULL)
1186 constr.name = "priority:%i" % self.bonePriorities[niBone]
1187
1188 return b_armature
1189
1190 - def importBone(self, niBlock, b_armature, b_armatureData, niArmature):
1191 """Adds a bone to the armature in edit mode."""
1192
1193 if not self.is_bone(niBlock):
1194 return None
1195
1196
1197 nub_length = 5.0
1198 scale = self.IMPORT_SCALE_CORRECTION
1199
1200 bone_name = self.importName(niBlock, 32)
1201 niChildBones = [ child for child in niBlock.children
1202 if self.is_bone(child) ]
1203
1204 b_bone = Blender.Armature.Editbone()
1205
1206 armature_space_matrix = self.importMatrix(niBlock,
1207 relative_to = niArmature)
1208
1209 b_bone_head_x = armature_space_matrix[3][0]
1210 b_bone_head_y = armature_space_matrix[3][1]
1211 b_bone_head_z = armature_space_matrix[3][2]
1212
1213 b_bone_tail_x = b_bone_head_x
1214 b_bone_tail_y = b_bone_head_y
1215 b_bone_tail_z = b_bone_head_z
1216 is_zero_length = True
1217
1218 if len(niChildBones) > 0:
1219 m_correction = self.find_correction_matrix(niBlock, niArmature)
1220 child_matrices = [ self.importMatrix(child,
1221 relative_to = niArmature)
1222 for child in niChildBones ]
1223 b_bone_tail_x = sum(child_matrix[3][0]
1224 for child_matrix
1225 in child_matrices) / len(child_matrices)
1226 b_bone_tail_y = sum(child_matrix[3][1]
1227 for child_matrix
1228 in child_matrices) / len(child_matrices)
1229 b_bone_tail_z = sum(child_matrix[3][2]
1230 for child_matrix
1231 in child_matrices) / len(child_matrices)
1232
1233 dx = b_bone_head_x - b_bone_tail_x
1234 dy = b_bone_head_y - b_bone_tail_y
1235 dz = b_bone_head_z - b_bone_tail_z
1236 is_zero_length = abs(dx + dy + dz) * 200 < self.EPSILON
1237 elif self.IMPORT_REALIGN_BONES == 2:
1238
1239
1240
1241
1242 m_correction = self.find_correction_matrix(niBlock._parent,
1243 niArmature)
1244
1245 if is_zero_length:
1246
1247
1248 if (self.IMPORT_REALIGN_BONES == 2) \
1249 or not self.is_bone(niBlock._parent):
1250
1251
1252 b_bone_tail_x = b_bone_head_x + (nub_length * scale)
1253 else:
1254
1255
1256
1257 parent_tail = b_armatureData.bones[
1258 self.names[niBlock._parent]].tail
1259 dx = b_bone_head_x - parent_tail[0]
1260 dy = b_bone_head_y - parent_tail[1]
1261 dz = b_bone_head_z - parent_tail[2]
1262 if abs(dx + dy + dz) * 200 < self.EPSILON:
1263
1264
1265 parent_head = b_armatureData.bones[
1266 self.names[niBlock._parent]].head
1267 dx = parent_tail[0] - parent_head[0]
1268 dy = parent_tail[1] - parent_head[1]
1269 dz = parent_tail[2] - parent_head[2]
1270 direction = Vector(dx, dy, dz)
1271 direction.normalize()
1272 b_bone_tail_x = b_bone_head_x + (direction[0] * nub_length * scale)
1273 b_bone_tail_y = b_bone_head_y + (direction[1] * nub_length * scale)
1274 b_bone_tail_z = b_bone_head_z + (direction[2] * nub_length * scale)
1275
1276
1277 b_bone.head = Vector(b_bone_head_x, b_bone_head_y, b_bone_head_z)
1278 b_bone.tail = Vector(b_bone_tail_x, b_bone_tail_y, b_bone_tail_z)
1279
1280 if self.IMPORT_REALIGN_BONES == 2:
1281
1282 b_bone.matrix = m_correction.resize4x4() * armature_space_matrix
1283 elif self.IMPORT_REALIGN_BONES == 1:
1284
1285 pass
1286 else:
1287
1288 b_bone.matrix = armature_space_matrix
1289
1290
1291 b_armatureData.bones[bone_name] = b_bone
1292
1293
1294 old_bone_matrix_inv = Blender.Mathutils.Matrix(armature_space_matrix)
1295 old_bone_matrix_inv.invert()
1296 new_bone_matrix = Blender.Mathutils.Matrix(b_bone.matrix)
1297 new_bone_matrix.resize4x4()
1298 new_bone_matrix[3][0] = b_bone_head_x
1299 new_bone_matrix[3][1] = b_bone_head_y
1300 new_bone_matrix[3][2] = b_bone_head_z
1301
1302
1303 self.bonesExtraMatrix[niBlock] = new_bone_matrix * old_bone_matrix_inv
1304
1305 for niBone in niChildBones:
1306 b_child_bone = self.importBone(
1307 niBone, b_armature, b_armatureData, niArmature)
1308 b_child_bone.parent = b_bone
1309
1310 return b_bone
1311
1313 """Returns the correction matrix for a bone."""
1314 m_correction = self.IDENTITY44.rotationPart()
1315 if (self.IMPORT_REALIGN_BONES == 2) and self.is_bone(niBlock):
1316 armature_space_matrix = self.importMatrix(niBlock,
1317 relative_to = niArmature)
1318
1319 niChildBones = [ child for child in niBlock.children
1320 if self.is_bone(child) ]
1321 (sum_x, sum_y, sum_z, dummy) = armature_space_matrix[3]
1322 if len(niChildBones) > 0:
1323 child_local_matrices = [ self.importMatrix(child)
1324 for child in niChildBones ]
1325 sum_x = sum(cm[3][0] for cm in child_local_matrices)
1326 sum_y = sum(cm[3][1] for cm in child_local_matrices)
1327 sum_z = sum(cm[3][2] for cm in child_local_matrices)
1328 list_xyz = [ int(c * 200)
1329 for c in (sum_x, sum_y, sum_z,
1330 -sum_x, -sum_y, -sum_z) ]
1331 idx_correction = list_xyz.index(max(list_xyz))
1332 alignment_offset = 0.0
1333 if (idx_correction == 0 or idx_correction == 3) and abs(sum_x) > 0:
1334 alignment_offset = float(abs(sum_y) + abs(sum_z)) / abs(sum_x)
1335 elif (idx_correction == 1 or idx_correction == 4) and abs(sum_y) > 0:
1336 alignment_offset = float(abs(sum_z) + abs(sum_x)) / abs(sum_y)
1337 elif abs(sum_z) > 0:
1338 alignment_offset = float(abs(sum_x) + abs(sum_y)) / abs(sum_z)
1339
1340
1341 if alignment_offset < 0.25:
1342 m_correction = self.BONE_CORRECTION_MATRICES[idx_correction]
1343 return m_correction
1344
1345
1346 - def getTextureHash(self, source):
1347 """Helper function for importTexture. Returns a key that uniquely
1348 identifies a texture from its source (which is either a
1349 NiSourceTexture block, or simply a path string).
1350 """
1351 if not source:
1352 return None
1353 elif isinstance(source, NifFormat.NiSourceTexture):
1354 return source.getHash()
1355 elif isinstance(source, basestring):
1356 return source.lower()
1357 else:
1358 raise TypeError("source must be NiSourceTexture block or string")
1359
1360 - def importTexture(self, source):
1361 """Convert a NiSourceTexture block, or simply a path string,
1362 to a Blender Texture object, return the Texture object and
1363 stores it in the self.textures dictionary to avoid future
1364 duplicate imports.
1365 """
1366
1367
1368 if not source:
1369 return None
1370
1371
1372 texture_hash = self.getTextureHash(source)
1373
1374 try:
1375
1376
1377 return self.textures[texture_hash]
1378 except KeyError:
1379 pass
1380
1381 b_image = None
1382
1383 if (isinstance(source, NifFormat.NiSourceTexture)
1384 and not source.useExternal):
1385
1386 n = 0
1387 while True:
1388 fn = "image%03i.dds" % n
1389 tex = Blender.sys.join(Blender.sys.dirname(self.IMPORT_FILE),
1390 fn)
1391 if not Blender.sys.exists(tex):
1392 break
1393 n += 1
1394 if self.IMPORT_EXPORTEMBEDDEDTEXTURES:
1395
1396 stream = open(tex, "wb")
1397 try:
1398 self.logger.info("Saving embedded texture as %s" % tex)
1399 source.pixelData.saveAsDDS(stream)
1400 except ValueError:
1401
1402 b_image = None
1403 else:
1404
1405 b_image = Blender.Image.Load(tex)
1406
1407
1408
1409
1410 try:
1411 b_image.size
1412 except:
1413 b_image = None
1414 finally:
1415 stream.close()
1416 else:
1417 b_image = None
1418 else:
1419
1420 if isinstance(source, NifFormat.NiSourceTexture):
1421 fn = source.fileName
1422 elif isinstance(source, basestring):
1423 fn = source
1424 else:
1425 raise TypeError(
1426 "source must be NiSourceTexture block or string")
1427 fn = fn.replace( '\\', Blender.sys.sep )
1428 fn = fn.replace( '/', Blender.sys.sep )
1429
1430 importpath = Blender.sys.dirname(self.IMPORT_FILE)
1431 searchPathList = (
1432 [importpath]
1433 + self.IMPORT_TEXTURE_PATH)
1434 if Blender.Get("texturesdir"):
1435 searchPathList += [
1436 Blender.sys.dirname(Blender.Get("texturesdir"))]
1437
1438
1439 meshes_index = importpath.lower().find("meshes")
1440 if meshes_index != -1:
1441 searchPathList.append(importpath[:meshes_index] + 'textures')
1442
1443
1444 art_index = importpath.lower().find("art")
1445 if art_index != -1:
1446 searchPathList.append(importpath[:art_index] + 'shared')
1447
1448 for texdir in searchPathList:
1449 texdir = texdir.replace( '\\', Blender.sys.sep )
1450 texdir = texdir.replace( '/', Blender.sys.sep )
1451
1452
1453 texfns = reduce(operator.add,
1454 [ [ fn[:-4]+ext, fn[:-4].lower()+ext ]
1455 for ext in ('.DDS','.dds','.PNG','.png',
1456 '.TGA','.tga','.BMP','.bmp',
1457 '.JPG','.jpg') ] )
1458 texfns = [fn, fn.lower()] + list(set(texfns))
1459 for texfn in texfns:
1460
1461 if (texfn[:9].lower() == 'textures' + Blender.sys.sep) \
1462 and (texdir[-9:].lower() == Blender.sys.sep + 'textures'):
1463
1464 tex = Blender.sys.join( texdir[:-9], texfn )
1465 else:
1466 tex = Blender.sys.join( texdir, texfn )
1467 self.logger.debug("Searching %s" % tex)
1468 if Blender.sys.exists(tex) == 1:
1469
1470 b_image = Blender.Image.Load(tex)
1471
1472
1473
1474
1475 try:
1476 b_image.size
1477 except:
1478 b_image = None
1479 else:
1480
1481 self.logger.debug("Found '%s' at %s" % (fn, tex))
1482 break
1483 if b_image:
1484 break
1485 else:
1486 tex = Blender.sys.join(searchPathList[0], fn)
1487
1488
1489 if not b_image:
1490 self.logger.warning("""\
1491 Texture '%s' not found or not supported and no alternate available"""
1492 % fn)
1493 b_image = Blender.Image.New(fn, 1, 1, 24)
1494 b_image.filename = tex
1495
1496
1497 b_texture = Blender.Texture.New()
1498 b_texture.setType('Image')
1499 b_texture.setImage(b_image)
1500 b_texture.imageFlags |= Blender.Texture.ImageFlags.INTERPOL
1501 b_texture.imageFlags |= Blender.Texture.ImageFlags.MIPMAP
1502
1503
1504 self.textures[texture_hash] = b_texture
1505 return b_texture
1506
1507 - def getMaterialHash(self, matProperty, textProperty,
1508 alphaProperty, specProperty,
1509 textureEffect, wireProperty,
1510 bsShaderProperty, extraDatas):
1511 """Helper function for importMaterial. Returns a key that
1512 uniquely identifies a material from its properties. The key
1513 ignores the material name as that does not affect the
1514 rendering.
1515 """
1516 return (matProperty.getHash(ignore_strings=True)
1517 if matProperty else None,
1518 textProperty.getHash() if textProperty else None,
1519 alphaProperty.getHash() if alphaProperty else None,
1520 specProperty.getHash() if specProperty else None,
1521 textureEffect.getHash() if textureEffect else None,
1522 wireProperty.getHash() if wireProperty else None,
1523 bsShaderProperty.getHash() if bsShaderProperty else None,
1524 tuple(extra.getHash() for extra in extraDatas))
1525
1526 - def importMaterial(self, matProperty, textProperty,
1527 alphaProperty, specProperty,
1528 textureEffect, wireProperty,
1529 bsShaderProperty, extraDatas):
1530 """Creates and returns a material."""
1531
1532 material_hash = self.getMaterialHash(matProperty, textProperty,
1533 alphaProperty, specProperty,
1534 textureEffect, wireProperty,
1535 bsShaderProperty,
1536 extraDatas)
1537 try:
1538 return self.materials[material_hash]
1539 except KeyError:
1540 pass
1541
1542
1543 name = self.importName(matProperty)
1544 material = Blender.Material.New(name)
1545
1546 blendmode = Blender.Texture.BlendModes["MIX"]
1547 if textProperty:
1548 if textProperty.applyMode == NifFormat.ApplyMode.APPLY_MODULATE:
1549 blendmode = Blender.Texture.BlendModes["MIX"]
1550 elif textProperty.applyMode == NifFormat.ApplyMode.APPLY_REPLACE:
1551 blendmode = Blender.Texture.BlendModes["MIX"]
1552 elif textProperty.applyMode == NifFormat.ApplyMode.APPLY_DECAL:
1553 blendmode = Blender.Texture.BlendModes["MIX"]
1554 elif textProperty.applyMode == NifFormat.ApplyMode.APPLY_HILIGHT:
1555 blendmode = Blender.Texture.BlendModes["LIGHTEN"]
1556 elif textProperty.applyMode == NifFormat.ApplyMode.APPLY_HILIGHT2:
1557 blendmode = Blender.Texture.BlendModes["MULTIPLY"]
1558 else:
1559 self.logger.warning("Unknown apply mode (%i) in material '%s', using blending mode 'MIX'"% (textProperty.applyMode, matProperty.name))
1560 elif bsShaderProperty:
1561
1562 blendmode = Blender.Texture.BlendModes["MIX"]
1563
1564
1565 spec = matProperty.specularColor
1566 material.setSpecCol([spec.r, spec.g, spec.b])
1567
1568 material.setSpec(1.0)
1569
1570 diff = matProperty.diffuseColor
1571 emit = matProperty.emissiveColor
1572
1573 if diff.r < self.EPSILON and diff.g < self.EPSILON and diff.b < self.EPSILON:
1574 if (emit.r + emit.g + emit.b) < self.EPSILON:
1575
1576 diff.r = 1.0
1577 diff.g = 1.0
1578 diff.b = 1.0
1579 else:
1580 diff.r = emit.r
1581 diff.g = emit.g
1582 diff.b = emit.b
1583 material.setRGBCol(diff.r, diff.g, diff.b)
1584
1585
1586
1587 amb = matProperty.ambientColor
1588
1589 if amb.r < self.EPSILON and amb.g < self.EPSILON and amb.b < self.EPSILON:
1590 amb.r = 1.0
1591 amb.g = 1.0
1592 amb.b = 1.0
1593 b_amb = 1.0
1594 if (emit.r + emit.g + emit.b) < self.EPSILON:
1595 b_emit = 0.0
1596 else:
1597 b_emit = matProperty.emitMulti / 10.0
1598 else:
1599 b_amb = 0.0
1600 b_emit = 0.0
1601 b_n = 0
1602 if diff.r > self.EPSILON:
1603 b_amb += amb.r/diff.r
1604 b_emit += emit.r/diff.r
1605 b_n += 1
1606 if diff.g > self.EPSILON:
1607 b_amb += amb.g/diff.g
1608 b_emit += emit.g/diff.g
1609 b_n += 1
1610 if diff.b > self.EPSILON:
1611 b_amb += amb.b/diff.b
1612 b_emit += emit.b/diff.b
1613 b_n += 1
1614 if b_n > 0:
1615 b_amb /= b_n
1616 b_emit /= b_n
1617 if b_amb > 1.0:
1618 b_amb = 1.0
1619 if b_emit > 1.0:
1620 b_emit = 1.0
1621 material.setAmb(b_amb)
1622 material.setEmit(b_emit)
1623
1624 glossiness = matProperty.glossiness
1625 hardness = int(glossiness * 4)
1626 if hardness < 1: hardness = 1
1627 if hardness > 511: hardness = 511
1628 material.setHardness(hardness)
1629
1630 alpha = matProperty.alpha
1631 material.setAlpha(alpha)
1632 baseTexture = None
1633 glowTexture = None
1634 envmapTexture = None
1635 bumpTexture = None
1636 darkTexture = None
1637 detailTexture = None
1638 refTexture = None
1639 if textProperty:
1640
1641 baseTexDesc = textProperty.baseTexture
1642 glowTexDesc = textProperty.glowTexture
1643 bumpTexDesc = textProperty.bumpMapTexture
1644 glossTexDesc = textProperty.glossTexture
1645 darkTexDesc = textProperty.darkTexture
1646 detailTexDesc = textProperty.detailTexture
1647 refTexDesc = None
1648
1649 for shaderTexDesc in textProperty.shaderTextures:
1650 if not shaderTexDesc.isUsed:
1651 continue
1652
1653 for extra in extraDatas:
1654 if extra.integerData == shaderTexDesc.mapIndex:
1655
1656 shader_name = extra.name
1657 break
1658 else:
1659
1660 self.logger.warn(
1661 "No slot for shader texture %s."
1662 % shaderTexDesc.textureData.source.fileName)
1663 continue
1664 try:
1665 extra_shader_index = (
1666 self.EXTRA_SHADER_TEXTURES.index(shader_name))
1667 except ValueError:
1668
1669 self.logger.warn(
1670 "No slot for shader texture %s."
1671 % shaderTexDesc.textureData.source.fileName)
1672 continue
1673 if extra_shader_index == 0:
1674
1675 if shaderTexDesc.textureData.source.fileName.lower().startswith("rrt_engine_env_map"):
1676
1677
1678 continue
1679
1680 self.logger.warn("Skipping environment map texture.")
1681 continue
1682 elif extra_shader_index == 1:
1683
1684 bumpTexDesc = shaderTexDesc.textureData
1685 elif extra_shader_index == 2:
1686
1687 glossTexDesc = shaderTexDesc.textureData
1688 elif extra_shader_index == 3:
1689
1690 refTexDesc = shaderTexDesc.textureData
1691 elif extra_shader_index == 4:
1692
1693 if shaderTexDesc.textureData.source.fileName.lower().startswith("rrt_cube_light_map"):
1694
1695
1696 continue
1697 self.logger.warn("Skipping light cube texture.")
1698 continue
1699 elif extra_shader_index == 5:
1700
1701 self.logger.warn("Skipping shadow texture.")
1702 continue
1703
1704 if baseTexDesc:
1705 baseTexture = self.importTexture(baseTexDesc.source)
1706 if baseTexture:
1707
1708 texco = Blender.Texture.TexCo.UV
1709
1710 mapto = Blender.Texture.MapTo.COL
1711
1712 material.setTexture(0, baseTexture, texco, mapto)
1713 mbaseTexture = material.getTextures()[0]
1714 mbaseTexture.blendmode = blendmode
1715 mbaseTexture.uvlayer = self.getUVLayerName(baseTexDesc.uvSet)
1716 if glowTexDesc:
1717 glowTexture = self.importTexture(glowTexDesc.source)
1718 if glowTexture:
1719
1720 glowTexture.imageFlags |= Blender.Texture.ImageFlags.CALCALPHA
1721
1722 texco = Blender.Texture.TexCo.UV
1723
1724 mapto = Blender.Texture.MapTo.COL | Blender.Texture.MapTo.EMIT
1725
1726 material.setTexture(1, glowTexture, texco, mapto)
1727 mglowTexture = material.getTextures()[1]
1728 mglowTexture.uvlayer = self.getUVLayerName(glowTexDesc.uvSet)
1729 if bumpTexDesc:
1730 bumpTexture = self.importTexture(bumpTexDesc.source)
1731 if bumpTexture:
1732
1733 texco = Blender.Texture.TexCo.UV
1734
1735 mapto = Blender.Texture.MapTo.NOR
1736
1737 material.setTexture(2, bumpTexture, texco, mapto)
1738 mbumpTexture = material.getTextures()[2]
1739 mbumpTexture.uvlayer = self.getUVLayerName(bumpTexDesc.uvSet)
1740 if glossTexDesc:
1741 glossTexture = self.importTexture(glossTexDesc.source)
1742 if glossTexture:
1743
1744 texco = Blender.Texture.TexCo.UV
1745
1746 mapto = Blender.Texture.MapTo.SPEC
1747
1748 material.setTexture(4, glossTexture, texco, mapto)
1749 mglossTexture = material.getTextures()[4]
1750 mglossTexture.uvlayer = self.getUVLayerName(glossTexDesc.uvSet)
1751 if darkTexDesc:
1752 darkTexture = self.importTexture(darkTexDesc.source)
1753 if darkTexture:
1754
1755 texco = Blender.Texture.TexCo.UV
1756
1757 mapto = Blender.Texture.MapTo.COL
1758
1759 material.setTexture(5, darkTexture, texco, mapto)
1760 mdarkTexture = material.getTextures()[5]
1761 mdarkTexture.uvlayer = self.getUVLayerName(darkTexDesc.uvSet)
1762
1763 mdarkTexture.blendmode = Blender.Texture.BlendModes["DARKEN"]
1764 if detailTexDesc:
1765 detailTexture = self.importTexture(detailTexDesc.source)
1766 if detailTexture:
1767
1768
1769 texco = Blender.Texture.TexCo.UV
1770
1771 mapto = Blender.Texture.MapTo.COL
1772
1773 material.setTexture(6, detailTexture, texco, mapto)
1774 mdetailTexture = material.getTextures()[6]
1775 mdetailTexture.uvlayer = self.getUVLayerName(detailTexDesc.uvSet)
1776 if refTexDesc:
1777 refTexture = self.importTexture(refTexDesc.source)
1778 if refTexture:
1779
1780 texco = Blender.Texture.TexCo.UV
1781
1782 mapto = Blender.Texture.MapTo.REF
1783
1784 material.setTexture(7, refTexture, texco, mapto)
1785 mrefTexture = material.getTextures()[7]
1786 mrefTexture.uvlayer = self.getUVLayerName(refTexDesc.uvSet)
1787
1788 elif bsShaderProperty:
1789
1790 baseTexFile = bsShaderProperty.textureSet.textures[0]
1791 if baseTexFile:
1792 baseTexture = self.importTexture(baseTexFile)
1793 if baseTexture:
1794
1795 texco = Blender.Texture.TexCo.UV
1796
1797 mapto = Blender.Texture.MapTo.COL
1798
1799 material.setTexture(0, baseTexture, texco, mapto)
1800 mbaseTexture = material.getTextures()[0]
1801 mbaseTexture.blendmode = blendmode
1802
1803 glowTexFile = bsShaderProperty.textureSet.textures[2]
1804 if glowTexFile:
1805 glowTexture = self.importTexture(glowTexFile)
1806 if glowTexture:
1807
1808 glowTexture.imageFlags |= Blender.Texture.ImageFlags.CALCALPHA
1809
1810 texco = Blender.Texture.TexCo.UV
1811
1812 mapto = Blender.Texture.MapTo.COL | Blender.Texture.MapTo.EMIT
1813
1814 material.setTexture(1, glowTexture, texco, mapto)
1815 mglowTexture = material.getTextures()[1]
1816 mglowTexture.blendmode = blendmode
1817
1818 bumpTexFile = bsShaderProperty.textureSet.textures[1]
1819 if bumpTexFile:
1820 bumpTexture = self.importTexture(bumpTexFile)
1821 if bumpTexture:
1822
1823 texco = Blender.Texture.TexCo.UV
1824
1825 mapto = Blender.Texture.MapTo.NOR
1826
1827 material.setTexture(2, bumpTexture, texco, mapto)
1828 mbumpTexture = material.getTextures()[2]
1829 mbumpTexture.blendmode = blendmode
1830
1831 if textureEffect:
1832 envmapTexture = self.importTexture(textureEffect.sourceTexture)
1833 if envmapTexture:
1834
1835 texco = Blender.Texture.TexCo.REFL
1836
1837 mapto = Blender.Texture.MapTo.COL
1838
1839 material.setTexture(3, envmapTexture, texco, mapto)
1840 menvmapTexture = material.getTextures()[3]
1841 menvmapTexture.blendmode = Blender.Texture.BlendModes["ADD"]
1842
1843 if alphaProperty:
1844 material.mode |= Blender.Material.Modes.ZTRANSP
1845
1846 if baseTexture:
1847
1848
1849
1850 if True:
1851 baseTexture.imageFlags |= Blender.Texture.ImageFlags.USEALPHA
1852 mbaseTexture.mapto |= Blender.Texture.MapTo.ALPHA
1853
1854
1855
1856 material.setAlpha(0.0)
1857 mbaseTexture.varfac = alpha
1858
1859
1860
1861 else:
1862
1863 material.setAlpha(1.0)
1864
1865 if (not specProperty) and (self.version != 0x14000004):
1866
1867
1868
1869
1870
1871 material.setSpec(0.0)
1872
1873 if wireProperty:
1874
1875 material.mode |= Blender.Material.Modes.WIRE
1876
1877 self.materials[material_hash] = material
1878 return material
1879
1881 """Import material animation data for given geometry."""
1882 if not self.IMPORT_ANIMATION:
1883 return
1884
1885 self.importMaterialUVController(b_material, n_geom)
1886
1888 """Import UV controller data."""
1889
1890 for n_ctrl in n_geom.getControllers():
1891 if isinstance(n_ctrl, NifFormat.NiUVController):
1892 self.logger.info("importing UV controller")
1893 b_channels = ("OfsX", "OfsY", "SizeX", "SizeY")
1894 for b_channel, n_uvgroup in zip(b_channels,
1895 n_ctrl.data.uvGroups):
1896 if n_uvgroup.keys:
1897
1898 b_ipo = self.getMaterialIpo(b_material)
1899 b_curve = b_ipo.addCurve(b_channel)
1900
1901
1902 b_curve.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
1903 b_curve.extend = self.get_extend_from_flags(n_ctrl.flags)
1904 for n_key in n_uvgroup.keys:
1905 if b_channel.startswith("Ofs"):
1906
1907 b_curve[1 + n_key.time * self.fps] = -n_key.value
1908 else:
1909 b_curve[1 + n_key.time * self.fps] = n_key.value
1910
1912 """Return existing material ipo data, or if none exists, create one
1913 and return that.
1914 """
1915 if not b_material.ipo:
1916 b_material.ipo = Blender.Ipo.New("Material", "MatIpo")
1917 return b_material.ipo
1918
1919 - def importMesh(self, niBlock,
1920 group_mesh=None,
1921 applytransform=False,
1922 relative_to=None):
1923 """Creates and returns a raw mesh, or appends geometry data to
1924 group_mesh.
1925
1926 @param niBlock: The nif block whose mesh data to import.
1927 @type niBlock: C{NiTriBasedGeom}
1928 @param group_mesh: The mesh to which to append the geometry
1929 data. If C{None}, a new mesh is created.
1930 @type group_mesh: A Blender object that has mesh data.
1931 @param applytransform: Whether to apply the niBlock's
1932 transformation to the mesh. If group_mesh is not C{None},
1933 then applytransform must be C{True}.
1934 @type applytransform: C{bool}
1935 """
1936 assert(isinstance(niBlock, NifFormat.NiTriBasedGeom))
1937
1938 logger = logging.getLogger("niftools.blender.import.mesh")
1939 logger.info("Importing mesh data for geometry %s" % niBlock.name)
1940
1941 if group_mesh:
1942 b_mesh = group_mesh
1943 b_meshData = group_mesh.getData(mesh=True)
1944 else:
1945
1946 b_name = self.importName(niBlock, 22)
1947
1948 b_meshData = Blender.Mesh.New(b_name)
1949 b_meshData.properties['longName'] = niBlock.name
1950
1951 b_mesh = self.scene.objects.new(b_meshData, b_name)
1952
1953
1954 if niBlock.flags & 1 == 1:
1955 b_mesh.setDrawType(2)
1956 else:
1957 b_mesh.setDrawType(4)
1958
1959
1960 if not applytransform:
1961 if group_mesh: raise NifImportError('BUG: cannot set matrix when importing meshes in groups; use applytransform = True')
1962 b_mesh.setMatrix(self.importMatrix(niBlock,
1963 relative_to = relative_to))
1964 else:
1965
1966 transform = self.importMatrix(niBlock, relative_to = relative_to)
1967
1968
1969 niData = niBlock.data
1970 if not niData:
1971 raise NifImportError("no ShapeData returned. Node name: %s " % b_name)
1972
1973
1974 verts = niData.vertices
1975
1976
1977 tris = [list(tri) for tri in niData.getTriangles()]
1978
1979
1980 uvco = niData.uvSets
1981
1982
1983 norms = niData.normals
1984
1985
1986 stencilProperty = self.find_property(niBlock,
1987 NifFormat.NiStencilProperty)
1988
1989 if stencilProperty:
1990 b_meshData.mode |= Blender.Mesh.Modes.TWOSIDED
1991 else:
1992 b_meshData.mode &= ~Blender.Mesh.Modes.TWOSIDED
1993
1994
1995
1996
1997 matProperty = self.find_property(niBlock, NifFormat.NiMaterialProperty)
1998 if matProperty:
1999
2000 textProperty = None
2001 if uvco:
2002 textProperty = self.find_property(niBlock,
2003 NifFormat.NiTexturingProperty)
2004
2005
2006 alphaProperty = self.find_property(niBlock,
2007 NifFormat.NiAlphaProperty)
2008
2009
2010 specProperty = self.find_property(niBlock,
2011 NifFormat.NiSpecularProperty)
2012
2013
2014 wireProperty = self.find_property(niBlock,
2015 NifFormat.NiWireframeProperty)
2016
2017
2018 bsShaderProperty = self.find_property(
2019 niBlock, NifFormat.BSShaderPPLightingProperty)
2020
2021
2022
2023
2024 textureEffect = None
2025 if isinstance(niBlock._parent, NifFormat.NiNode):
2026 lastchild = None
2027 for child in niBlock._parent.children:
2028 if child is niBlock:
2029 if isinstance(lastchild, NifFormat.NiTextureEffect):
2030 textureEffect = lastchild
2031 break
2032 lastchild = child
2033 else:
2034 raise RuntimeError("texture effect scanning bug")
2035
2036
2037
2038 if not textureEffect:
2039 for effect in niBlock._parent.effects:
2040 if isinstance(effect, NifFormat.NiTextureEffect):
2041 textureEffect = effect
2042 break
2043
2044
2045 extraDatas = []
2046 for extra in niBlock.getExtraDatas():
2047 if isinstance(extra, NifFormat.NiIntegerExtraData):
2048 if extra.name in self.EXTRA_SHADER_TEXTURES:
2049
2050 extraDatas.append(extra)
2051
2052
2053
2054 material = self.importMaterial(matProperty, textProperty,
2055 alphaProperty, specProperty,
2056 textureEffect, wireProperty,
2057 bsShaderProperty, extraDatas)
2058
2059 self.importMaterialControllers(material, niBlock)
2060 b_mesh_materials = b_meshData.materials
2061 try:
2062 materialIndex = b_mesh_materials.index(material)
2063 except ValueError:
2064 materialIndex = len(b_mesh_materials)
2065 b_meshData.materials += [material]
2066
2067
2068 if wireProperty:
2069 b_mesh.drawType = Blender.Object.DrawTypes["WIRE"]
2070 else:
2071 material = None
2072 materialIndex = 0
2073
2074
2075
2076 if len(b_meshData.verts) == 0:
2077 check_shift = True
2078 else:
2079 check_shift = False
2080
2081
2082
2083 v_map = [0 for i in xrange(len(verts))]
2084
2085
2086
2087
2088
2089 n_map = {}
2090 b_v_index = len(b_meshData.verts)
2091 for i, v in enumerate(verts):
2092
2093
2094
2095 if norms:
2096 n = norms[i]
2097 k = ( int(v.x*200), int(v.y*200), int(v.z*200),
2098 int(n.x*200), int(n.y*200), int(n.z*200) )
2099 else:
2100 k = ( int(v.x*200), int(v.y*200), int(v.z*200) )
2101
2102 try:
2103
2104
2105 n_map_k = n_map[k]
2106 except KeyError:
2107 n_map_k = None
2108 if not n_map_k:
2109
2110 n_map[k] = i
2111 v_map[i] = b_v_index
2112
2113 if applytransform:
2114 v = Blender.Mathutils.Vector(v.x, v.y, v.z)
2115 v *= transform
2116 b_meshData.verts.extend(v)
2117 else:
2118 b_meshData.verts.extend(v.x, v.y, v.z)
2119
2120
2121
2122
2123
2124
2125 b_v_index += 1
2126 else:
2127
2128
2129 v_map[i] = v_map[n_map_k]
2130
2131 logger.debug("%i unique vertex-normal pairs" % len(n_map))
2132
2133 del n_map
2134
2135
2136 f_map = [None]*len(tris)
2137 b_f_index = len(b_meshData.faces)
2138 num_new_faces = 0
2139 for i, f in enumerate(tris):
2140
2141 f_verts = [b_meshData.verts[v_map[vert_index]] for vert_index in f]
2142
2143
2144 if (f_verts[0] == f_verts[1]) or (f_verts[1] == f_verts[2]) or (f_verts[2] == f_verts[0]): continue
2145 tmp1 = len(b_meshData.faces)
2146
2147
2148 b_meshData.faces.extend(*f_verts)
2149 if tmp1 == len(b_meshData.faces): continue
2150
2151
2152
2153 if check_shift:
2154 added_face = b_meshData.faces[-1]
2155 if added_face.verts[0] == f_verts[0]:
2156 pass
2157 elif added_face.verts[2] == f_verts[0]:
2158 f[0], f[1], f[2] = f[1], f[2], f[0]
2159 elif added_face.verts[1] == f_verts[0]:
2160 f[0], f[1], f[2] = f[2], f[0], f[1]
2161 else:
2162 raise RuntimeError("face extend index bug")
2163
2164
2165 f_map[i] = b_f_index
2166 b_f_index += 1
2167 num_new_faces += 1
2168
2169
2170
2171 logger.debug("%i unique faces" % num_new_faces)
2172
2173
2174 for b_f_index in f_map:
2175 if b_f_index is None:
2176 continue
2177 f = b_meshData.faces[b_f_index]
2178 f.smooth = 1 if norms else 0
2179 f.mat = materialIndex
2180
2181
2182 vcol = niData.vertexColors
2183
2184 if vcol:
2185 b_meshData.vertexColors = 1
2186 for f, b_f_index in izip(tris, f_map):
2187 if b_f_index is None:
2188 continue
2189 b_face = b_meshData.faces[b_f_index]
2190
2191 for f_vert_index, vert_index in enumerate(f):
2192 b_face.col[f_vert_index].r = int(vcol[vert_index].r * 255)
2193 b_face.col[f_vert_index].g = int(vcol[vert_index].g * 255)
2194 b_face.col[f_vert_index].b = int(vcol[vert_index].b * 255)
2195 b_face.col[f_vert_index].a = int(vcol[vert_index].a * 255)
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210 if b_meshData.faces:
2211 b_meshData.faceUV = 1
2212 b_meshData.vertexUV = 0
2213 for i, uvSet in enumerate(uvco):
2214
2215
2216
2217 uvlayer = self.getUVLayerName(i)
2218 if not uvlayer in b_meshData.getUVLayerNames():
2219 b_meshData.addUVLayer(uvlayer)
2220 b_meshData.activeUVLayer = uvlayer
2221 for f, b_f_index in izip(tris, f_map):
2222 if b_f_index is None:
2223 continue
2224 uvlist = [ Vector(uvSet[vert_index].u, 1.0 - uvSet[vert_index].v) for vert_index in f ]
2225 b_meshData.faces[b_f_index].uv = tuple(uvlist)
2226 b_meshData.activeUVLayer = self.getUVLayerName(0)
2227
2228 if material:
2229
2230
2231 mbasetex = material.getTextures()[0]
2232 mglowtex = material.getTextures()[1]
2233 if b_meshData.vertexColors == 1:
2234 if mbasetex or mglowtex:
2235
2236 material.mode |= Blender.Material.Modes.VCOL_LIGHT
2237 else:
2238
2239 material.mode |= Blender.Material.Modes.VCOL_PAINT
2240
2241
2242
2243
2244 if mbasetex and uvco:
2245
2246 TEX = Blender.Mesh.FaceModes['TEX']
2247 imgobj = mbasetex.tex.getImage()
2248 if imgobj:
2249 for b_f_index in f_map:
2250 if b_f_index is None:
2251 continue
2252 f = b_meshData.faces[b_f_index]
2253 f.mode = TEX
2254 f.image = imgobj
2255
2256
2257 skininst = niBlock.skinInstance
2258 if skininst:
2259 skindata = skininst.data
2260 bones = skininst.bones
2261 boneWeights = skindata.boneList
2262 for idx, bone in enumerate(bones):
2263 vertexWeights = boneWeights[idx].vertexWeights
2264 groupname = self.names[bone]
2265 if not groupname in b_meshData.getVertGroupNames():
2266 b_meshData.addVertGroup(groupname)
2267 for skinWeight in vertexWeights:
2268 vert = skinWeight.index
2269 weight = skinWeight.weight
2270 b_meshData.assignVertsToGroup(
2271 groupname, [v_map[vert]], weight,
2272 Blender.Mesh.AssignModes.REPLACE)
2273
2274
2275 if isinstance(skininst, NifFormat.BSDismemberSkinInstance):
2276 skinpart = niBlock.getSkinPartition()
2277 for bodypart, skinpartblock in izip(
2278 skininst.partitions, skinpart.skinPartitionBlocks):
2279 bodypart_wrap = NifFormat.BSDismemberBodyPartType()
2280 bodypart_wrap.setValue(bodypart.bodyPart)
2281 groupname = bodypart_wrap.getDetailDisplay()
2282
2283 if not(groupname in b_meshData.getVertGroupNames()):
2284 b_meshData.addVertGroup(groupname)
2285
2286 groupverts = [v_map[v_index]
2287 for v_index in skinpartblock.vertexMap]
2288
2289 b_meshData.assignVertsToGroup(
2290 groupname, groupverts, 1,
2291 Blender.Mesh.AssignModes.ADD)
2292
2293
2294 if self.IMPORT_ANIMATION:
2295 morphCtrl = self.find_controller(niBlock, NifFormat.NiGeomMorpherController)
2296 if morphCtrl:
2297 morphData = morphCtrl.data
2298 if morphData.numMorphs:
2299
2300 b_meshData.insertKey(1, 'relative')
2301 baseverts = morphData.morphs[0].vectors
2302 b_ipo = Blender.Ipo.New('Key' , 'KeyIpo')
2303 b_meshData.key.ipo = b_ipo
2304 for idxMorph in xrange(1, morphData.numMorphs):
2305
2306 keyname = morphData.morphs[idxMorph].frameName
2307 if not keyname:
2308 keyname = 'Key %i' % idxMorph
2309
2310 morphverts = morphData.morphs[idxMorph].vectors
2311
2312
2313 assert(len(baseverts) == len(morphverts) == len(v_map))
2314 for bv, mv, b_v_index in izip(baseverts, morphverts, v_map):
2315 base = Blender.Mathutils.Vector(bv.x, bv.y, bv.z)
2316 delta = Blender.Mathutils.Vector(mv.x, mv.y, mv.z)
2317 v = base + delta
2318 if applytransform:
2319 v *= transform
2320 b_meshData.verts[b_v_index].co[0] = v.x
2321 b_meshData.verts[b_v_index].co[1] = v.y
2322 b_meshData.verts[b_v_index].co[2] = v.z
2323
2324 b_meshData.insertKey(idxMorph, 'relative')
2325
2326 b_meshData.key.blocks[idxMorph].name = keyname
2327
2328 b_curve = b_ipo.addCurve(keyname)
2329
2330
2331 b_curve.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
2332
2333 b_curve.extend = self.get_extend_from_flags(morphCtrl.flags)
2334
2335
2336
2337 morphkeys = morphData.morphs[idxMorph].keys
2338
2339 if (not morphkeys) and morphCtrl.interpolators:
2340 morphkeys = morphCtrl.interpolators[idxMorph].data.data.keys
2341 for key in morphkeys:
2342 x = key.value
2343 frame = 1+int(key.time * self.fps + 0.5)
2344 b_curve.addBezier( ( frame, x ) )
2345
2346 for bv, b_v_index in izip(baseverts, v_map):
2347 base = Blender.Mathutils.Vector(bv.x, bv.y, bv.z)
2348 if applytransform:
2349 base *= transform
2350 b_meshData.verts[b_v_index].co[0] = base.x
2351 b_meshData.verts[b_v_index].co[1] = base.y
2352 b_meshData.verts[b_v_index].co[2] = base.z
2353
2354
2355 if self.egmdata:
2356
2357
2358 sym_morphs = [list(morph.get_relative_vertices())
2359 for morph in self.egmdata.sym_morphs]
2360 asym_morphs = [list(morph.get_relative_vertices())
2361 for morph in self.egmdata.asym_morphs]
2362
2363
2364 b_meshData.insertKey(1, 'relative')
2365
2366 if self.IMPORT_EGMANIM:
2367
2368 b_ipo = Blender.Ipo.New('Key' , 'KeyIpo')
2369 b_meshData.key.ipo = b_ipo
2370
2371
2372 morphs = ([(morph, "EGM SYM %i" % i)
2373 for i, morph in enumerate(sym_morphs)]
2374 +
2375 [(morph, "EGM ASYM %i" % i)
2376 for i, morph in enumerate(asym_morphs)])
2377
2378 for morphverts, keyname in morphs:
2379
2380
2381
2382
2383
2384
2385 for bv, mv, b_v_index in izip(verts, morphverts, v_map):
2386 base = Blender.Mathutils.Vector(bv.x, bv.y, bv.z)
2387 delta = Blender.Mathutils.Vector(mv[0], mv[1], mv[2])
2388 v = base + delta
2389 if applytransform:
2390 v *= transform
2391 b_meshData.verts[b_v_index].co[0] = v.x
2392 b_meshData.verts[b_v_index].co[1] = v.y
2393 b_meshData.verts[b_v_index].co[2] = v.z
2394
2395 b_meshData.insertKey(1, 'relative')
2396
2397 b_meshData.key.blocks[-1].name = keyname
2398
2399 if self.IMPORT_EGMANIM:
2400
2401 b_curve = b_ipo.addCurve(keyname)
2402
2403 b_curve.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
2404
2405 b_curve.extend = Blender.IpoCurve.ExtendTypes.CONST
2406
2407 framestart = 1 + len(b_meshData.key.blocks) * 10
2408 for frame, value in ((framestart, 0),
2409 (framestart + 5, self.IMPORT_EGMANIMSCALE),
2410 (framestart + 10, 0)):
2411 b_curve.addBezier( ( frame, value ) )
2412
2413 if self.IMPORT_EGMANIM:
2414
2415 self.scene.getRenderingContext().startFrame(1)
2416 self.scene.getRenderingContext().endFrame(
2417 11 + len(b_meshData.key.blocks) * 10)
2418
2419
2420 for bv, b_v_index in izip(verts, v_map):
2421 base = Blender.Mathutils.Vector(bv.x, bv.y, bv.z)
2422 if applytransform:
2423 base *= transform
2424 b_meshData.verts[b_v_index].co[0] = base.x
2425 b_meshData.verts[b_v_index].co[1] = base.y
2426 b_meshData.verts[b_v_index].co[2] = base.z
2427
2428
2429 b_meshData.calcNormals()
2430
2431 return b_mesh
2432
2433
2434
2435
2436 - def importTextkey(self, niBlock):
2437 """Stores the text keys that define animation start and end in a text
2438 buffer, so that they can be re-exported. Since the text buffer is
2439 cleared on each import only the last import will be exported
2440 correctly."""
2441 if isinstance(niBlock, NifFormat.NiControllerSequence):
2442 txk = niBlock.textKeys
2443 else:
2444 txk = niBlock.find(block_type=NifFormat.NiTextKeyExtraData)
2445 if txk:
2446
2447 try:
2448 animtxt = [txt for txt in Blender.Text.Get() if txt.getName() == "Anim"][0]
2449 animtxt.clear()
2450 except:
2451 animtxt = Blender.Text.New("Anim")
2452
2453 frame = 1
2454 for key in txk.textKeys:
2455 newkey = str(key.value).replace('\r\n', '/').rstrip('/')
2456 frame = 1 + int(key.time * self.fps + 0.5)
2457 animtxt.write('%i/%s\n'%(frame, newkey))
2458
2459
2460 self.scene.getRenderingContext().startFrame(1)
2461 self.scene.getRenderingContext().endFrame(frame)
2462
2464 """Stores correction matrices in a text buffer so that the original
2465 alignment can be re-exported. In order for this to work it is necessary
2466 to mantain the imported names unaltered. Since the text buffer is
2467 cleared on each import only the last import will be exported
2468 correctly."""
2469
2470 try:
2471 bonetxt = Blender.Text.Get("BoneExMat")
2472 except NameError:
2473 bonetxt = Blender.Text.New("BoneExMat")
2474 bonetxt.clear()
2475
2476 for niBone, correction_matrix in self.bonesExtraMatrix.iteritems():
2477
2478 if sum(sum(abs(x) for x in row)
2479 for row in (correction_matrix - self.IDENTITY44)) \
2480 < self.EPSILON:
2481 continue
2482
2483 line = ''
2484 for row in correction_matrix:
2485 line = '%s;%s,%s,%s,%s' % (line, row[0], row[1], row[2], row[3])
2486
2487 blender_bone_name = self.names[niBone]
2488
2489 bonetxt.write('%s/%s\n' % (blender_bone_name, line[1:]))
2490
2491
2493 """Stores the original, long object names so that they can be
2494 re-exported. In order for this to work it is necessary to mantain the
2495 imported names unaltered. Since the text buffer is cleared on each
2496 import only the last import will be exported correctly."""
2497
2498 try:
2499 namestxt = Blender.Text.Get("FullNames")
2500 except NameError:
2501 namestxt = Blender.Text.New("FullNames")
2502 namestxt.clear()
2503
2504 for block, shortname in self.names.iteritems():
2505 if block.name and shortname != block.name:
2506 namestxt.write('%s;%s\n'% (shortname, block.name))
2507
2509 """Scan all blocks and return a reasonable number for FPS."""
2510
2511 key_times = []
2512 for root in roots:
2513 for kfd in root.tree(block_type = NifFormat.NiKeyframeData):
2514 key_times.extend(key.time for key in kfd.translations.keys)
2515 key_times.extend(key.time for key in kfd.scales.keys)
2516 key_times.extend(key.time for key in kfd.quaternionKeys)
2517 key_times.extend(key.time for key in kfd.xyzRotations[0].keys)
2518 key_times.extend(key.time for key in kfd.xyzRotations[1].keys)
2519 key_times.extend(key.time for key in kfd.xyzRotations[2].keys)
2520 for kfi in root.tree(block_type = NifFormat.NiBSplineInterpolator):
2521 if not kfi.basisData:
2522
2523
2524 continue
2525 key_times.extend(
2526 point * (kfi.stopTime - kfi.startTime)
2527 / (kfi.basisData.numControlPoints - 2)
2528 for point in xrange(kfi.basisData.numControlPoints - 2))
2529 for uvdata in root.tree(block_type = NifFormat.NiUVData):
2530 for uvgroup in uvdata.uvGroups:
2531 key_times.extend(key.time for key in uvgroup.keys)
2532
2533 if not key_times:
2534 return 30
2535
2536 fps = 30
2537 lowestDiff = sum(abs(int(time*fps+0.5)-(time*fps)) for time in key_times)
2538
2539 for testFps in [20, 25, 35]:
2540 diff = sum(abs(int(time*testFps+0.5)-(time*testFps)) for time in key_times)
2541 if diff < lowestDiff:
2542 lowestDiff = diff
2543 fps = testFps
2544 return fps
2545
2547 return
2548
2549 """
2550 niBlockList = [block for block in rootBlock.tree() if isinstance(block, NifFormat.NiAVObject)]
2551 for niBlock in niBlockList:
2552 kfc = self.find_controller(niBlock, NifFormat.NiKeyframeController)
2553 if not kfc: continue
2554 kfd = kfc.data
2555 if not kfd: continue
2556 _ANIMATION_DATA.extend([{'data': key, 'block': niBlock, 'frame': None} for key in kfd.translations.keys])
2557 _ANIMATION_DATA.extend([{'data': key, 'block': niBlock, 'frame': None} for key in kfd.scales.keys])
2558 if kfd.rotationType == 4:
2559 _ANIMATION_DATA.extend([{'data': key, 'block': niBlock, 'frame': None} for key in kfd.xyzRotations.keys])
2560 else:
2561 _ANIMATION_DATA.extend([{'data': key, 'block': niBlock, 'frame': None} for key in kfd.quaternionKeys])
2562
2563 # set the frames in the _ANIMATION_DATA list
2564 for key in _ANIMATION_DATA:
2565 # time 0 is frame 1
2566 key['frame'] = 1 + int(key['data'].time * self.fps + 0.5)
2567
2568 # sort by frame, I need this later
2569 _ANIMATION_DATA.sort(lambda key1, key2: cmp(key1['frame'], key2['frame']))
2570 """
2571
2572
2574 """Find a controller."""
2575 ctrl = niBlock.controller
2576 while ctrl:
2577 if isinstance(ctrl, controllerType):
2578 break
2579 ctrl = ctrl.nextController
2580 return ctrl
2581
2583 """Find a property."""
2584 for prop in niBlock.properties:
2585 if isinstance(prop, propertyType):
2586 return prop
2587 return None
2588
2589
2591 """Find extra data."""
2592
2593 extra = niBlock.extraData
2594 while extra:
2595 if isinstance(extra, extratype):
2596 break
2597 extra = extra.nextExtraData
2598 if extra:
2599 return extra
2600
2601
2602 for extra in niBlock.extraDataList:
2603 if isinstance(extra, extratype):
2604 return extra
2605 return None
2606
2608 """Set the parent block recursively through the tree, to allow
2609 crawling back as needed."""
2610 if isinstance(niBlock, NifFormat.NiNode):
2611
2612 children = [ child for child in niBlock.children if child ]
2613 for child in children:
2614 child._parent = niBlock
2615 self.set_parents(child)
2616
2618 """Mark armatures and bones by peeking into NiSkinInstance blocks."""
2619
2620
2621
2622 if self.IMPORT_SKELETON == 1 or (
2623 self.version == 0x14000005 and
2624 self.filebase.lower() in ('skeleton', 'skeletonbeast')):
2625
2626 if not isinstance(niBlock, NifFormat.NiNode):
2627 raise NifImportError('cannot import skeleton: root is not a NiNode')
2628
2629 if self.version == 0x04000002:
2630 skelroot = niBlock.find(block_name = 'Bip01',
2631 block_type = NifFormat.NiNode)
2632 if not skelroot:
2633 skelroot = niBlock
2634 else:
2635 skelroot = niBlock
2636 if skelroot not in self.armatures:
2637 self.armatures[skelroot] = []
2638 self.logger.info("Selecting node '%s' as skeleton root"
2639 % skelroot.name)
2640
2641 for bone in skelroot.tree():
2642 if bone is skelroot:
2643 continue
2644 if not isinstance(bone, NifFormat.NiNode):
2645 continue
2646 if self.is_grouping_node(bone):
2647 continue
2648 if bone not in self.armatures[skelroot]:
2649 self.armatures[skelroot].append(bone)
2650 return
2651
2652
2653 elif self.IMPORT_SKELETON == 2 and not self.armatures:
2654 skelroot = niBlock.find(block_name = self.selectedObjects[0].name)
2655 if not skelroot:
2656 raise NifImportError("nif has no armature '%s'"%self.selectedObjects[0].name)
2657 self.logger.debug("Identified '%s' as armature" % skelroot.name)
2658 self.armatures[skelroot] = []
2659 for bone_name in self.selectedObjects[0].data.bones.keys():
2660
2661 nif_bone_name = self.get_bone_name_for_nif(bone_name)
2662
2663 bone_block = skelroot.find(block_name = nif_bone_name)
2664
2665 if bone_block:
2666 self.logger.info(
2667 "Identified nif block '%s' with bone '%s' "
2668 "in selected armature" % (nif_bone_name, bone_name))
2669 self.names[bone_block] = bone_name
2670 self.armatures[skelroot].append(bone_block)
2671 self.complete_bone_tree(bone_block, skelroot)
2672
2673
2674 if isinstance(niBlock, NifFormat.NiTriBasedGeom):
2675
2676 if niBlock.isSkin():
2677 self.logger.debug("Skin found on block '%s'" % niBlock.name)
2678
2679
2680
2681 skininst = niBlock.skinInstance
2682 skelroot = skininst.skeletonRoot
2683 if self.IMPORT_SKELETON == 0:
2684 if not self.armatures.has_key(skelroot):
2685 self.armatures[skelroot] = []
2686 self.logger.debug("'%s' is an armature" % skelroot.name)
2687 elif self.IMPORT_SKELETON == 2:
2688 if not self.armatures.has_key(skelroot):
2689 raise NifImportError("nif structure incompatible with '%s' as armature: \nnode '%s' has '%s' as armature"%(self.selectedObjects[0].name, niBlock.name, skelroot.name))
2690
2691 for i, boneBlock in enumerate(skininst.bones):
2692 if boneBlock not in self.armatures[skelroot]:
2693 self.armatures[skelroot].append(boneBlock)
2694 self.logger.debug(
2695 "'%s' is a bone of armature '%s'"
2696 % (boneBlock.name, skelroot.name))
2697
2698
2699
2700 self.complete_bone_tree(boneBlock, skelroot)
2701
2702
2703 if self.IMPORT_EXTRANODES:
2704
2705 for bone in skelroot.tree():
2706 if bone is skelroot:
2707 continue
2708 if not isinstance(bone, NifFormat.NiNode):
2709 continue
2710 if self.is_grouping_node(bone):
2711 continue
2712 if bone not in self.armatures[skelroot]:
2713 self.armatures[skelroot].append(bone)
2714 self.logger.debug(
2715 "'%s' marked as extra bone of armature '%s'"
2716 % (bone.name, skelroot.name))
2717
2718
2719
2720 self.complete_bone_tree(bone, skelroot)
2721
2722
2723 for child in niBlock.getRefs():
2724 if not isinstance(child, NifFormat.NiAVObject): continue
2725 self.markArmaturesBones(child)
2726
2728 """Make sure that the bones actually form a tree all the way down to
2729 the armature node. Call this function on all bones of a skin instance."""
2730
2731 assert self.armatures.has_key(skelroot)
2732 assert bone in self.armatures[skelroot]
2733
2734 boneparent = bone._parent
2735 if boneparent != skelroot:
2736
2737 if boneparent not in self.armatures[skelroot]:
2738
2739 self.armatures[skelroot].append(boneparent)
2740
2741 self.logger.debug("'%s' is a bone of armature '%s'" % (boneparent.name, skelroot.name))
2742
2743
2744
2745 self.complete_bone_tree(boneparent, skelroot)
2746
2748 """Tests a NiNode to see if it's a bone."""
2749 if not niBlock : return False
2750 for bones in self.armatures.values():
2751 if niBlock in bones:
2752 return True
2753 return False
2754
2756 """Tests a block to see if it's an armature."""
2757 if isinstance(niBlock, NifFormat.NiNode):
2758 return self.armatures.has_key(niBlock)
2759 return False
2760
2762 """Detect closest bone ancestor."""
2763 par = niBlock._parent
2764 while par:
2765 if par == skelroot:
2766 return None
2767 if self.is_bone(par):
2768 return par
2769 par = par._parent
2770 return par
2771
2773 """Retrieves the Blender object or Blender bone matching the block."""
2774 if self.is_bone(niBlock):
2775 boneName = self.names[niBlock]
2776 armatureName = None
2777 for armatureBlock, boneBlocks in self.armatures.iteritems():
2778 if niBlock in boneBlocks:
2779 armatureName = self.names[armatureBlock]
2780 break
2781 else:
2782 raise NifImportError("cannot find bone '%s'"%boneName)
2783 armatureObject = Blender.Object.Get(armatureName)
2784 return armatureObject.data.bones[boneName]
2785 else:
2786 return Blender.Object.Get(self.names[niBlock])
2787
2789 """Determine whether node is grouping node.
2790 Returns the children which are grouped, or empty list if it is not a
2791 grouping node."""
2792
2793 if not isinstance(niBlock, NifFormat.NiNode): return []
2794
2795 if isinstance(niBlock, NifFormat.RootCollisionNode):
2796 return [ child for child in niBlock.children if
2797 isinstance(child, NifFormat.NiTriBasedGeom) ]
2798
2799 node_name = niBlock.name
2800 if not node_name:
2801 return []
2802
2803 if node_name[-9:].lower() == " nonaccum":
2804 node_name = node_name[:-9]
2805
2806 return [ child for child in niBlock.children
2807 if (isinstance(child, NifFormat.NiTriBasedGeom)
2808 and child.name.find(node_name) != -1) ]
2809
2811 """Load basic animation info for this object."""
2812 kfc = self.find_controller(niBlock, NifFormat.NiKeyframeController)
2813 if not kfc:
2814
2815 return
2816
2817 if kfc.interpolator:
2818 kfd = kfc.interpolator.data
2819 else:
2820 kfd = kfc.data
2821
2822 if not kfd:
2823
2824 return
2825
2826
2827 self.msg_progress("Animation")
2828 self.logger.info("Importing animation data for %s" % b_obj.name)
2829 assert(isinstance(kfd, NifFormat.NiKeyframeData))
2830
2831 b_ipo = b_obj.getIpo()
2832 if b_ipo is None:
2833 b_ipo = Blender.Ipo.New('Object', b_obj.name)
2834 b_obj.setIpo(b_ipo)
2835
2836 translations = kfd.translations
2837 scales = kfd.scales
2838
2839 self.logger.debug('Scale keys...')
2840 for key in scales.keys:
2841 frame = 1+int(key.time * self.fps + 0.5)
2842 Blender.Set('curframe', frame)
2843 b_obj.SizeX = key.value
2844 b_obj.SizeY = key.value
2845 b_obj.SizeZ = key.value
2846 b_obj.insertIpoKey(Blender.Object.SIZE)
2847
2848
2849 rotationType = kfd.rotationType
2850 if rotationType == 4:
2851
2852 xkeys = kfd.xyzRotations[0].keys
2853 ykeys = kfd.xyzRotations[1].keys
2854 zkeys = kfd.xyzRotations[2].keys
2855 self.logger.debug('Rotation keys...(euler)')
2856 for (xkey, ykey, zkey) in izip(xkeys, ykeys, zkeys):
2857 frame = 1+int(xkey.time * self.fps + 0.5)
2858
2859 Blender.Set('curframe', frame)
2860
2861 b_obj.RotX = xkey.value
2862 b_obj.RotY = ykey.value
2863 b_obj.RotZ = zkey.value
2864 b_obj.insertIpoKey(Blender.Object.ROT)
2865 else:
2866
2867 if kfd.quaternionKeys:
2868 self.logger.debug('Rotation keys...(quaternions)')
2869 for key in kfd.quaternionKeys:
2870 frame = 1+int(key.time * self.fps + 0.5)
2871 Blender.Set('curframe', frame)
2872 rot = Blender.Mathutils.Quaternion(key.value.w, key.value.x, key.value.y, key.value.z).toEuler()
2873
2874 b_obj.RotX = rot.x / self.R2D
2875 b_obj.RotY = rot.y / self.R2D
2876 b_obj.RotZ = rot.z / self.R2D
2877 b_obj.insertIpoKey(Blender.Object.ROT)
2878
2879 if translations.keys:
2880 self.logger.debug('Translation keys...')
2881 for key in translations.keys:
2882 frame = 1+int(key.time * self.fps + 0.5)
2883 Blender.Set('curframe', frame)
2884 b_obj.LocX = key.value.x
2885 b_obj.LocY = key.value.y
2886 b_obj.LocZ = key.value.z
2887 b_obj.insertIpoKey(Blender.Object.LOC)
2888
2889 Blender.Set('curframe', 1)
2890
2892 """Import an oblivion collision shape as list of blender meshes."""
2893 if isinstance(bhkshape, NifFormat.bhkConvexVerticesShape):
2894
2895 vertices, triangles = pyffi.utils.quickhull.qhull3d(
2896 [ (7 * vert.x, 7 * vert.y, 7 * vert.z)
2897 for vert in bhkshape.vertices ])
2898
2899
2900 me = Blender.Mesh.New('convexpoly')
2901 for vert in vertices:
2902 me.verts.extend(*vert)
2903 for triangle in triangles:
2904 me.faces.extend(triangle)
2905
2906
2907 ob = self.scene.objects.new(me, 'convexpoly')
2908
2909
2910 ob.drawType = Blender.Object.DrawTypes['BOUNDBOX']
2911
2912
2913 ob.rbShapeBoundType = 5
2914 ob.drawMode = Blender.Object.DrawModes['WIRE']
2915
2916 ob.rbRadius = min(vert.co.length for vert in me.verts)
2917
2918
2919 numverts = len(me.verts)
2920
2921 numdel = me.remDoubles(0.005)
2922 if numdel:
2923 self.logger.info('Removed %i duplicate vertices \
2924 (out of %i) from collision mesh' % (numdel, numverts))
2925
2926 return [ ob ]
2927
2928 elif isinstance(bhkshape, NifFormat.bhkTransformShape):
2929
2930 collision_objs = self.importBhkShape(bhkshape.shape)
2931
2932 transform = Blender.Mathutils.Matrix(*bhkshape.transform.asList())
2933 transform.transpose()
2934
2935 transform[3][0] *= 7
2936 transform[3][1] *= 7
2937 transform[3][2] *= 7
2938
2939 for ob in collision_objs:
2940 ob.setMatrix(ob.getMatrix('localspace') * transform)
2941
2942 return collision_objs
2943
2944 elif isinstance(bhkshape, NifFormat.bhkRigidBody):
2945
2946 collision_objs = self.importBhkShape(bhkshape.shape)
2947
2948 if isinstance(bhkshape, NifFormat.bhkRigidBodyT):
2949
2950 transform = Blender.Mathutils.Quaternion(
2951 bhkshape.rotation.w, bhkshape.rotation.x,
2952 bhkshape.rotation.y, bhkshape.rotation.z).toMatrix()
2953 transform.resize4x4()
2954
2955 transform[3][0] = bhkshape.translation.x * 7
2956 transform[3][1] = bhkshape.translation.y * 7
2957 transform[3][2] = bhkshape.translation.z * 7
2958
2959 for ob in collision_objs:
2960 ob.setMatrix(ob.getMatrix('localspace') * transform)
2961
2962 for ob in collision_objs:
2963 ob.rbFlags = \
2964 Blender.Object.RBFlags["ACTOR"] + \
2965 Blender.Object.RBFlags["DYNAMIC"] + \
2966 Blender.Object.RBFlags["RIGIDBODY"] + \
2967 Blender.Object.RBFlags["BOUNDS"]
2968 if bhkshape.mass > 0.0001:
2969 ob.rbMass = bhkshape.mass
2970
2971
2972
2973 self.havokObjects[bhkshape] = collision_objs
2974
2975 return collision_objs
2976
2977 elif isinstance(bhkshape, NifFormat.bhkBoxShape):
2978
2979 minx = -bhkshape.dimensions.x * 7
2980 maxx = +bhkshape.dimensions.x * 7
2981 miny = -bhkshape.dimensions.y * 7
2982 maxy = +bhkshape.dimensions.y * 7
2983 minz = -bhkshape.dimensions.z * 7
2984 maxz = +bhkshape.dimensions.z * 7
2985
2986 me = Blender.Mesh.New('box')
2987 for x in [minx, maxx]:
2988 for y in [miny, maxy]:
2989 for z in [minz, maxz]:
2990 me.verts.extend(x,y,z)
2991 me.faces.extend(
2992 [[0,1,3,2],[6,7,5,4],[0,2,6,4],[3,1,5,7],[4,5,1,0],[7,6,2,3]])
2993
2994
2995 ob = self.scene.objects.new(me, 'box')
2996
2997
2998 ob.setDrawType(Blender.Object.DrawTypes['BOUNDBOX'])
2999 ob.rbShapeBoundType = Blender.Object.RBShapes['BOX']
3000 ob.rbRadius = min(maxx, maxy, maxz)
3001 return [ ob ]
3002
3003 elif isinstance(bhkshape, NifFormat.bhkSphereShape):
3004 minx = miny = minz = -bhkshape.radius * 7
3005 maxx = maxy = maxz = +bhkshape.radius * 7
3006 me = Blender.Mesh.New('sphere')
3007 for x in [minx, maxx]:
3008 for y in [miny, maxy]:
3009 for z in [minz, maxz]:
3010 me.verts.extend(x,y,z)
3011 me.faces.extend(
3012 [[0,1,3,2],[6,7,5,4],[0,2,6,4],[3,1,5,7],[4,5,1,0],[7,6,2,3]])
3013
3014
3015 ob = self.scene.objects.new(me, 'sphere')
3016
3017
3018 ob.setDrawType(Blender.Object.DrawTypes['BOUNDBOX'])
3019 ob.rbShapeBoundType = Blender.Object.RBShapes['SPHERE']
3020 ob.rbRadius = maxx
3021 return [ ob ]
3022
3023 elif isinstance(bhkshape, NifFormat.bhkCapsuleShape):
3024
3025 length = (bhkshape.firstPoint - bhkshape.secondPoint).norm()
3026 minx = miny = -bhkshape.radius * 7
3027 maxx = maxy = +bhkshape.radius * 7
3028 minz = -(length + 2*bhkshape.radius) * 3.5
3029 maxz = +(length + 2*bhkshape.radius) * 3.5
3030
3031 me = Blender.Mesh.New('capsule')
3032 for x in [minx, maxx]:
3033 for y in [miny, maxy]:
3034 for z in [minz, maxz]:
3035 me.verts.extend(x,y,z)
3036 me.faces.extend(
3037 [[0,1,3,2],[6,7,5,4],[0,2,6,4],[3,1,5,7],[4,5,1,0],[7,6,2,3]])
3038
3039
3040 ob = self.scene.objects.new(me, 'capsule')
3041
3042
3043 ob.setDrawType(Blender.Object.DrawTypes['BOUNDBOX'])
3044 ob.rbShapeBoundType = Blender.Object.RBShapes['CYLINDER']
3045 ob.rbRadius = maxx
3046
3047
3048 normal = (bhkshape.firstPoint - bhkshape.secondPoint) / length
3049 normal = Blender.Mathutils.Vector(normal.x, normal.y, normal.z)
3050 minindex = min((abs(x), i) for i, x in enumerate(normal))[1]
3051 orthvec = Blender.Mathutils.Vector([(1 if i == minindex else 0)
3052 for i in (0,1,2)])
3053 vec1 = Blender.Mathutils.CrossVecs(normal, orthvec)
3054 vec1.normalize()
3055 vec2 = Blender.Mathutils.CrossVecs(normal, vec1)
3056
3057
3058 transform = Blender.Mathutils.Matrix(vec1, vec2, normal)
3059 transform.resize4x4()
3060 transform[3][0] = 3.5 * (bhkshape.firstPoint.x
3061 + bhkshape.secondPoint.x)
3062 transform[3][1] = 3.5 * (bhkshape.firstPoint.y
3063 + bhkshape.secondPoint.y)
3064 transform[3][2] = 3.5 * (bhkshape.firstPoint.z
3065 + bhkshape.secondPoint.z)
3066 ob.setMatrix(transform)
3067
3068
3069 return [ ob ]
3070
3071 elif isinstance(bhkshape, NifFormat.bhkPackedNiTriStripsShape):
3072
3073 hk_objects = []
3074 vertex_offset = 0
3075 subshapes = bhkshape.subShapes
3076 if not subshapes:
3077
3078 subshapes = bhkshape.data.subShapes
3079 for subshape_num, subshape in enumerate(subshapes):
3080 me = Blender.Mesh.New('poly%i' % subshape_num)
3081 for vert_index in xrange(vertex_offset,
3082 vertex_offset + subshape.numVertices):
3083 vert = bhkshape.data.vertices[vert_index]
3084 me.verts.extend(vert.x * 7, vert.y * 7, vert.z * 7)
3085 for hktriangle in bhkshape.data.triangles:
3086 if ((vertex_offset <= hktriangle.triangle.v1)
3087 and (hktriangle.triangle.v1
3088 < vertex_offset + subshape.numVertices)):
3089 me.faces.extend(hktriangle.triangle.v1 - vertex_offset,
3090 hktriangle.triangle.v2 - vertex_offset,
3091 hktriangle.triangle.v3 - vertex_offset)
3092 else:
3093 continue
3094
3095 align_plus = sum(abs(x)
3096 for x in ( me.faces[-1].no[0]
3097 + hktriangle.normal.x,
3098 me.faces[-1].no[1]
3099 + hktriangle.normal.y,
3100 me.faces[-1].no[2]
3101 + hktriangle.normal.z ))
3102 align_minus = sum(abs(x)
3103 for x in ( me.faces[-1].no[0]
3104 - hktriangle.normal.x,
3105 me.faces[-1].no[1]
3106 - hktriangle.normal.y,
3107 me.faces[-1].no[2]
3108 - hktriangle.normal.z ))
3109
3110 if align_plus < align_minus:
3111 me.faces[-1].verts = ( me.faces[-1].verts[1],
3112 me.faces[-1].verts[0],
3113 me.faces[-1].verts[2] )
3114
3115
3116 ob = self.scene.objects.new(me, 'poly%i' % subshape_num)
3117
3118
3119 ob.drawType = Blender.Object.DrawTypes['BOUNDBOX']
3120 ob.rbShapeBoundType = Blender.Object.RBShapes['POLYHEDERON']
3121 ob.drawMode = Blender.Object.DrawModes['WIRE']
3122
3123 ob.rbRadius = min(vert.co.length for vert in me.verts)
3124
3125 ob.addProperty("HavokMaterial", subshape.material, "INT")
3126
3127
3128 numverts = len(me.verts)
3129
3130 numdel = me.remDoubles(0.005)
3131 if numdel:
3132 self.logger.info('Removed %i duplicate vertices \
3133 (out of %i) from collision mesh' % (numdel, numverts))
3134
3135 vertex_offset += subshape.numVertices
3136 hk_objects.append(ob)
3137
3138 return hk_objects
3139
3140 elif isinstance(bhkshape, NifFormat.bhkNiTriStripsShape):
3141 return reduce(operator.add,
3142 (self.importBhkShape(strips)
3143 for strips in bhkshape.stripsData))
3144
3145 elif isinstance(bhkshape, NifFormat.NiTriStripsData):
3146 me = Blender.Mesh.New('poly')
3147
3148 for vert in bhkshape.vertices:
3149 me.verts.extend(vert.x, vert.y, vert.z)
3150 me.faces.extend(list(bhkshape.getTriangles()))
3151
3152
3153 ob = self.scene.objects.new(me, 'poly')
3154
3155
3156 ob.drawType = Blender.Object.DrawTypes['BOUNDBOX']
3157 ob.rbShapeBoundType = Blender.Object.RBShapes['POLYHEDERON']
3158 ob.drawMode = Blender.Object.DrawModes['WIRE']
3159
3160 ob.rbRadius = min(vert.co.length for vert in me.verts)
3161
3162
3163 numverts = len(me.verts)
3164
3165 numdel = me.remDoubles(0.005)
3166 if numdel:
3167 self.logger.info('Removed %i duplicate vertices \
3168 (out of %i) from collision mesh' % (numdel, numverts))
3169
3170 return [ ob ]
3171
3172 elif isinstance(bhkshape, NifFormat.bhkMoppBvTreeShape):
3173 return self.importBhkShape(bhkshape.shape)
3174
3175 elif isinstance(bhkshape, NifFormat.bhkListShape):
3176 return reduce(operator.add, ( self.importBhkShape(subshape)
3177 for subshape in bhkshape.subShapes ))
3178
3179 self.logger.warning("Unsupported bhk shape %s"
3180 % bhkshape.__class__.__name__)
3181 return []
3182
3183
3184
3186 """Imports a bone havok constraint as Blender object constraint."""
3187 assert(isinstance(hkbody, NifFormat.bhkRigidBody))
3188
3189
3190 if not hkbody.constraints:
3191 return
3192
3193
3194 if len(self.havokObjects[hkbody]) != 1:
3195 self.logger.warning("""\
3196 Rigid body with no or multiple shapes, constraints skipped""")
3197 return
3198
3199 b_hkobj = self.havokObjects[hkbody][0]
3200
3201 self.logger.info("Importing constraints for %s" % b_hkobj.name)
3202
3203
3204 for hkconstraint in hkbody.constraints:
3205
3206
3207 if not hkconstraint.numEntities == 2:
3208 self.logger.warning("Constraint with more than 2 entities, skipped")
3209 continue
3210 if not hkconstraint.entities[0] is hkbody:
3211 self.logger.warning("First constraint entity not self, skipped")
3212 continue
3213 if not hkconstraint.entities[1] in self.havokObjects:
3214 self.logger.warning("Second constraint entity not imported, skipped")
3215 continue
3216
3217
3218 if isinstance(hkconstraint, NifFormat.bhkRagdollConstraint):
3219 hkdescriptor = hkconstraint.ragdoll
3220 elif isinstance(hkconstraint, NifFormat.bhkLimitedHingeConstraint):
3221 hkdescriptor = hkconstraint.limitedHinge
3222 elif isinstance(hkconstraint, NifFormat.bhkHingeConstraint):
3223 hkdescriptor = hkconstraint.hinge
3224 elif isinstance(hkconstraint, NifFormat.bhkMalleableConstraint):
3225 if hkconstraint.type == 7:
3226 hkdescriptor = hkconstraint.ragdoll
3227 elif hkconstraint.type == 2:
3228 hkdescriptor = hkconstraint.limitedHinge
3229 else:
3230 self.logger.warning("Unknown malleable type (%i), skipped"
3231 % hkconstraint.type)
3232
3233
3234
3235 else:
3236 self.logger.warning("Unknown constraint type (%s), skipped"
3237 % hkconstraint.__class__.__name__)
3238 continue
3239
3240
3241 b_constr = b_hkobj.constraints.append(Blender.Constraint.Type.RIGIDBODYJOINT)
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274 b_constr[Blender.Constraint.Settings.TARGET] = \
3275 self.havokObjects[hkconstraint.entities[1]][0]
3276
3277 b_constr[Blender.Constraint.Settings.CONSTR_RB_TYPE] = 12
3278
3279 b_constr[Blender.Constraint.Settings.LIMIT] = 63
3280
3281
3282 pivot = Blender.Mathutils.Vector(
3283 hkdescriptor.pivotA.x * 7,
3284 hkdescriptor.pivotA.y * 7,
3285 hkdescriptor.pivotA.z * 7)
3286
3287
3288
3289 if isinstance(hkdescriptor, NifFormat.RagdollDescriptor):
3290
3291
3292 axis_z = Blender.Mathutils.Vector(
3293 hkdescriptor.twistA.x,
3294 hkdescriptor.twistA.y,
3295 hkdescriptor.twistA.z)
3296
3297 axis_x = Blender.Mathutils.Vector(
3298 hkdescriptor.planeA.x,
3299 hkdescriptor.planeA.y,
3300 hkdescriptor.planeA.z)
3301
3302
3303
3304 b_constr[Blender.Constraint.Settings.CONSTR_RB_MINLIMIT5] = \
3305 hkdescriptor.twistMinAngle
3306 b_constr[Blender.Constraint.Settings.CONSTR_RB_MAXLIMIT5] = \
3307 hkdescriptor.twistMaxAngle
3308 b_constr[Blender.Constraint.Settings.CONSTR_RB_MINLIMIT3] = \
3309 -hkdescriptor.coneMaxAngle
3310 b_constr[Blender.Constraint.Settings.CONSTR_RB_MAXLIMIT3] = \
3311 hkdescriptor.coneMaxAngle
3312 b_constr[Blender.Constraint.Settings.CONSTR_RB_MINLIMIT4] = \
3313 hkdescriptor.planeMinAngle
3314 b_constr[Blender.Constraint.Settings.CONSTR_RB_MAXLIMIT4] = \
3315 hkdescriptor.planeMaxAngle
3316 elif isinstance(hkdescriptor, NifFormat.LimitedHingeDescriptor):
3317
3318
3319 axis_y = Blender.Mathutils.Vector(
3320 hkdescriptor.perp2AxleInA1.x,
3321 hkdescriptor.perp2AxleInA1.y,
3322 hkdescriptor.perp2AxleInA1.z)
3323
3324
3325 axis_x = Blender.Mathutils.Vector(
3326 hkdescriptor.axleA.x,
3327 hkdescriptor.axleA.y,
3328 hkdescriptor.axleA.z)
3329
3330
3331 axis_z = Blender.Mathutils.Vector(
3332 hkdescriptor.perp2AxleInA2.x,
3333 hkdescriptor.perp2AxleInA2.y,
3334 hkdescriptor.perp2AxleInA2.z)
3335
3336 if (Blender.Mathutils.CrossVecs(axis_x, axis_y)
3337 - axis_z).length > 0.01:
3338
3339 if (Blender.Mathutils.CrossVecs(-axis_x, axis_y)
3340 - axis_z).length > 0.01:
3341 self.logger.warning("""\
3342 Axes are not orthogonal in %s; arbitrary orientation has been chosen"""
3343 % hkdescriptor.__class__.__name__)
3344 axis_z = Blender.Mathutils.CrossVecs(axis_x, axis_y)
3345 else:
3346
3347 self.logger.warning("""\
3348 X axis flipped in %s to fix orientation""" % hkdescriptor.__class__.__name__)
3349 axis_x = -axis_x
3350 elif isinstance(hkdescriptor, NifFormat.HingeDescriptor):
3351
3352
3353 axis_y = Blender.Mathutils.Vector(
3354 hkdescriptor.perp2AxleInA1.x,
3355 hkdescriptor.perp2AxleInA1.y,
3356 hkdescriptor.perp2AxleInA1.z)
3357
3358
3359 axis_z = Blender.Mathutils.Vector(
3360 hkdescriptor.perp2AxleInA2.x,
3361 hkdescriptor.perp2AxleInA2.y,
3362 hkdescriptor.perp2AxleInA2.z)
3363
3364
3365 axis_x = Blender.Mathutils.CrossVecs(axis_y, axis_z)
3366 else:
3367 raise ValueError("unknown descriptor %s"
3368 % hkdescriptor.__class__.__name__)
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395 if isinstance(hkbody, NifFormat.bhkRigidBodyT):
3396
3397 transform = Blender.Mathutils.Quaternion(
3398 hkbody.rotation.w, hkbody.rotation.x,
3399 hkbody.rotation.y, hkbody.rotation.z).toMatrix()
3400 transform.resize4x4()
3401
3402 transform[3][0] = hkbody.translation.x * 7
3403 transform[3][1] = hkbody.translation.y * 7
3404 transform[3][2] = hkbody.translation.z * 7
3405
3406 pivot = pivot * transform
3407 transform = transform.rotationPart()
3408 axis_z = axis_z * transform
3409 axis_x = axis_x * transform
3410
3411
3412
3413
3414 for niBone in self.bonesExtraMatrix:
3415 if niBone.collisionObject \
3416 and niBone.collisionObject.body is hkbody:
3417 transform = Blender.Mathutils.Matrix(
3418 self.bonesExtraMatrix[niBone])
3419 transform.invert()
3420 pivot = pivot * transform
3421 transform = transform.rotationPart()
3422 axis_z = axis_z * transform
3423 axis_x = axis_x * transform
3424 break
3425
3426
3427 if b_hkobj.parentbonename:
3428 pivot[1] -= b_hkobj.getParent().data.bones[
3429 b_hkobj.parentbonename].length
3430
3431
3432 transform = Blender.Mathutils.Matrix(
3433 b_hkobj.getMatrix('localspace'))
3434 transform.invert()
3435 pivot = pivot * transform
3436 transform = transform.rotationPart()
3437 axis_z = axis_z * transform
3438 axis_x = axis_x * transform
3439
3440
3441 b_constr[Blender.Constraint.Settings.CONSTR_RB_PIVX] = pivot[0]
3442 b_constr[Blender.Constraint.Settings.CONSTR_RB_PIVY] = pivot[1]
3443 b_constr[Blender.Constraint.Settings.CONSTR_RB_PIVZ] = pivot[2]
3444
3445
3446 constr_matrix = Blender.Mathutils.Matrix(
3447 axis_x,
3448 Blender.Mathutils.CrossVecs(axis_z, axis_x),
3449 axis_z)
3450 constr_euler = constr_matrix.toEuler()
3451 b_constr[Blender.Constraint.Settings.CONSTR_RB_AXX] = constr_euler.x
3452 b_constr[Blender.Constraint.Settings.CONSTR_RB_AXY] = constr_euler.y
3453 b_constr[Blender.Constraint.Settings.CONSTR_RB_AXZ] = constr_euler.z
3454
3455 assert((axis_x - Blender.Mathutils.Vector(1,0,0) * constr_matrix).length < 0.0001)
3456 assert((axis_z - Blender.Mathutils.Vector(0,0,1) * constr_matrix).length < 0.0001)
3457
3458
3459
3460 if isinstance(hkdescriptor, NifFormat.RagdollDescriptor):
3461
3462 b_constr[Blender.Constraint.Settings.CONSTR_RB_TYPE] = 1
3463 elif isinstance(hkdescriptor, (NifFormat.LimitedHingeDescriptor,
3464 NifFormat.HingeDescriptor)):
3465
3466 b_constr[Blender.Constraint.Settings.CONSTR_RB_TYPE] = 2
3467 else:
3468 raise ValueError("unknown descriptor %s"
3469 % hkdescriptor.__class__.__name__)
3470
3472 """Import a bounding box."""
3473 me = Blender.Mesh.New('BSBound')
3474 minx = bbox.center.x - bbox.dimensions.x * 0.5
3475 miny = bbox.center.y - bbox.dimensions.y * 0.5
3476 minz = bbox.center.z - bbox.dimensions.z * 0.5
3477 maxx = bbox.center.x + bbox.dimensions.x * 0.5
3478 maxy = bbox.center.y + bbox.dimensions.y * 0.5
3479 maxz = bbox.center.z + bbox.dimensions.z * 0.5
3480 for x in [minx, maxx]:
3481 for y in [miny, maxy]:
3482 for z in [minz, maxz]:
3483 me.verts.extend(x,y,z)
3484 me.faces.extend(
3485 [[0,1,3,2],[6,7,5,4],[0,2,6,4],[3,1,5,7],[4,5,1,0],[7,6,2,3]])
3486
3487
3488 ob = self.scene.objects.new(me, 'BSBound')
3489
3490
3491 ob.setDrawType(Blender.Object.DrawTypes['BOUNDBOX'])
3492 ob.rbShapeBoundType = Blender.Object.RBShapes['BOX']
3493 ob.rbRadius = min(maxx, maxy, maxz)
3494 return ob
3495
3497 return "UVTex.%03i" % uvset if uvset != 0 else "UVTex"
3498
3499
3500
3502 """Merge kf into nif.
3503
3504 *** Note: this function will eventually move to PyFFI. ***
3505 """
3506
3507 self.logger.info("Merging kf tree into nif tree")
3508
3509
3510 if not isinstance(kf_root, NifFormat.NiControllerSequence):
3511 raise NifImportError("non-Oblivion .kf import not supported")
3512
3513
3514 self.importTextkey(kf_root)
3515
3516
3517
3518 for controlledblock in kf_root.controlledBlocks:
3519
3520 nodename = controlledblock.getNodeName()
3521
3522 node = root.find(block_name = nodename)
3523 if not node:
3524 self.logger.info(
3525 "Animation for %s but no such node found in nif tree"
3526 % nodename)
3527 continue
3528
3529 controllertype = controlledblock.getControllerType()
3530 if not controllertype:
3531 self.logger.info(
3532 "Animation for %s without controller type, so skipping"
3533 % nodename)
3534 continue
3535 controller = self.find_controller(node, getattr(NifFormat, controllertype))
3536 if not controller:
3537 self.logger.info("Animation for %s with %s controller, but no such controller type found in corresponding node, so creating one"
3538 % (nodename, controllertype))
3539 controller = getattr(NifFormat, controllertype)()
3540
3541 node.addController(controller)
3542
3543 controller.interpolator = controlledblock.interpolator
3544
3545
3546
3547
3548
3549
3550 if isinstance(controller.interpolator,
3551 NifFormat.NiTransformInterpolator) \
3552 and controller.interpolator.data is None:
3553
3554 kfi = controller.interpolator
3555 kfi.data = NifFormat.NiTransformData()
3556
3557 kfd = controller.interpolator.data
3558
3559 kfd.numRotationKeys = 1
3560 kfd.rotationType = NifFormat.KeyType.LINEAR_KEY
3561 kfd.quaternionKeys.updateSize()
3562 kfd.quaternionKeys[0].time = 0.0
3563 kfd.quaternionKeys[0].value.x = kfi.rotation.x
3564 kfd.quaternionKeys[0].value.y = kfi.rotation.y
3565 kfd.quaternionKeys[0].value.z = kfi.rotation.z
3566 kfd.quaternionKeys[0].value.w = kfi.rotation.w
3567
3568 kfd.translations.numKeys = 1
3569 kfd.translations.keys.updateSize()
3570 kfd.translations.keys[0].time = 0.0
3571 kfd.translations.keys[0].value.x = kfi.translation.x
3572 kfd.translations.keys[0].value.y = kfi.translation.y
3573 kfd.translations.keys[0].value.z = kfi.translation.z
3574
3575
3576
3577
3578
3579 self.bonePriorities[node] = controlledblock.priority
3580
3581
3582
3583
3584
3585
3587 """Called when config script is done. Starts and times import."""
3588
3589
3590 is_editmode = Blender.Window.EditMode()
3591 Blender.Window.EditMode(0)
3592 Blender.Window.WaitCursor(1)
3593 t = Blender.sys.time()
3594
3595 try:
3596
3597 importer = NifImport(**config)
3598 importer.logger.info(
3599 'Finished in %.2f seconds' % (Blender.sys.time()-t))
3600 finally:
3601 Blender.Window.WaitCursor(0)
3602 if is_editmode:
3603 Blender.Window.EditMode(1)
3604
3609
3610 if __name__ == '__main__':
3611 _CONFIG = NifConfig()
3612 Blender.Window.FileSelector(fileselect_callback, "Import NIF", _CONFIG.config["IMPORT_FILE"])
3613