1
2
3 """
4 Name: 'NetImmerse/Gamebryo (.nif & .kf & .egm)'
5 Blender: 245
6 Group: 'Export'
7 Tooltip: 'Export NIF File Format (.nif & .kf & egm)'
8 """
9
10 __author__ = "The NifTools team, http://niftools.sourceforge.net/"
11 __url__ = ("blender", "elysiun", "http://niftools.sourceforge.net/")
12 __bpydoc__ = """\
13 This script exports Netimmerse and Gamebryo .nif files from Blender.
14 """
15
16 from itertools import izip
17 import logging
18
19 import Blender
20 from Blender import Ipo
21
22 from nif_common import NifImportExport
23 from nif_common import NifConfig
24 from nif_common import NifFormat
25 from nif_common import EgmFormat
26 from nif_common import __version__
27
28 import pyffi.spells.nif
29 import pyffi.spells.nif.fix
30
31
32
33
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
67
68
70 IDENTITY44 = NifFormat.Matrix44()
71 IDENTITY44.setIdentity()
72
73 APPLYMODE = {
74 Blender.Texture.BlendModes["MIX"] : NifFormat.ApplyMode.APPLY_MODULATE,
75 Blender.Texture.BlendModes["LIGHTEN"] : NifFormat.ApplyMode.APPLY_HILIGHT,
76 Blender.Texture.BlendModes["MULTIPLY"] : NifFormat.ApplyMode.APPLY_HILIGHT2
77 }
78 FLOAT_MIN = -3.4028234663852886e+38
79 FLOAT_MAX = +3.4028234663852886e+38
80
82 """Recover bone extra matrices."""
83 try:
84 bonetxt = Blender.Text.Get('BoneExMat')
85 except NameError:
86 return
87
88 for ln in bonetxt.asLines():
89 if len(ln)>0:
90
91 b, m = ln.split('/')
92 try:
93 mat = Blender.Mathutils.Matrix(
94 *[[float(f) for f in row.split(',')]
95 for row in m.split(';')])
96 except:
97 raise NifExportError('Syntax error in BoneExMat buffer.')
98
99 quat = mat.rotationPart().toQuat()
100 if sum(sum(abs(x) for x in vec)
101 for vec in mat.rotationPart() - quat.toMatrix()) > 0.01:
102 self.logger.warn(
103 "Bad bone extra matrix for bone %s. \n"
104 "Attempting to fix... but bone transform \n"
105 "may be incompatible with existing animations." % b)
106 self.logger.warn("old invalid matrix:\n%s" % mat)
107 trans = mat.translationPart()
108 mat = quat.toMatrix().resize4x4()
109 mat[3][0] = trans[0]
110 mat[3][1] = trans[1]
111 mat[3][2] = trans[2]
112 self.logger.warn("new valid matrix:\n%s" % mat)
113
114 mat.invert()
115 self.setBoneExtraMatrixInv(b, mat)
116
118 """Set bone extra matrix, inverted. The bonename is first converted
119 to blender style (to ensure compatibility with older imports).
120 """
121 self.bonesExtraMatrixInv[self.get_bone_name_for_blender(bonename)] = mat
122
124 """Get bone extra matrix, inverted. The bonename is first converted
125 to blender style (to ensure compatibility with older imports).
126 """
127 return self.bonesExtraMatrixInv[self.get_bone_name_for_blender(bonename)]
128
130 """Recovers the full object names from the text buffer and rebuilds
131 the names dictionary."""
132 try:
133 namestxt = Blender.Text.Get('FullNames')
134 except NameError:
135 return
136 for ln in namestxt.asLines():
137 if len(ln)>0:
138 name, fullname = ln.split(';')
139 self.names[name] = fullname
140
141
143 """Returns an unique name for use in the NIF file, from the name of a
144 Blender object."""
145 unique_name = "default_name"
146 if blender_name != None:
147 unique_name = blender_name
148
149 unique_name = self.get_bone_name_for_nif(unique_name)
150
151 if unique_name in self.blockNames or unique_name in self.names.values():
152 unique_int = 0
153 old_name = unique_name
154 while unique_name in self.blockNames or unique_name in self.names.values():
155 unique_name = '%s.%02d' % (old_name, unique_int)
156 unique_int += 1
157 self.blockNames.append(unique_name)
158 self.names[blender_name] = unique_name
159 return unique_name
160
162 """Returns the original imported name if present, or the name by which
163 the object was exported already."""
164 try:
165 return self.names[blender_name]
166 except KeyError:
167 return self.getUniqueName(blender_name)
168
170 """Return a list of exported objects."""
171 exported_objects = []
172
173
174 for obj in self.blocks.itervalues():
175
176 if obj is None:
177 continue
178
179 if obj in exported_objects:
180 continue
181
182 exported_objects.append(obj)
183
184 return exported_objects
185
187 """Main export function."""
188
189
190
191 self.msg_progress("Initializing", progbar=0)
192
193
194 for name, value in config.iteritems():
195 setattr(self, name, value)
196 if self.EXPORT_MW_NIFXNIFKF and self.EXPORT_VERSION == 'Morrowind':
197
198
199 self.EXPORT_ANIMATION = 0
200
201
202 self.logger = logging.getLogger("niftools.blender.export")
203
204
205 self.filename = self.EXPORT_FILE[:]
206 self.filepath = Blender.sys.dirname(self.filename)
207 self.filebase, self.fileext = Blender.sys.splitext(
208 Blender.sys.basename(self.filename))
209
210
211
212
213 self.blocks = {}
214
215
216 self.names = {}
217
218 self.blockNames = []
219
220
221
222
223
224
225
226
227
228
229 self.bonesExtraMatrixInv = {}
230
231
232
233
234 self.bonePriorities = {}
235
236
237 self.egmdata = None
238
239 try:
240
241
242 try:
243 self.version = NifFormat.versions[self.EXPORT_VERSION]
244 self.logger.info("Writing NIF version 0x%08X" % self.version)
245 except KeyError:
246
247 self.version = NifFormat.games[self.EXPORT_VERSION][-1]
248 self.logger.info("Writing %s NIF (version 0x%08X)"
249 % (self.EXPORT_VERSION, self.version))
250
251 if self.EXPORT_ANIMATION == 0:
252 self.logger.info("Exporting geometry and animation")
253 elif self.EXPORT_ANIMATION == 1:
254
255 self.logger.info("Exporting geometry only")
256 elif self.EXPORT_ANIMATION == 2:
257
258 self.logger.info("Exporting animation only (as .kf file)")
259
260 for ob in Blender.Object.Get():
261
262 if ob.getType() == 'Armature':
263
264
265 ob.data.restPosition = False
266 if (ob.data.envelopes):
267 self.logger.critical("'%s': Cannot export envelope skinning. If you have vertex groups, turn off envelopes. If you don't have vertex groups, select the bones one by one press W to convert their envelopes to vertex weights, and turn off envelopes." % ob.getName())
268 raise NifExportError("""\
269 '%s': Cannot export envelope skinning. Check console for instructions."""
270 % ob.getName())
271
272
273
274
275 if ob.getType() != 'Lattice':
276 try:
277 self.decomposeSRT(ob.getMatrix('localspace'))
278 except NifExportError:
279 raise NifExportError("""\
280 Non-uniform scaling not supported.
281 Workaround: apply size and rotation (CTRL-A) on '%s'.""" % ob.name)
282
283
284 self.scene = Blender.Scene.GetCurrent()
285 context = self.scene.getRenderingContext()
286 self.fspeed = 1.0 / context.framesPerSec()
287 self.fstart = context.startFrame()
288 self.fend = context.endFrame()
289
290
291 if (self.EXPORT_VERSION
292 in ('Civilization IV', 'Oblivion', 'Fallout 3')):
293 root_name = 'Scene Root'
294
295 else:
296 root_name = self.filebase
297
298
299
300 if (Blender.Object.GetSelected() == None):
301 raise NifExportError("""\
302 Please select the object(s) to export, and run this script again.""")
303 root_objects = set()
304 export_types = ('Empty', 'Mesh', 'Armature')
305 for root_object in [ob for ob in Blender.Object.GetSelected()
306 if ob.getType() in export_types]:
307 while (root_object.getParent() != None):
308 root_object = root_object.getParent()
309 if root_object.getType() not in export_types:
310 raise NifExportError("""\
311 Root object (%s) must be an 'Empty', 'Mesh', or 'Armature' object."""
312 % root_object.getName())
313 root_objects.add(root_object)
314
315
316 if self.EXPORT_SMOOTHOBJECTSEAMS:
317
318 self.logger.info("Smoothing seams between objects...")
319 vdict = {}
320 for ob in [ob for ob in self.scene.objects
321 if ob.getType() == 'Mesh']:
322 mesh = ob.getData(mesh=1)
323
324
325 for f in mesh.faces:
326 for v in f.verts:
327 vkey = (int(v.co[0]*200),
328 int(v.co[1]*200),
329 int(v.co[2]*200))
330 try:
331 vdict[vkey].append((v, f, mesh))
332 except KeyError:
333 vdict[vkey] = [(v, f, mesh)]
334
335 nv = 0
336 for vlist in vdict.itervalues():
337 if len(vlist) <= 1: continue
338 meshes = set([mesh for v, f, mesh in vlist])
339 if len(meshes) <= 1: continue
340
341
342 norm = Blender.Mathutils.Vector(0,0,0)
343 for v, f, mesh in vlist:
344 norm += f.no
345 norm.normalize()
346
347
348 fitlist = [Blender.Mathutils.DotVecs(f.no, norm)
349 for v, f, mesh in vlist]
350 bestfit = max(fitlist)
351
352
353 norm = Blender.Mathutils.Vector(0,0,0)
354 for (v, f, mesh), fit in zip(vlist, fitlist):
355 if fit >= bestfit - 0.2:
356 norm += f.no
357 norm.normalize()
358
359 for v, f, mesh in vlist:
360 v.no = norm
361
362 nv += 1
363 self.logger.info("Fixed normals on %i vertices." % nv)
364
365
366
367 try:
368 animtxt = Blender.Text.Get("Anim")
369 except NameError:
370 animtxt = None
371
372
373 self.rebuildBonesExtraMatrices()
374
375
376 self.rebuildFullNames()
377
378
379
380 self.msg_progress("Exporting")
381
382
383
384
385
386 root_block = self.exportNode(None, 'none', None, '')
387
388
389 self.logger.info("Exporting objects")
390 for root_object in root_objects:
391
392
393
394
395 self.exportNode(root_object, 'localspace',
396 root_block, root_object.getName())
397
398
399
400
401
402
403 self.logger.info("Checking animation groups")
404 if not animtxt:
405 has_controllers = False
406 for block in self.blocks:
407
408 if isinstance(block, NifFormat.NiObjectNET):
409 if block.controller:
410 has_controllers = True
411 break
412 if has_controllers:
413 self.logger.info("Defining default animation group.")
414
415 animtxt = Blender.Text.New("Anim")
416 animtxt.write("%i/Idle: Start/Idle: Loop Start\n%i/Idle: Loop Stop/Idle: Stop" % (self.fstart, self.fend))
417
418
419
420 self.logger.info("Checking controllers")
421 if animtxt and self.EXPORT_VERSION == "Morrowind":
422 has_keyframecontrollers = False
423 for block in self.blocks:
424 if isinstance(block, NifFormat.NiKeyframeController):
425 has_keyframecontrollers = True
426 break
427 if not has_keyframecontrollers:
428 self.logger.info("Defining dummy keyframe controller")
429
430 self.exportKeyframes(None, 'localspace', root_block)
431
432
433
434 if self.EXPORT_VERSION in ("Oblivion", "Fallout 3") \
435 and self.filebase.lower() in ('skeleton', 'skeletonbeast'):
436
437
438 self.logger.info("Adding controllers and interpolators for skeleton")
439 for block in self.blocks.keys():
440 if isinstance(block, NifFormat.NiNode) \
441 and block.name == "Bip01":
442 for bone in block.tree(block_type = NifFormat.NiNode):
443 ctrl = self.createBlock("NiTransformController")
444 interp = self.createBlock("NiTransformInterpolator")
445
446 ctrl.interpolator = interp
447 bone.addController(ctrl)
448
449 ctrl.flags = 12
450 ctrl.frequency = 1.0
451 ctrl.phase = 0.0
452 ctrl.startTime = self.FLOAT_MAX
453 ctrl.stopTime = self.FLOAT_MIN
454 interp.translation.x = bone.translation.x
455 interp.translation.y = bone.translation.y
456 interp.translation.z = bone.translation.z
457 scale, quat = bone.rotation.getScaleQuat()
458 interp.rotation.x = quat.x
459 interp.rotation.y = quat.y
460 interp.rotation.z = quat.z
461 interp.rotation.w = quat.w
462 interp.scale = bone.scale
463 else:
464
465
466
467 if animtxt:
468 anim_textextra = self.exportAnimGroups(animtxt, root_block)
469 else:
470 anim_textextra = None
471
472
473 if (self.EXPORT_VERSION in ('Oblivion', 'Fallout 3')
474 and self.filebase[:15].lower() == 'furnituremarker'):
475
476 try:
477 furniturenumber = int(self.filebase[15:])
478 except ValueError:
479 raise NifExportError("""\
480 Furniture marker has invalid number (%s). Name your file
481 'furnituremarkerxx.nif' where xx is a number between 00 and 19."""
482 % self.filebase[15:])
483
484 root_name = self.filebase
485
486 furnmark = self.createBlock("BSFurnitureMarker")
487 furnmark.name = "FRN"
488 furnmark.numPositions = 1
489 furnmark.positions.updateSize()
490 furnmark.positions[0].positionRef1 = furniturenumber
491 furnmark.positions[0].positionRef2 = furniturenumber
492
493 sgokeep = self.createBlock("NiStringExtraData")
494 sgokeep.name = "UBP"
495 sgokeep.stringData = "sgoKeep"
496
497 root_block.addExtraData(furnmark)
498 root_block.addExtraData(sgokeep)
499
500 self.logger.info("Checking collision")
501
502 if self.EXPORT_VERSION in ('Oblivion', 'Fallout 3'):
503 hascollision = False
504 for block in self.blocks:
505 if isinstance(block, NifFormat.bhkCollisionObject):
506 hascollision = True
507 break
508 if hascollision:
509
510 bsx = self.createBlock("BSXFlags")
511 bsx.name = 'BSX'
512 bsx.integerData = self.EXPORT_OB_BSXFLAGS
513 root_block.addExtraData(bsx)
514
515
516 total_mass = 0
517 for block in self.blocks:
518 if isinstance(block, NifFormat.bhkRigidBody):
519 block.updateMassCenterInertia(
520 solid = self.EXPORT_OB_SOLID)
521 total_mass += block.mass
522 if total_mass == 0:
523
524
525
526 total_mass = 1
527
528
529 for block in self.blocks:
530 if isinstance(block, NifFormat.bhkRigidBody):
531 mass = self.EXPORT_OB_MASS * block.mass / total_mass
532
533 if mass < 0.0001:
534 mass = 0.05
535 block.updateMassCenterInertia(
536 mass = mass,
537 solid = self.EXPORT_OB_SOLID)
538
539
540
541
542
543
544
545
546
547 for b_obj in self.getExportedObjects():
548 self.exportConstraints(b_obj, root_block)
549
550
551 if self.EXPORT_VERSION in ("Oblivion", "Fallout 3"):
552 if self.EXPORT_OB_PRN != "NONE":
553
554 prn = self.createBlock("NiStringExtraData")
555 prn.name = 'Prn'
556 prn.stringData = {
557 "BACK": "BackWeapon",
558 "SIDE": "SideWeapon",
559 "QUIVER": "Quiver",
560 "SHIELD": "Bip01 L ForearmTwist",
561 "HELM": "Bip01 Head",
562 "RING": "Bip01 R Finger1"}[self.EXPORT_OB_PRN]
563 root_block.addExtraData(prn)
564
565
566 if self.EXPORT_VERSION in ["Civilization IV",
567 "Sid Meier's Railroads"]:
568 self.exportVertexColorProperty(root_block)
569 self.exportZBufferProperty(root_block)
570
571 if self.EXPORT_FLATTENSKIN:
572
573
574
575 skelroots = set()
576 affectedbones = []
577 for block in self.blocks:
578 if isinstance(block, NifFormat.NiGeometry) and block.isSkin():
579 self.logger.info("Flattening skin on geometry %s"
580 % block.name)
581 affectedbones.extend(block.flattenSkin())
582 skelroots.add(block.skinInstance.skeletonRoot)
583
584 for skelroot in skelroots:
585 self.logger.info("Removing unused NiNodes in '%s'"
586 % skelroot.name)
587 skelrootchildren = [child for child in skelroot.children
588 if ((not isinstance(child,
589 NifFormat.NiNode))
590 or (child in affectedbones))]
591 skelroot.numChildren = len(skelrootchildren)
592 skelroot.children.updateSize()
593 for i, child in enumerate(skelrootchildren):
594 skelroot.children[i] = child
595
596
597 if abs(self.EXPORT_SCALE_CORRECTION - 1.0) > self.EPSILON:
598 self.logger.info("Applying scale correction %f"
599 % self.EXPORT_SCALE_CORRECTION)
600 data = NifFormat.Data()
601 data.roots = [root_block]
602 toaster = pyffi.spells.nif.NifToaster()
603 toaster.scale = self.EXPORT_SCALE_CORRECTION
604 pyffi.spells.nif.fix.SpellScale(data=data, toaster=toaster).recurse()
605
606 if self.egmdata:
607 self.egmdata.apply_scale(self.EXPORT_SCALE_CORRECTION)
608
609
610 if self.EXPORT_VERSION in ('Oblivion', 'Fallout 3'):
611 for block in self.blocks:
612 if isinstance(block, NifFormat.bhkMoppBvTreeShape):
613 self.logger.info("Generating mopp...")
614 block.updateMopp()
615
616
617
618
619
620
621 if ((root_block.numChildren == 1)
622 and (root_block.children[0].name in ['Scene Root', 'Bip01'])):
623 self.logger.info(
624 "Making '%s' the root block" % root_block.children[0].name)
625
626 self.blocks.pop(root_block)
627
628 old_root_block = root_block
629 root_block = old_root_block.children[0]
630
631 for extra in old_root_block.getExtraDatas():
632
633 extra.nextExtraData = None
634
635 root_block.addExtraData(extra)
636 for b in old_root_block.getControllers():
637 root_block.addController(b)
638 for b in old_root_block.properties:
639 root_block.addProperty(b)
640 for b in old_root_block.effects:
641 root_block.addEffect(b)
642 else:
643 root_block.name = root_name
644
645
646 if (self.EXPORT_VERSION == "Fallout 3"
647 and self.EXPORT_FO3_FADENODE):
648 self.logger.info(
649 "Making root block a BSFadeNode")
650 fade_root_block = NifFormat.BSFadeNode().deepcopy(root_block)
651 fade_root_block.replaceGlobalNode(root_block, fade_root_block)
652 root_block = fade_root_block
653
654
655 if self.EXPORT_VERSION == "Oblivion":
656 NIF_USER_VERSION = 11
657 NIF_USER_VERSION2 = 11
658 elif self.EXPORT_VERSION == "Fallout 3":
659 NIF_USER_VERSION = 11
660 NIF_USER_VERSION2 = 34
661 else:
662 NIF_USER_VERSION = 0
663 NIF_USER_VERSION2 = 0
664
665
666
667
668 if self.EXPORT_ANIMATION != 2:
669 ext = ".nif"
670 self.logger.info("Writing %s file" % ext)
671 self.msg_progress("Writing %s file" % ext)
672
673
674 if (self.fileext.lower() != ext):
675 self.logger.warning(
676 "Changing extension from %s to %s on output file"
677 % (self.fileext, ext))
678 self.filename = Blender.sys.join(self.filepath,
679 self.filebase + ext)
680 data = NifFormat.Data(version=self.version,
681 user_version=NIF_USER_VERSION,
682 user_version2=NIF_USER_VERSION2)
683 data.roots = [root_block]
684 data.neosteam = (self.EXPORT_VERSION == "NeoSteam")
685 stream = open(self.filename, "wb")
686 try:
687 data.write(stream)
688 finally:
689 stream.close()
690
691
692
693
694
695 if self.EXPORT_ANIMATION == 2 or self.EXPORT_MW_NIFXNIFKF:
696 self.logger.info("Creating keyframe tree")
697
698 node_kfctrls = {}
699 for node in root_block.tree():
700 if not isinstance(node, NifFormat.NiAVObject):
701 continue
702
703 ctrls = node.getControllers()
704 for ctrl in ctrls:
705 if self.EXPORT_VERSION == "Morrowind":
706
707 if not isinstance(ctrl,
708 NifFormat.NiKeyframeController):
709 continue
710 if not node in node_kfctrls:
711 node_kfctrls[node] = []
712 node_kfctrls[node].append(ctrl)
713
714 if self.EXPORT_VERSION == "Morrowind":
715
716 kf_root = self.createBlock("NiSequenceStreamHelper")
717 kf_root.addExtraData(anim_textextra)
718
719 for node, ctrls in node_kfctrls.iteritems():
720 for ctrl in ctrls:
721
722 nodename_extra = self.createBlock(
723 "NiStringExtraData")
724 nodename_extra.bytesRemaining = len(node.name) + 4
725 nodename_extra.stringData = node.name
726
727
728 ctrl.nextController = None
729
730
731 kf_root.addExtraData(nodename_extra)
732 kf_root.addController(ctrl)
733
734 ctrl.target = None
735
736 elif self.EXPORT_VERSION in ("Oblivion", "Fallout 3",
737 "Civilization IV", "Zoo Tycoon 2"):
738
739 kf_root = self.createBlock("NiControllerSequence")
740 if self.EXPORT_ANIMSEQUENCENAME:
741 kf_root.name = self.EXPORT_ANIMSEQUENCENAME
742 else:
743 kf_root.name = self.filebase
744 kf_root.unknownInt1 = 1
745 kf_root.weight = 1.0
746 kf_root.textKeys = anim_textextra
747 kf_root.cycleType = NifFormat.CycleType.CYCLE_CLAMP
748 kf_root.frequency = 1.0
749 kf_root.startTime =(self.fstart - 1) * self.fspeed
750 kf_root.stopTime = (self.fend - self.fstart) * self.fspeed
751
752 if "Bip01" in [node.name for
753 node in node_kfctrls.iterkeys()]:
754 targetname = "Bip01"
755 else:
756 targetname = root_block.name
757 kf_root.targetName = targetname
758 kf_root.stringPalette = NifFormat.NiStringPalette()
759 for node, ctrls \
760 in izip(node_kfctrls.iterkeys(),
761 node_kfctrls.itervalues()):
762
763
764 for ctrl in ctrls:
765 if isinstance(ctrl,
766 NifFormat.NiSingleInterpController):
767 interpolators = [ctrl.interpolator]
768 else:
769 interpolators = ctrl.interpolators
770 if isinstance(ctrl,
771 NifFormat.NiGeomMorpherController):
772 variable2s = [morph.frameName
773 for morph in ctrl.data.morphs]
774 else:
775 variable2s = [None
776 for interpolator in interpolators]
777 for interpolator, variable2 in izip(interpolators,
778 variable2s):
779
780
781 controlledblock = kf_root.addControlledBlock()
782 if self.version < 0x0A020000:
783
784
785 controlledblock.targetName = node.name
786 controlledblock.controller = ctrl
787
788 ctrl.target = None
789 else:
790
791
792 controlledblock.interpolator = interpolator
793
794
795
796 if not node in self.bonePriorities:
797 priority = 26
798 self.logger.warning("""\
799 No priority set for bone %s, falling back on default value (%i)"""
800 % (node.name, priority))
801 else:
802 priority = self.bonePriorities[node]
803 controlledblock.priority = priority
804
805
806 controlledblock.stringPalette = kf_root.stringPalette
807 controlledblock.setNodeName(node.name)
808 controlledblock.setControllerType(ctrl.__class__.__name__)
809 if variable2:
810 controlledblock.setVariable2(variable2)
811 else:
812 raise NifExportError("""\
813 Keyframe export for '%s' is not supported. Only Morrowind, Oblivion, Fallout 3,
814 Civilization IV, and Zoo Tycoon 2 keyframes are supported."""
815 % self.EXPORT_VERSION)
816
817
818 prefix = "" if not self.EXPORT_MW_NIFXNIFKF else "x"
819
820 ext = ".kf"
821 self.logger.info("Writing %s file" % (prefix + ext))
822 self.msg_progress("Writing %s file" % (prefix + ext))
823
824 self.filename = Blender.sys.join(self.filepath,
825 prefix + self.filebase + ext)
826 data = NifFormat.Data(version=self.version,
827 user_version=NIF_USER_VERSION,
828 user_version2=NIF_USER_VERSION2)
829 data.roots = [kf_root]
830 data.neosteam = (self.EXPORT_VERSION == "NeoSteam")
831 stream = open(self.filename, "wb")
832 try:
833 data.write(stream)
834 finally:
835 stream.close()
836
837 if self.EXPORT_MW_NIFXNIFKF:
838 self.logger.info("Detaching keyframe controllers from nif")
839
840 for node in root_block.tree():
841 if not isinstance(node, NifFormat.NiNode):
842 continue
843
844
845 while isinstance(node.controller, NifFormat.NiKeyframeController):
846 node.controller = node.controller.nextController
847 ctrl = node.controller
848 while ctrl:
849 if isinstance(ctrl.nextController,
850 NifFormat.NiKeyframeController):
851 ctrl.nextController = ctrl.nextController.nextController
852 else:
853 ctrl = ctrl.nextController
854
855 self.logger.info("Detaching animation text keys from nif")
856
857 if root_block.extraData is not anim_textextra:
858 raise RuntimeError("Oops, you found a bug! Animation extra data wasn't where expected...")
859 root_block.extraData = None
860
861 prefix = "x"
862 ext = ".nif"
863 self.logger.info("Writing %s file" % (prefix + ext))
864 self.msg_progress("Writing %s file" % (prefix + ext))
865
866 self.filename = Blender.sys.join(self.filepath,
867 prefix + self.filebase + ext)
868 data = NifFormat.Data(version=self.version,
869 user_version=NIF_USER_VERSION,
870 user_version2=NIF_USER_VERSION2)
871 data.roots = [root_block]
872 data.neosteam = (self.EXPORT_VERSION == "NeoSteam")
873 stream = open(self.filename, "wb")
874 try:
875 data.write(stream)
876 finally:
877 stream.close()
878
879
880
881
882 if self.egmdata:
883 ext = ".egm"
884 self.logger.info("Writing %s file" % ext)
885 self.msg_progress("Writing %s file" % ext)
886
887 self.filename = Blender.sys.join(self.filepath,
888 self.filebase + ext)
889 stream = open(self.filename, "wb")
890 try:
891 self.egmdata.write(stream)
892 finally:
893 stream.close()
894
895 except NifExportError, e:
896 e = str(e).replace("\n", " ")
897 Blender.Draw.PupMenu('EXPORT ERROR%t|' + str(e))
898 print 'NifExportError: ' + str(e)
899 return
900
901 except IOError, e:
902 e = str(e).replace("\n", " ")
903 Blender.Draw.PupMenu('I/O ERROR%t|' + str(e))
904 print 'IOError: ' + str(e)
905 return
906
907 except StandardError, e:
908 e = str(e).replace("\n", " ")
909 Blender.Draw.PupMenu('ERROR%t|' + str(e) + ' Check console for possibly more details.')
910 raise
911
912 finally:
913
914 self.msg_progress("Finished", progbar = 1)
915
916
917 self.root_blocks = [root_block]
918
919
920
921 - def exportNode(self, ob, space, parent_block, node_name):
922 """
923 Export a mesh/armature/empty object ob as child of parent_block.
924 Export also all children of ob.
925 - space is 'none', 'worldspace', or 'localspace', and determines
926 relative to what object the transformation should be stored.
927 - parent_block is the parent nif block of the object (None for the
928 root node)
929 - for the root node, ob is None, and node_name is usually the base
930 filename (either with or without extension)
931 """
932
933
934
935
936 if (ob == None):
937
938 assert(parent_block == None)
939 node = self.createBlock("NiNode")
940 ob_type = None
941 ob_ipo = None
942 else:
943
944 ob_type = ob.getType()
945 assert(ob_type in ['Empty', 'Mesh', 'Armature'])
946 assert(parent_block)
947 ob_ipo = ob.getIpo()
948 ob_children = [child for child in Blender.Object.Get() if child.parent == ob]
949
950 if (node_name == 'RootCollisionNode'):
951
952 ob.rbShapeBoundType = Blender.Object.RBShapes['POLYHEDERON']
953 ob.drawType = Blender.Object.DrawTypes['BOUNDBOX']
954 ob.drawMode = Blender.Object.DrawModes['WIRE']
955 self.exportCollision(ob, parent_block)
956 return None
957 elif ob_type == 'Mesh' and ob.name.lower()[:7] == 'bsbound':
958
959 self.exportBSBound(ob, parent_block)
960 return None
961 elif ob_type == 'Mesh':
962
963
964
965 is_collision = (ob.getDrawType()
966 == Blender.Object.DrawTypes['BOUNDBOX'])
967 has_ipo = ob_ipo and len(ob_ipo.getCurves()) > 0
968 has_children = len(ob_children) > 0
969 is_multimaterial = len(set([f.mat for f in ob.data.faces])) > 1
970
971 has_track = False
972 for constr in ob.constraints:
973 if constr.type == Blender.Constraint.Type.TRACKTO:
974 has_track = True
975 break
976 if is_collision:
977 self.exportCollision(ob, parent_block)
978 return None
979 elif has_ipo or has_children or is_multimaterial or has_track:
980
981 if not has_track:
982 node = self.createBlock('NiNode', ob)
983 else:
984 node = self.createBlock('NiBillboardNode', ob)
985 else:
986
987 self.exportTriShapes(ob, space, parent_block, node_name)
988
989 return None
990 else:
991
992 node = self.createBlock("NiNode", ob)
993
994
995
996 if ob_type == 'Mesh':
997 ob_parent = ob.getParent()
998 if ob_parent and ob_parent.getType() == 'Armature':
999 if ob_ipo:
1000
1001 self.logger.warn(
1002 "Mesh %s is skinned but also has object animation. "
1003 "The nif format does not support this: "
1004 "ignoring object animation." % ob.name)
1005 ob_ipo = None
1006 trishape_space = space
1007 space = 'none'
1008 else:
1009 trishape_space = 'none'
1010
1011
1012 if parent_block:
1013 parent_block.addChild(node)
1014
1015
1016 node.name = self.getFullName(node_name)
1017
1018
1019 if self.EXPORT_VERSION in ('Oblivion', 'Fallout 3'):
1020 node.flags = 0x000E
1021 elif self.EXPORT_VERSION in ("Sid Meier's Railroads",
1022 "Civilization IV"):
1023 node.flags = 0x0010
1024 else:
1025
1026 node.flags = 0x000C
1027
1028 self.exportMatrix(ob, space, node)
1029
1030 if (ob != None):
1031
1032 if (ob_ipo != None):
1033 self.exportKeyframes(ob_ipo, space, node)
1034
1035
1036 if (ob.getType() == 'Mesh'):
1037 self.exportTriShapes(ob, trishape_space, node)
1038
1039
1040 elif (ob.getType() == 'Armature'):
1041 self.exportBones(ob, node)
1042
1043
1044 self.exportChildren(ob, node)
1045
1046 return node
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094 - def exportKeyframes(self, ipo, space, parent_block, bind_mat = None,
1095 extra_mat_inv = None):
1096 if self.EXPORT_ANIMATION == 1 and self.version < 0x0A020000:
1097
1098
1099
1100 return
1101
1102
1103 assert(space == 'localspace')
1104
1105
1106 assert(isinstance(parent_block, NifFormat.NiNode))
1107
1108
1109
1110 if self.version < 0x0A020000:
1111 kfc = self.createBlock("NiKeyframeController", ipo)
1112 else:
1113 kfc = self.createBlock("NiTransformController", ipo)
1114 kfi = self.createBlock("NiTransformInterpolator", ipo)
1115
1116 kfc.interpolator = kfi
1117
1118 scale, quat, trans = \
1119 parent_block.getTransform().getScaleQuatTranslation()
1120 kfi.translation.x = trans.x
1121 kfi.translation.y = trans.y
1122 kfi.translation.z = trans.z
1123 kfi.rotation.x = quat.x
1124 kfi.rotation.y = quat.y
1125 kfi.rotation.z = quat.z
1126 kfi.rotation.w = quat.w
1127 kfi.scale = scale
1128
1129 parent_block.addController(kfc)
1130
1131
1132
1133 extend = None
1134 if ipo:
1135 for curve in ipo:
1136 if extend is None:
1137 extend = curve.extend
1138 elif extend != curve.extend:
1139 self.logger.warn(
1140 "Inconsistent extend type in %s, will use %s."
1141 % (ipo, extend))
1142 else:
1143
1144 extend = Blender.IpoCurve.ExtendTypes.CYCLIC
1145
1146
1147
1148
1149 kfc.flags = 8 + self.get_flags_from_extend(extend)
1150 kfc.frequency = 1.0
1151 kfc.phase = 0.0
1152 kfc.startTime = (self.fstart - 1) * self.fspeed
1153 kfc.stopTime = (self.fend - self.fstart) * self.fspeed
1154
1155 if self.EXPORT_ANIMATION == 1:
1156
1157 return
1158
1159
1160
1161
1162 if bind_mat:
1163 bind_scale, bind_rot, bind_trans = self.decomposeSRT(bind_mat)
1164 bind_quat = bind_rot.toQuat()
1165 else:
1166 bind_scale = 1.0
1167 bind_rot = Blender.Mathutils.Matrix([1,0,0],[0,1,0],[0,0,1])
1168 bind_quat = Blender.Mathutils.Quaternion(1,0,0,0)
1169 bind_trans = Blender.Mathutils.Vector(0,0,0)
1170 if extra_mat_inv:
1171 extra_scale_inv, extra_rot_inv, extra_trans_inv = \
1172 self.decomposeSRT(extra_mat_inv)
1173 extra_quat_inv = extra_rot_inv.toQuat()
1174 else:
1175 extra_scale_inv = 1.0
1176 extra_rot_inv = Blender.Mathutils.Matrix([1,0,0],[0,1,0],[0,0,1])
1177 extra_quat_inv = Blender.Mathutils.Quaternion(1,0,0,0)
1178 extra_trans_inv = Blender.Mathutils.Vector(0,0,0)
1179
1180
1181 if (ipo == None):
1182 scale_curve = {}
1183 rot_curve = {}
1184 trans_curve = {}
1185
1186 else:
1187
1188 scale_curve = {}
1189 rot_curve = {}
1190 trans_curve = {}
1191
1192 assert(Ipo.PO_SCALEX == Ipo.OB_SCALEX)
1193 assert(Ipo.PO_LOCX == Ipo.OB_LOCX)
1194
1195 for curvecollection in (
1196 (Ipo.PO_SCALEX, Ipo.PO_SCALEY, Ipo.PO_SCALEZ),
1197 (Ipo.PO_LOCX, Ipo.PO_LOCY, Ipo.PO_LOCZ),
1198 (Ipo.PO_QUATX, Ipo.PO_QUATY, Ipo.PO_QUATZ, Ipo.PO_QUATW),
1199 (Ipo.OB_ROTX, Ipo.OB_ROTY, Ipo.OB_ROTZ)):
1200
1201 try:
1202 ipo[curvecollection[0]]
1203 except KeyError:
1204 continue
1205
1206
1207 if (any(ipo[curve] for curve in curvecollection)
1208 and not all(ipo[curve] for curve in curvecollection)):
1209 keytype = {Ipo.PO_SCALEX: "SCALE",
1210 Ipo.PO_LOCX: "LOC",
1211 Ipo.PO_QUATX: "ROT",
1212 Ipo.OB_ROTX: "ROT"}
1213 raise NifExportError("""\
1214 missing curves in %s; insert %s key at frame 1 and try again"""
1215 % (ipo, keytype[curvecollection[0]]))
1216
1217 ipo_curves = ipo.curveConsts.values()
1218 for curve in ipo_curves:
1219
1220 if ipo[curve] is None:
1221 continue
1222
1223 for btriple in ipo[curve].bezierPoints:
1224 frame = btriple.pt[0]
1225 if (frame < self.fstart) or (frame > self.fend):
1226 continue
1227
1228
1229 if curve in (Ipo.PO_SCALEX, Ipo.PO_SCALEY, Ipo.PO_SCALEZ):
1230
1231 scale_curve[frame] = (ipo[Ipo.PO_SCALEX][frame]
1232 + ipo[Ipo.PO_SCALEY][frame]
1233 + ipo[Ipo.PO_SCALEZ][frame]) / 3.0
1234
1235 scale_curve[frame] = \
1236 scale_curve[frame] * bind_scale * extra_scale_inv
1237
1238 elif curve in (Ipo.OB_ROTX, Ipo.OB_ROTY, Ipo.OB_ROTZ):
1239 rot_curve[frame] = Blender.Mathutils.Euler(
1240 [10 * ipo[Ipo.OB_ROTX][frame],
1241 10 * ipo[Ipo.OB_ROTY][frame],
1242 10 * ipo[Ipo.OB_ROTZ][frame]])
1243
1244
1245 if bind_mat or extra_mat_inv:
1246 rot_curve[frame] = rot_curve[frame].toQuat()
1247
1248
1249 rot_curve[frame] = Blender.Mathutils.CrossQuats(Blender.Mathutils.CrossQuats(bind_quat, rot_curve[frame]), extra_quat_inv)
1250
1251 elif curve in (Ipo.PO_QUATX, Ipo.PO_QUATY,
1252 Ipo.PO_QUATZ, Ipo.PO_QUATW):
1253 rot_curve[frame] = Blender.Mathutils.Quaternion()
1254 rot_curve[frame].x = ipo[Ipo.PO_QUATX][frame]
1255 rot_curve[frame].y = ipo[Ipo.PO_QUATY][frame]
1256 rot_curve[frame].z = ipo[Ipo.PO_QUATZ][frame]
1257 rot_curve[frame].w = ipo[Ipo.PO_QUATW][frame]
1258
1259
1260 rot_curve[frame] = Blender.Mathutils.CrossQuats(Blender.Mathutils.CrossQuats(bind_quat, rot_curve[frame]), extra_quat_inv)
1261
1262
1263 elif curve in (Ipo.PO_LOCX, Ipo.PO_LOCY, Ipo.PO_LOCZ):
1264 trans_curve[frame] = Blender.Mathutils.Vector(
1265 [ipo[Ipo.PO_LOCX][frame],
1266 ipo[Ipo.PO_LOCY][frame],
1267 ipo[Ipo.PO_LOCZ][frame]])
1268
1269 trans_curve[frame] *= bind_scale
1270 trans_curve[frame] *= bind_rot
1271 trans_curve[frame] += bind_trans
1272
1273 if Ipo.OB_ROTX in ipo_curves and ipo[Ipo.OB_ROTX]:
1274 rot_c = Blender.Mathutils.Euler(
1275 [10 * ipo[Ipo.OB_ROTX][frame],
1276 10 * ipo[Ipo.OB_ROTY][frame],
1277 10 * ipo[Ipo.OB_ROTZ][frame]]).toMatrix()
1278 elif Ipo.PO_QUATX in ipo_curves and ipo[Ipo.PO_QUATX]:
1279 rot_c = Blender.Mathutils.Quaternion()
1280 rot_c.x = ipo[Ipo.PO_QUATX][frame]
1281 rot_c.y = ipo[Ipo.PO_QUATY][frame]
1282 rot_c.z = ipo[Ipo.PO_QUATZ][frame]
1283 rot_c.w = ipo[Ipo.PO_QUATW][frame]
1284 rot_c = rot_c.toMatrix()
1285 else:
1286 rot_c = Blender.Mathutils.Matrix([1,0,0],[0,1,0],[0,0,1])
1287
1288 if ipo[Ipo.PO_SCALEX]:
1289
1290 scale_c = (ipo[Ipo.PO_SCALEX][frame]
1291 + ipo[Ipo.PO_SCALEY][frame]
1292 + ipo[Ipo.PO_SCALEZ][frame]) / 3.0
1293 else:
1294 scale_c = 1.0
1295 trans_curve[frame] += \
1296 extra_trans_inv * rot_c * bind_rot * \
1297 scale_c * bind_scale
1298
1299
1300
1301 if (max(len(rot_curve), len(trans_curve), len(scale_curve)) <= 1
1302 and self.version >= 0x0A020000):
1303
1304
1305
1306
1307 if trans_curve:
1308 trans = trans_curve.values()[0]
1309 kfi.translation.x = trans[0]
1310 kfi.translation.y = trans[1]
1311 kfi.translation.z = trans[2]
1312 if rot_curve:
1313 rot = rot_curve.values()[0]
1314
1315 if isinstance(rot, Blender.Mathutils.Euler().__class__):
1316 rot = rot.toQuat()
1317 kfi.rotation.x = rot.x
1318 kfi.rotation.y = rot.y
1319 kfi.rotation.z = rot.z
1320 kfi.rotation.w = rot.w
1321
1322 kfi.scale = 1.0
1323
1324 return
1325
1326
1327 if self.version < 0x0A020000:
1328 kfd = self.createBlock("NiKeyframeData", ipo)
1329 kfc.data = kfd
1330 else:
1331
1332 kfd = self.createBlock("NiTransformData", ipo)
1333 kfi.data = kfd
1334
1335 frames = rot_curve.keys()
1336 frames.sort()
1337
1338 if (frames
1339 and isinstance(rot_curve.values()[0],
1340 Blender.Mathutils.Euler().__class__)):
1341
1342 kfd.rotationType = NifFormat.KeyType.XYZ_ROTATION_KEY
1343 kfd.numRotationKeys = 1
1344 kfd.xyzRotations[0].numKeys = len(frames)
1345 kfd.xyzRotations[1].numKeys = len(frames)
1346 kfd.xyzRotations[2].numKeys = len(frames)
1347
1348 kfd.xyzRotations[0].interpolation = NifFormat.KeyType.LINEAR_KEY
1349 kfd.xyzRotations[1].interpolation = NifFormat.KeyType.LINEAR_KEY
1350 kfd.xyzRotations[2].interpolation = NifFormat.KeyType.LINEAR_KEY
1351 kfd.xyzRotations[0].keys.updateSize()
1352 kfd.xyzRotations[1].keys.updateSize()
1353 kfd.xyzRotations[2].keys.updateSize()
1354 for i, frame in enumerate(frames):
1355
1356 rot_frame_x = kfd.xyzRotations[0].keys[i]
1357 rot_frame_y = kfd.xyzRotations[1].keys[i]
1358 rot_frame_z = kfd.xyzRotations[2].keys[i]
1359 rot_frame_x.time = (frame - 1) * self.fspeed
1360 rot_frame_y.time = (frame - 1) * self.fspeed
1361 rot_frame_z.time = (frame - 1) * self.fspeed
1362 rot_frame_x.value = rot_curve[frame].x * 3.14159265358979323846 / 180.0
1363 rot_frame_y.value = rot_curve[frame].y * 3.14159265358979323846 / 180.0
1364 rot_frame_z.value = rot_curve[frame].z * 3.14159265358979323846 / 180.0
1365 else:
1366
1367
1368 kfd.rotationType = NifFormat.KeyType.LINEAR_KEY
1369 kfd.numRotationKeys = len(frames)
1370 kfd.quaternionKeys.updateSize()
1371 for i, frame in enumerate(frames):
1372 rot_frame = kfd.quaternionKeys[i]
1373 rot_frame.time = (frame - 1) * self.fspeed
1374 rot_frame.value.w = rot_curve[frame].w
1375 rot_frame.value.x = rot_curve[frame].x
1376 rot_frame.value.y = rot_curve[frame].y
1377 rot_frame.value.z = rot_curve[frame].z
1378
1379 frames = trans_curve.keys()
1380 frames.sort()
1381 kfd.translations.interpolation = NifFormat.KeyType.LINEAR_KEY
1382 kfd.translations.numKeys = len(frames)
1383 kfd.translations.keys.updateSize()
1384 for i, frame in enumerate(frames):
1385 trans_frame = kfd.translations.keys[i]
1386 trans_frame.time = (frame - 1) * self.fspeed
1387 trans_frame.value.x = trans_curve[frame][0]
1388 trans_frame.value.y = trans_curve[frame][1]
1389 trans_frame.value.z = trans_curve[frame][2]
1390
1391 frames = scale_curve.keys()
1392 frames.sort()
1393 kfd.scales.interpolation = NifFormat.KeyType.LINEAR_KEY
1394 kfd.scales.numKeys = len(frames)
1395 kfd.scales.keys.updateSize()
1396 for i, frame in enumerate(frames):
1397 scale_frame = kfd.scales.keys[i]
1398 scale_frame.time = (frame - 1) * self.fspeed
1399 scale_frame.value = scale_curve[frame]
1400
1404 """Create a vertex color property, and attach it to an existing block
1405 (typically, the root of the nif tree).
1406
1407 @param block_parent: The block to which to attach the new property.
1408 @param flags: The C{flags} of the new property.
1409 @param vertex_mode: The C{vertexMode} of the new property.
1410 @param lighting_mode: The C{lightingMode} of the new property.
1411 @return: The new property block.
1412 """
1413
1414 vcolprop = self.createBlock("NiVertexColorProperty")
1415
1416
1417 block_parent.addProperty(vcolprop)
1418
1419
1420 vcolprop.flags = flags
1421 vcolprop.vertexMode = vertex_mode
1422 vcolprop.lightingMode = lighting_mode
1423
1424 return vcolprop
1425
1428 """Create a z-buffer property, and attach it to an existing block
1429 (typically, the root of the nif tree).
1430
1431 @param block_parent: The block to which to attach the new property.
1432 @param flags: The C{flags} of the new property.
1433 @param function: The C{function} of the new property.
1434 @return: The new property block.
1435 """
1436
1437 zbuf = self.createBlock("NiZBufferProperty")
1438
1439
1440 block_parent.addProperty(zbuf)
1441
1442
1443 zbuf.flags = 15
1444 zbuf.function = 3
1445
1446 return zbuf
1447
1449 """Parse the animation groups buffer and write an extra string
1450 data block, and attach it to an existing block (typically, the root
1451 of the nif tree)."""
1452 if self.EXPORT_ANIMATION == 1:
1453
1454 return
1455
1456 self.logger.info("Exporting animation groups")
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469 slist = animtxt.asLines()
1470 flist = []
1471 dlist = []
1472 for s in slist:
1473
1474 if not s:
1475 continue
1476
1477 t = s.split('/')
1478 if (len(t) < 2):
1479 raise NifExportError("Syntax error in Anim buffer ('%s')" % s)
1480 f = int(t[0])
1481 if ((f < self.fstart) or (f > self.fend)):
1482 self.logger.warn("frame in animation buffer out of range "
1483 "(%i not in [%i, %i])"
1484 % (f, self.fstart, self.fend))
1485 d = t[1].strip(' ')
1486 for i in range(2, len(t)):
1487 d = d + '\r\n' + t[i].strip(' ')
1488
1489 flist.append(f)
1490 dlist.append(d)
1491
1492
1493
1494
1495
1496 textextra = self.createBlock("NiTextKeyExtraData", animtxt)
1497 block_parent.addExtraData(textextra)
1498
1499
1500 textextra.numTextKeys = len(flist)
1501 textextra.textKeys.updateSize()
1502 for i, key in enumerate(textextra.textKeys):
1503 key.time = self.fspeed * (flist[i]-1)
1504 key.value = dlist[i]
1505
1506 return textextra
1507
1508 - def exportTextureFilename(self, texture):
1509 """Returns file name from texture.
1510
1511 @param texture: The texture object in blender.
1512 @return: The file name of the image used in the texture.
1513 """
1514 if texture.type == Blender.Texture.Types.ENVMAP:
1515
1516 if self.EXPORT_VERSION != "Morrowind":
1517 raise NifExportError(
1518 "cannot export environment maps for nif version '%s'"%
1519 self.EXPORT_VERSION)
1520 return "enviro 01.TGA"
1521 elif texture.type == Blender.Texture.Types.IMAGE:
1522
1523
1524
1525 if texture.getImage() is None:
1526 raise NifExportError(
1527 "image type texture has no file loaded ('%s')"
1528 % texture.getName())
1529
1530 filename = texture.image.getFilename()
1531
1532
1533 if texture.getImage().packed:
1534 self.logger.warn(
1535 "Packed image in texture '%s' ignored, "
1536 "exporting as '%s' instead."
1537 % (texture.getName(), filename))
1538
1539
1540 ddsfilename = "%s%s" % (filename[:-4], '.dds')
1541 if Blender.sys.exists(ddsfilename) or self.EXPORT_FORCEDDS:
1542 filename = ddsfilename
1543
1544
1545 if not self.EXPORT_VERSION in ('Morrowind', 'Oblivion',
1546 'Fallout 3'):
1547
1548 filename = Blender.sys.basename(filename)
1549 else:
1550
1551 filename = filename.lower()
1552 idx = filename.find("textures")
1553 if ( idx >= 0 ):
1554 filename = filename[idx:]
1555 else:
1556 self.logger.warn("%s does not reside in a 'Textures' folder; texture path will be stripped and textures may not display in-game" % filename)
1557 filename = Blender.sys.basename(filename)
1558
1559 return filename.replace('/', '\\')
1560 else:
1561
1562 raise NifExportError(
1563 "Error: Texture '%s' must be of type IMAGE or ENVMAP"
1564 % texture.getName())
1565
1566 - def exportSourceTexture(self, texture=None, filename=None):
1567 """Export a NiSourceTexture.
1568
1569 @param texture: The texture object in blender to be exported.
1570 @param filename: The full or relative path to the texture file
1571 (this argument is used when exporting NiFlipControllers
1572 and when exporting default shader slots that have no use in
1573 being imported into Blender).
1574 @return: The exported NiSourceTexture block."""
1575
1576
1577 srctex = NifFormat.NiSourceTexture()
1578 srctex.useExternal = True
1579 if not filename is None:
1580
1581 srctex.fileName = filename
1582 elif not texture is None:
1583 srctex.fileName = self.exportTextureFilename(texture)
1584 else:
1585
1586 logger.warning(
1587 "Exporting source texture without texture or filename (bug?).")
1588
1589
1590 if self.version >= 0x0a000100:
1591 srctex.pixelLayout = 6
1592 else:
1593 srctex.pixelLayout = 5
1594 srctex.useMipmaps = 1
1595 srctex.alphaFormat = 3
1596 srctex.unknownByte = 1
1597
1598
1599 for block in self.blocks:
1600 if isinstance(block, NifFormat.NiSourceTexture) and block.getHash() == srctex.getHash():
1601 return block
1602
1603
1604
1605 return self.registerBlock(srctex, texture)
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1621 tlist = fliptxt.asLines()
1622
1623
1624 flip = self.createBlock("NiFlipController", fliptxt)
1625 target.addController(flip)
1626
1627
1628 flip.flags = 0x0008
1629 flip.frequency = 1.0
1630 flip.startTime = (self.fstart - 1) * self.fspeed
1631 flip.stopTime = ( self.fend - self.fstart ) * self.fspeed
1632 flip.textureSlot = target_tex
1633 count = 0
1634 for t in tlist:
1635 if len( t ) == 0: continue
1636
1637 tex = self.exportSourceTexture(texture, t)
1638 flip.numSources += 1
1639 flip.sources.updateSize()
1640 flip.sources[flip.numSources-1] = tex
1641 count += 1
1642 if count < 2:
1643 raise NifExportError("Error in Texture Flip buffer '%s': Must define at least two textures"%fliptxt.getName())
1644 flip.delta = (flip.stopTime - flip.startTime) / count
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658 - def exportTriShapes(self, ob, space, parent_block, trishape_name = None):
1659 self.logger.info("Exporting %s" % ob)
1660 self.msg_progress("Exporting %s" % ob.name)
1661 assert(ob.getType() == 'Mesh')
1662
1663
1664 mesh = ob.getData(mesh=1)
1665
1666
1667
1668
1669 if len(ob.data.verts) == 0:
1670
1671 self.logger.warn("%s has no vertices, skipped." % ob)
1672 return
1673
1674
1675 if not isinstance(parent_block, NifFormat.RootCollisionNode):
1676 mesh_mats = mesh.materials
1677 else:
1678
1679 mesh_mats = []
1680
1681 if (mesh_mats == []):
1682 mesh_mats = [None]
1683
1684
1685 mesh_doublesided = (mesh.mode & Blender.Mesh.Modes.TWOSIDED)
1686
1687
1688
1689
1690 for materialIndex, mesh_mat in enumerate(mesh_mats):
1691
1692
1693 mesh_base_mtex = None
1694 mesh_glow_mtex = None
1695 mesh_bump_mtex = None
1696 mesh_gloss_mtex = None
1697 mesh_dark_mtex = None
1698 mesh_detail_mtex = None
1699 mesh_texeff_mtex = None
1700 mesh_ref_mtex = None
1701 mesh_uvlayers = []
1702 mesh_hasalpha = False
1703 mesh_haswire = False
1704 mesh_hasspec = False
1705 mesh_hasvcol = False
1706 mesh_hasnormals = False
1707 if (mesh_mat != None):
1708 mesh_hasnormals = True
1709
1710
1711
1712 mesh_hasvcol = mesh.vertexColors
1713
1714 mesh_mat_ambient = mesh_mat.getAmb()
1715 mesh_mat_diffuse_color = mesh_mat.getRGBCol()
1716 mesh_mat_specular_color = mesh_mat.getSpecCol()
1717 specval = mesh_mat.getSpec()
1718 mesh_mat_specular_color[0] *= specval
1719 mesh_mat_specular_color[1] *= specval
1720 mesh_mat_specular_color[2] *= specval
1721 if mesh_mat_specular_color[0] > 1.0: mesh_mat_specular_color[0] = 1.0
1722 if mesh_mat_specular_color[1] > 1.0: mesh_mat_specular_color[1] = 1.0
1723 if mesh_mat_specular_color[2] > 1.0: mesh_mat_specular_color[2] = 1.0
1724 if ( mesh_mat_specular_color[0] > self.EPSILON ) \
1725 or ( mesh_mat_specular_color[1] > self.EPSILON ) \
1726 or ( mesh_mat_specular_color[2] > self.EPSILON ):
1727 mesh_hasspec = True
1728 mesh_mat_emissive = mesh_mat.getEmit()
1729 mesh_mat_glossiness = mesh_mat.getHardness() / 4.0
1730 mesh_mat_transparency = mesh_mat.getAlpha()
1731 mesh_hasalpha = (abs(mesh_mat_transparency - 1.0) > self.EPSILON) \
1732 or (mesh_mat.getIpo() != None
1733 and mesh_mat.getIpo().getCurve('Alpha'))
1734 mesh_haswire = mesh_mat.mode & Blender.Material.Modes.WIRE
1735 mesh_mat_ambient_color = [0.0, 0.0, 0.0]
1736 mesh_mat_ambient_color[0] = mesh_mat_diffuse_color[0] * mesh_mat_ambient
1737 mesh_mat_ambient_color[1] = mesh_mat_diffuse_color[1] * mesh_mat_ambient
1738 mesh_mat_ambient_color[2] = mesh_mat_diffuse_color[2] * mesh_mat_ambient
1739 mesh_mat_emissive_color = [0.0, 0.0, 0.0]
1740 mesh_mat_emitmulti = 1.0
1741 if self.EXPORT_VERSION != "Fallout 3":
1742 mesh_mat_emissive_color[0] = mesh_mat_diffuse_color[0] * mesh_mat_emissive
1743 mesh_mat_emissive_color[1] = mesh_mat_diffuse_color[1] * mesh_mat_emissive
1744 mesh_mat_emissive_color[2] = mesh_mat_diffuse_color[2] * mesh_mat_emissive
1745 else:
1746
1747
1748
1749 if mesh_mat.emit > self.EPSILON:
1750 mesh_mat_emissive_color = mesh_mat_diffuse_color
1751 mesh_mat_emitmulti = mesh_mat.emit * 10.0
1752
1753
1754 for mtex in mesh_mat.getTextures():
1755 if not mtex:
1756
1757 continue
1758
1759
1760
1761 if mtex.texco == Blender.Texture.TexCo.REFL:
1762
1763
1764
1765
1766
1767 if (mtex.mapto & Blender.Texture.MapTo.COL) == 0:
1768
1769 raise NifExportError("Non-COL-mapped reflection \
1770 texture in mesh '%s', material '%s', these cannot be exported to NIF. Either \
1771 delete all non-COL-mapped reflection textures, or in the Shading Panel, under \
1772 Material Buttons, set texture 'Map To' to \
1773 'COL'." % (ob.getName(),mesh_mat.getName()))
1774 if mtex.blendmode != Blender.Texture.BlendModes["ADD"]:
1775
1776 self.logger.warn("Reflection texture should \
1777 have blending mode 'Add' on texture in \
1778 mesh '%s', material '%s')."%(ob.getName(),mesh_mat.getName()))
1779
1780 mesh_texeff_mtex = mtex
1781
1782
1783 elif mtex.texco == Blender.Texture.TexCo.UV:
1784
1785 uvlayer = ( mtex.uvlayer if mtex.uvlayer
1786 else mesh.activeUVLayer )
1787 if not uvlayer in mesh_uvlayers:
1788 mesh_uvlayers.append(uvlayer)
1789
1790 if mtex.mapto & Blender.Texture.MapTo.EMIT:
1791
1792 if mesh_glow_mtex:
1793 raise NifExportError("Multiple glow textures \
1794 in mesh '%s', material '%s'. Make sure there is only one texture with \
1795 MapTo.EMIT"%(mesh.name,mesh_mat.getName()))
1796
1797
1798 if (mtex.tex.imageFlags & Blender.Texture.ImageFlags.CALCALPHA != 0) \
1799 and (mtex.mapto & Blender.Texture.MapTo.ALPHA != 0):
1800 self.logger.warn("In mesh '%s', material \
1801 '%s': glow texture must have CALCALPHA flag set, and must have MapTo.ALPHA \
1802 enabled."%(ob.getName(),mesh_mat.getName()))
1803 mesh_glow_mtex = mtex
1804 elif mtex.mapto & Blender.Texture.MapTo.SPEC:
1805
1806 if mesh_gloss_mtex:
1807 raise NifExportError("Multiple gloss textures \
1808 in mesh '%s', material '%s'. Make sure there is only one texture with \
1809 MapTo.SPEC"%(mesh.name,mesh_mat.getName()))
1810 mesh_gloss_mtex = mtex
1811 elif mtex.mapto & Blender.Texture.MapTo.NOR:
1812
1813 if mesh_bump_mtex:
1814 raise NifExportError("Multiple bump/normal textures \
1815 in mesh '%s', material '%s'. Make sure there is only one texture with \
1816 MapTo.NOR"%(mesh.name,mesh_mat.getName()))
1817 mesh_bump_mtex = mtex
1818 elif mtex.mapto & Blender.Texture.MapTo.COL and \
1819 mtex.blendmode == Blender.Texture.BlendModes["DARKEN"] and \
1820 not mesh_dark_mtex:
1821
1822 mesh_dark_mtex = mtex
1823 elif mtex.mapto & Blender.Texture.MapTo.COL and \
1824 not mesh_base_mtex:
1825
1826
1827 mesh_base_mtex = mtex
1828
1829 if (mesh_base_mtex.tex.imageFlags & Blender.Texture.ImageFlags.USEALPHA != 0) and (mtex.mapto & Blender.Texture.MapTo.ALPHA != 0):
1830
1831
1832
1833
1834
1835 if mesh_mat_transparency > self.EPSILON:
1836 raise NifExportError("Cannot export this \
1837 type of transparency in material '%s': instead, try to set alpha to 0.0 and to \
1838 use the 'Var' slider in the 'Map To' tab under the material \
1839 buttons."%mesh_mat.getName())
1840 if (mesh_mat.getIpo() and mesh_mat.getIpo().getCurve('Alpha')):
1841 raise NifExportError("Cannot export \
1842 animation for this type of transparency in material '%s': remove alpha \
1843 animation, or turn off MapTo.ALPHA, and try again."%mesh_mat.getName())
1844 mesh_mat_transparency = mtex.varfac
1845 mesh_hasalpha = True
1846 elif mtex.mapto & Blender.Texture.MapTo.COL and \
1847 not mesh_detail_mtex:
1848
1849
1850 mesh_detail_mtex = mtex
1851 elif mtex.mapto & Blender.Texture.MapTo.REF:
1852
1853 if mesh_ref_mtex:
1854 raise NifExportError("Multiple reflection textures \
1855 in mesh '%s', material '%s'. Make sure there is only one texture with \
1856 MapTo.REF"%(mesh.name,mesh_mat.getName()))
1857 mesh_ref_mtex = mtex
1858 else:
1859
1860 raise NifExportError("Do not know how to export \
1861 texture '%s', in mesh '%s', material '%s'. Either delete it, or if this \
1862 texture is to be your base texture, go to the Shading Panel, Material Buttons, \
1863 and set texture 'Map To' to 'COL'." % (mtex.tex.getName(),
1864 ob.getName(),
1865 mesh_mat.getName()))
1866 else:
1867
1868 raise NifExportError("Non-UV texture in mesh '%s', \
1869 material '%s'. Either delete all non-UV textures, or in the Shading Panel, \
1870 under Material Buttons, set texture 'Map Input' to 'UV'."%
1871 (ob.getName(),mesh_mat.getName()))
1872
1873
1874
1875 bodypartgroups = []
1876 for bodypartgroupname in NifFormat.BSDismemberBodyPartType().getEditorKeys():
1877 if bodypartgroupname in ob.data.getVertGroupNames():
1878 self.logger.debug("Found body part %s"
1879 % bodypartgroupname)
1880 bodypartgroups.append(
1881 [bodypartgroupname,
1882 getattr(NifFormat.BSDismemberBodyPartType,
1883 bodypartgroupname),
1884 set(ob.data.getVertsFromGroup(bodypartgroupname))])
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906 vertquad_list = []
1907 vertmap = [None for i in xrange(len(mesh.verts))]
1908 vertlist = []
1909 normlist = []
1910 vcollist = []
1911 uvlist = []
1912 trilist = []
1913
1914 bodypartfacemap = []
1915 faces_without_bodypart = []
1916 for f in mesh.faces:
1917
1918 if (mesh_mat != None):
1919 if (f.mat != materialIndex):
1920 continue
1921 f_numverts = len(f.v)
1922 if (f_numverts < 3): continue
1923 assert((f_numverts == 3) or (f_numverts == 4))
1924 if mesh_uvlayers:
1925
1926
1927 if not mesh.faceUV or len(f.uv) != len(f.v):
1928 raise NifExportError(
1929 'ERROR%t|Create a UV map for every texture, and run the script again.')
1930
1931 f_index = [ -1 ] * f_numverts
1932 for i in range(f_numverts):
1933 fv = f.v[i].co
1934
1935 if mesh_hasnormals:
1936 if f.smooth:
1937 fn = f.v[i].no
1938 else:
1939 fn = f.no
1940 else:
1941 fn = None
1942 fuv = []
1943 for uvlayer in mesh_uvlayers:
1944 mesh.activeUVLayer = uvlayer
1945 fuv.append(f.uv[i])
1946 if mesh_hasvcol:
1947 if (len(f.col) == 0):
1948 self.logger.warning('Vertex color painting/lighting enabled, but mesh has no vertex color data; vertex colors will not be written.')
1949 fcol = None
1950 mesh_hasvcol = False
1951 else:
1952
1953 fcol = f.col[i]
1954 else:
1955 fcol = None
1956
1957 vertquad = ( fv, fuv, fn, fcol )
1958
1959
1960 f_index[i] = len(vertquad_list)
1961 v_index = f.v[i].index
1962 if vertmap[v_index]:
1963
1964
1965 for j in vertmap[v_index]:
1966 if mesh_uvlayers:
1967 if max(abs(vertquad[1][uvlayer][0]
1968 - vertquad_list[j][1][uvlayer][0])
1969 for uvlayer
1970 in xrange(len(mesh_uvlayers))) \
1971 > self.EPSILON:
1972 continue
1973 if max(abs(vertquad[1][uvlayer][1]
1974 - vertquad_list[j][1][uvlayer][1])
1975 for uvlayer
1976 in xrange(len(mesh_uvlayers))) \
1977 > self.EPSILON:
1978 continue
1979 if mesh_hasnormals:
1980 if abs(vertquad[2][0] - vertquad_list[j][2][0]) > self.EPSILON: continue
1981 if abs(vertquad[2][1] - vertquad_list[j][2][1]) > self.EPSILON: continue
1982 if abs(vertquad[2][2] - vertquad_list[j][2][2]) > self.EPSILON: continue
1983 if mesh_hasvcol:
1984 if abs(vertquad[3].r - vertquad_list[j][3].r) > self.EPSILON: continue
1985 if abs(vertquad[3].g - vertquad_list[j][3].g) > self.EPSILON: continue
1986 if abs(vertquad[3].b - vertquad_list[j][3].b) > self.EPSILON: continue
1987 if abs(vertquad[3].a - vertquad_list[j][3].a) > self.EPSILON: continue
1988
1989 f_index[i] = j
1990 break
1991
1992 if f_index[i] > 65535:
1993 raise NifExportError('ERROR%t|Too many vertices. Decimate your mesh and try again.')
1994 if (f_index[i] == len(vertquad_list)):
1995
1996 if not vertmap[v_index]:
1997 vertmap[v_index] = []
1998 vertmap[v_index].append(len(vertquad_list))
1999
2000 vertquad_list.append(vertquad)
2001
2002 vertlist.append(vertquad[0])
2003 if mesh_hasnormals: normlist.append(vertquad[2])
2004 if mesh_hasvcol: vcollist.append(vertquad[3])
2005 if mesh_uvlayers: uvlist.append(vertquad[1])
2006
2007 for i in range(f_numverts - 2):
2008 if True:
2009 f_indexed = (f_index[0], f_index[1+i], f_index[2+i])
2010 else:
2011 f_indexed = (f_index[0], f_index[2+i], f_index[1+i])
2012 trilist.append(f_indexed)
2013
2014 if (self.EXPORT_VERSION != "Fallout 3"
2015 or not bodypartgroups
2016 or not self.EXPORT_FO3_BODYPARTS):
2017 bodypartfacemap.append(0)
2018 else:
2019 for bodypartname, bodypartindex, bodypartverts in bodypartgroups:
2020 if (set(b_vert.index for b_vert in f.verts)
2021 <= bodypartverts):
2022 bodypartfacemap.append(bodypartindex)
2023 break
2024 else:
2025
2026 faces_without_bodypart.append(f)
2027
2028
2029 if faces_without_bodypart:
2030 Blender.Window.EditMode(0)
2031
2032 for bobj in self.scene.objects:
2033 bobj.sel = False
2034 self.scene.objects.active = ob
2035 ob.sel = 1
2036
2037 for face in mesh.faces:
2038 face.sel = 0
2039 for face in faces_without_bodypart:
2040 face.sel = 1
2041
2042 Blender.Window.EditMode(1)
2043 raise ValueError(
2044 "Some faces of %s not assigned to any body part. \
2045 The unassigned faces have been selected in the mesh so \
2046 they can easily be identified." % ob)
2047
2048 if len(trilist) > 65535:
2049 raise NifExportError(
2050 'ERROR%t|Too many faces. Decimate your mesh and try again.')
2051 if len(vertlist) == 0:
2052 continue
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062 if not self.EXPORT_STRIPIFY:
2063 trishape = self.createBlock("NiTriShape", ob)
2064 else:
2065 trishape = self.createBlock("NiTriStrips", ob)
2066
2067
2068
2069 if self.EXPORT_VERSION == "Morrowind" and mesh_texeff_mtex:
2070
2071 extra_node = self.createBlock("NiNode", mesh_texeff_mtex)
2072 parent_block.addChild(extra_node)
2073
2074 extra_node.rotation.setIdentity()
2075 extra_node.scale = 1.0
2076 extra_node.flags = 0x000C
2077
2078
2079 texeff = self.exportTextureEffect(mesh_texeff_mtex)
2080 extra_node.addChild(texeff)
2081 extra_node.addChild(trishape)
2082 extra_node.addEffect(texeff)
2083 else:
2084
2085
2086 parent_block.addChild(trishape)
2087
2088
2089 if isinstance(parent_block, NifFormat.RootCollisionNode):
2090 trishape.name = ""
2091 elif not trishape_name:
2092 if parent_block.name:
2093 trishape.name = "Tri " + parent_block.name
2094 else:
2095 trishape.name = "Tri " + ob.getName()
2096 else:
2097 trishape.name = trishape_name
2098 if len(mesh_mats) > 1:
2099
2100
2101 trishape.name += " %i"%materialIndex
2102 trishape.name = self.getFullName(trishape.name)
2103 if self.EXPORT_VERSION in ('Oblivion', 'Fallout 3'):
2104 trishape.flags = 0x000E
2105 elif self.EXPORT_VERSION in ("Sid Meier's Railroads",
2106 "Civilization IV"):
2107 trishape.flags = 0x0010
2108 else:
2109
2110 if ob.getDrawType() != 2:
2111 trishape.flags = 0x0004
2112 else:
2113 trishape.flags = 0x0005
2114
2115
2116 if self.EXPORT_VERSION == "Sid Meier's Railroads":
2117 trishape.hasShader = True
2118 trishape.shaderName = "RRT_NormalMap_Spec_Env_CubeLight"
2119 trishape.unknownInteger = -1
2120
2121 self.exportMatrix(ob, space, trishape)
2122
2123 if mesh_base_mtex or mesh_glow_mtex:
2124
2125 if self.EXPORT_VERSION == "Fallout 3":
2126 trishape.addProperty(self.exportBSShaderProperty(
2127 basemtex = mesh_base_mtex,
2128 glowmtex = mesh_glow_mtex,
2129 bumpmtex = mesh_bump_mtex))
2130
2131
2132
2133 else:
2134 if (self.EXPORT_VERSION in self.USED_EXTRA_SHADER_TEXTURES
2135 and self.EXPORT_EXTRA_SHADER_TEXTURES):
2136
2137
2138 self.addShaderIntegerExtraDatas(trishape)
2139 trishape.addProperty(self.exportTexturingProperty(
2140 flags=0x0001,
2141 applymode=self.APPLYMODE[mesh_base_mtex.blendmode if mesh_base_mtex else Blender.Texture.BlendModes["MIX"]],
2142 uvlayers=mesh_uvlayers,
2143 basemtex=mesh_base_mtex,
2144 glowmtex=mesh_glow_mtex,
2145 bumpmtex=mesh_bump_mtex,
2146 glossmtex=mesh_gloss_mtex,
2147 darkmtex=mesh_dark_mtex,
2148 detailmtex=mesh_detail_mtex,
2149 refmtex=mesh_ref_mtex))
2150
2151 if mesh_hasalpha:
2152
2153
2154 if self.EXPORT_VERSION == "Sid Meier's Railroads":
2155 alphaflags = 0x32ED
2156 alphathreshold = 150
2157 else:
2158 alphaflags = 0x12ED
2159 alphathreshold = 0
2160 trishape.addProperty(
2161 self.exportAlphaProperty(flags=alphaflags,
2162 threshold=alphathreshold))
2163
2164 if mesh_haswire:
2165
2166 trishape.addProperty(self.exportWireframeProperty(flags=1))
2167
2168 if mesh_doublesided:
2169
2170 trishape.addProperty(self.exportStencilProperty())
2171
2172 if mesh_mat:
2173
2174
2175
2176 if (mesh_hasspec
2177 and (self.EXPORT_VERSION
2178 not in self.USED_EXTRA_SHADER_TEXTURES)):
2179
2180 trishape.addProperty(
2181 self.exportSpecularProperty(flags=0x0001))
2182
2183
2184 trimatprop = self.exportMaterialProperty(
2185 name=self.getFullName(mesh_mat.getName()),
2186 flags=0x0001,
2187 ambient=mesh_mat_ambient_color,
2188 diffuse=mesh_mat_diffuse_color,
2189 specular=mesh_mat_specular_color,
2190 emissive=mesh_mat_emissive_color,
2191 glossiness=mesh_mat_glossiness,
2192 alpha=mesh_mat_transparency,
2193 emitmulti=mesh_mat_emitmulti)
2194
2195
2196 trishape.addProperty(trimatprop)
2197
2198
2199
2200 ipo = mesh_mat.getIpo()
2201 a_curve = None
2202 if not(ipo is None):
2203 a_curve = ipo[Ipo.MA_ALPHA]
2204
2205 if not(a_curve is None):
2206
2207 alpha = {}
2208 for btriple in a_curve.getPoints():
2209 knot = btriple.getPoints()
2210 frame = knot[0]
2211 ftime = (frame - self.fstart) * self.fspeed
2212 alpha[ftime] = ipo[Ipo.MA_ALPHA][frame]
2213
2214
2215 alphac = self.createBlock("NiAlphaController", ipo)
2216 alphad = self.createBlock("NiFloatData", ipo)
2217 alphai = self.createBlock("NiFloatInterpolator", ipo)
2218
2219 trimatprop.addController(alphac)
2220 alphac.interpolator = alphai
2221 alphac.data = alphad
2222 alphai.data = alphad
2223
2224
2225 if ( a_curve.getExtrapolation() == "Cyclic" ):
2226 alphac.flags = 0x0008
2227 elif ( a_curve.getExtrapolation() == "Constant" ):
2228 alphac.flags = 0x000c
2229 else:
2230 self.logger.warning("Extrapolation \"%s\" for alpha curve not supported using \"cycle reverse\" instead" % a_curve.getExtrapolation())
2231 alphac.flags = 0x000a
2232
2233
2234 alphac.frequency = 1.0
2235 alphac.phase = 0.0
2236 alphac.startTime = (self.fstart - 1) * self.fspeed
2237 alphac.stopTime = (self.fend - self.fstart) * self.fspeed
2238
2239
2240 if ( a_curve.getInterpolation() == "Linear" ):
2241 alphad.data.interpolation = NifFormat.KeyType.LINEAR_KEY
2242 elif ( a_curve.getInterpolation() == "Bezier" ):
2243 alphad.data.interpolation = NifFormat.KeyType.QUADRATIC_KEY
2244 else:
2245 raise NifExportError( 'interpolation %s for alpha curve not supported use linear or bezier instead'%a_curve.getInterpolation() )
2246
2247 alphad.data.numKeys = len(alpha)
2248 alphad.data.keys.updateSize()
2249 for ftime, key in zip(sorted(alpha), alphad.data.keys):
2250 key.time = ftime
2251 key.value = alpha[ftime]
2252 key.forward = 0.0
2253 key.backward = 0.0
2254
2255 self.exportMaterialControllers(
2256 b_material=mesh_mat, n_geom=trishape)
2257
2258
2259
2260 if isinstance(trishape, NifFormat.NiTriShape):
2261 tridata = self.createBlock("NiTriShapeData", ob)
2262 else:
2263 tridata = self.createBlock("NiTriStripsData", ob)
2264 trishape.data = tridata
2265
2266
2267 tridata.consistencyFlags = NifFormat.ConsistencyType.CT_STATIC
2268
2269
2270
2271 tridata.numVertices = len(vertlist)
2272 tridata.hasVertices = True
2273 tridata.vertices.updateSize()
2274 for i, v in enumerate(tridata.vertices):
2275 v.x = vertlist[i][0]
2276 v.y = vertlist[i][1]
2277 v.z = vertlist[i][2]
2278 tridata.updateCenterRadius()
2279
2280 if mesh_hasnormals:
2281 tridata.hasNormals = True
2282 tridata.normals.updateSize()
2283 for i, v in enumerate(tridata.normals):
2284 v.x = normlist[i][0]
2285 v.y = normlist[i][1]
2286 v.z = normlist[i][2]
2287
2288 if mesh_hasvcol:
2289 tridata.hasVertexColors = True
2290 tridata.vertexColors.updateSize()
2291 for i, v in enumerate(tridata.vertexColors):
2292 v.r = vcollist[i].r / 255.0
2293 v.g = vcollist[i].g / 255.0
2294 v.b = vcollist[i].b / 255.0
2295 v.a = vcollist[i].a / 255.0
2296
2297 if mesh_uvlayers:
2298 tridata.numUvSets = len(mesh_uvlayers)
2299 tridata.bsNumUvSets = len(mesh_uvlayers)
2300 if self.EXPORT_VERSION == "Fallout 3":
2301 if len(mesh_uvlayers) > 1:
2302 raise NifExportError(
2303 "Fallout 3 does not support multiple UV layers")
2304 tridata.hasUv = True
2305 tridata.uvSets.updateSize()
2306 for j, uvlayer in enumerate(mesh_uvlayers):
2307 for i, uv in enumerate(tridata.uvSets[j]):
2308 uv.u = uvlist[i][j][0]
2309 uv.v = 1.0 - uvlist[i][j][1]
2310
2311
2312
2313 tridata.setTriangles(trilist,
2314 stitchstrips=self.EXPORT_STITCHSTRIPS)
2315
2316
2317
2318
2319
2320 if mesh_uvlayers and mesh_hasnormals:
2321 if (self.EXPORT_VERSION in ("Oblivion", "Fallout 3")
2322 or (self.EXPORT_VERSION in self.USED_EXTRA_SHADER_TEXTURES
2323 and self.EXPORT_EXTRA_SHADER_TEXTURES)):
2324 trishape.updateTangentSpace(
2325 as_extra=(self.EXPORT_VERSION == "Oblivion"))
2326
2327
2328 vertgroups = ob.data.getVertGroupNames()
2329 bonenames = []
2330 if ob.getParent():
2331 if ob.getParent().getType() == 'Armature':
2332 ob_armature = ob.getParent()
2333 armaturename = ob_armature.getName()
2334 bonenames = ob_armature.getData().bones.keys()
2335
2336
2337 boneinfluences = []
2338 for bone in bonenames:
2339 if bone in vertgroups:
2340 boneinfluences.append(bone)
2341 if boneinfluences:
2342
2343 if (self.EXPORT_VERSION == "Fallout 3"
2344 and self.EXPORT_FO3_BODYPARTS):
2345 skininst = self.createBlock("BSDismemberSkinInstance", ob)
2346 else:
2347 skininst = self.createBlock("NiSkinInstance", ob)
2348 trishape.skinInstance = skininst
2349 for block in self.blocks:
2350 if isinstance(block, NifFormat.NiNode):
2351 if block.name == self.getFullName(armaturename):
2352 skininst.skeletonRoot = block
2353 break
2354 else:
2355 raise NifExportError(
2356 "Skeleton root '%s' not found."%armaturename)
2357
2358
2359 skindata = self.createBlock("NiSkinData", ob)
2360 skininst.data = skindata
2361
2362 skindata.hasVertexWeights = True
2363
2364
2365 skindata.setTransform(
2366 self.getObjectMatrix(ob, 'localspace').getInverse())
2367
2368
2369
2370 vert_list = {}
2371 vert_norm = {}
2372 for bone in boneinfluences:
2373 try:
2374 vert_list[bone] = ob.data.getVertsFromGroup(bone, 1)
2375 except AttributeError:
2376
2377
2378 raise NifExportError("""\
2379 Mesh %s has vertex group for bone %s, but no weights.
2380 Please select the mesh, and either delete the vertex group, or
2381 go to weight paint mode, and paint weights.""" % (ob.name, bone))
2382 for v in vert_list[bone]:
2383 if vert_norm.has_key(v[0]):
2384 vert_norm[v[0]] += v[1]
2385 else:
2386 vert_norm[v[0]] = v[1]
2387
2388
2389
2390
2391
2392 vert_added = [False for i in xrange(len(vertlist))]
2393 for bone_index, bone in enumerate(boneinfluences):
2394
2395 bone_block = None
2396 for block in self.blocks:
2397 if isinstance(block, NifFormat.NiNode):
2398 if block.name == self.getFullName(bone):
2399 if not bone_block:
2400 bone_block = block
2401 else:
2402 raise NifExportError("""\
2403 multiple bones with name '%s': probably you have multiple armatures, please
2404 parent all meshes to a single armature and try again""" % bone)
2405 if not bone_block:
2406 raise NifExportError(
2407 "Bone '%s' not found." % bone)
2408
2409 vert_weights = {}
2410 for v in vert_list[bone]:
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421 if vertmap[v[0]] and vert_norm[v[0]]:
2422 for vert_index in vertmap[v[0]]:
2423 vert_weights[vert_index] = v[1] / vert_norm[v[0]]
2424 vert_added[vert_index] = True
2425
2426
2427 if vert_weights:
2428 trishape.addBone(bone_block, vert_weights)
2429
2430
2431
2432
2433 vert_weights = {}
2434 if False in vert_added:
2435
2436 for bobj in self.scene.objects:
2437 bobj.sel = False
2438 self.scene.objects.active = ob
2439 ob.sel = 1
2440
2441 for v in mesh.verts:
2442 v.sel = 0
2443 for i, added in enumerate(vert_added):
2444 if not added:
2445 for j, vlist in enumerate(vertmap):
2446 if vlist and (i in vlist):
2447 idx = j
2448 break
2449 else:
2450 raise RuntimeError("vertmap bug")
2451 mesh.verts[idx].sel = 1
2452
2453 Blender.Window.EditMode(1)
2454 raise NifExportError("Cannot export mesh with \
2455 unweighted vertices. The unweighted vertices have been selected in the mesh so \
2456 they can easily be identified.")
2457
2458
2459 trishape.updateBindPosition()
2460
2461
2462
2463 trishape.updateSkinCenterRadius()
2464
2465 if (self.version >= 0x04020100
2466 and self.EXPORT_SKINPARTITION):
2467 self.logger.info("Creating skin partition")
2468 lostweight = trishape.updateSkinPartition(
2469 maxbonesperpartition=self.EXPORT_BONESPERPARTITION,
2470 maxbonespervertex=self.EXPORT_BONESPERVERTEX,
2471 stripify=self.EXPORT_STRIPIFY,
2472 stitchstrips=self.EXPORT_STITCHSTRIPS,
2473 padbones=self.EXPORT_PADBONES,
2474 triangles=trilist,
2475 trianglepartmap=bodypartfacemap,
2476 maximize_bone_sharing=(
2477 self.EXPORT_VERSION == 'Fallout 3'))
2478
2479 if self.EXPORT_VERSION == 'Oblivion':
2480 if self.EXPORT_PADBONES:
2481 self.logger.warning("Using padbones on Oblivion export, but you probably do not want to do this. Disable the pad bones option to get higher quality skin partitions.")
2482 if self.EXPORT_VERSION in ('Oblivion', 'Fallout 3'):
2483 if self.EXPORT_BONESPERPARTITION < 18:
2484 self.logger.warning("Using less than 18 bones per partition on Oblivion/Fallout 3 export. Set it to 18 to get higher quality skin partitions")
2485 if lostweight > self.EPSILON:
2486 self.logger.warning("Lost %f in vertex weights while creating a skin partition for Blender object '%s' (nif block '%s')" % (lostweight, ob.name, trishape.name))
2487
2488
2489 del vert_weights
2490 del vert_added
2491
2492
2493
2494 key = mesh.key
2495 if key:
2496 if len(key.blocks) > 1:
2497
2498
2499 if key.blocks[1].name.startswith("EGM"):
2500
2501 self.exportEgm(key.blocks)
2502 elif key.ipo:
2503
2504
2505 keyipo = key.ipo
2506
2507 if not key.relative:
2508
2509
2510 raise ValueError(
2511 "Can only export relative shape keys.")
2512
2513
2514 morphctrl = self.createBlock("NiGeomMorpherController",
2515 keyipo)
2516 trishape.addController(morphctrl)
2517 morphctrl.target = trishape
2518 morphctrl.frequency = 1.0
2519 morphctrl.phase = 0.0
2520 ctrlStart = 1000000.0
2521 ctrlStop = -1000000.0
2522 ctrlFlags = 0x000c
2523
2524
2525 morphdata = self.createBlock("NiMorphData", keyipo)
2526 morphctrl.data = morphdata
2527 morphdata.numMorphs = len(key.blocks)
2528 morphdata.numVertices = len(vertlist)
2529 morphdata.morphs.updateSize()
2530
2531
2532
2533 morphctrl.numInterpolators = len(key.blocks)
2534 morphctrl.interpolators.updateSize()
2535
2536
2537
2538 morphctrl.numUnknownInts = len(key.blocks)
2539 morphctrl.unknownInts.updateSize()
2540
2541 for keyblocknum, keyblock in enumerate(key.blocks):
2542
2543 morph = morphdata.morphs[keyblocknum]
2544 morph.frameName = keyblock.name
2545 self.logger.info("Exporting morph %s: vertices"
2546 % keyblock.name)
2547 morph.arg = morphdata.numVertices
2548 morph.vectors.updateSize()
2549 for b_v_index, (vert_indices, vert) \
2550 in enumerate(zip(vertmap, keyblock.data)):
2551
2552 if not vert_indices:
2553 continue
2554
2555 mv = vert.copy()
2556 if keyblocknum > 0:
2557 mv.x -= mesh.verts[b_v_index].co.x
2558 mv.y -= mesh.verts[b_v_index].co.y
2559 mv.z -= mesh.verts[b_v_index].co.z
2560 for vert_index in vert_indices:
2561 morph.vectors[vert_index].x = mv.x
2562 morph.vectors[vert_index].y = mv.y
2563 morph.vectors[vert_index].z = mv.z
2564
2565
2566 curve = keyipo[keyblock.name]
2567
2568
2569
2570 interpol = self.createBlock("NiFloatInterpolator")
2571 interpol.value = 0
2572 interpol.data = self.createBlock("NiFloatData", curve)
2573 morphctrl.interpolators[keyblocknum] = interpol
2574 floatdata = interpol.data.data
2575
2576
2577
2578 if self.EXPORT_ANIMATION == 1:
2579 interpol.data = None
2580
2581
2582
2583 if curve:
2584
2585
2586
2587
2588 self.logger.info("Exporting morph %s: curve"
2589 % keyblock.name)
2590 if curve.getExtrapolation() == "Constant":
2591 ctrlFlags = 0x000c
2592 elif curve.getExtrapolation() == "Cyclic":
2593 ctrlFlags = 0x0008
2594 morph.interpolation = NifFormat.KeyType.LINEAR_KEY
2595 morph.numKeys = len(curve.getPoints())
2596 morph.keys.updateSize()
2597 floatdata.interpolation = NifFormat.KeyType.LINEAR_KEY
2598 floatdata.numKeys = len(curve.getPoints())
2599 floatdata.keys.updateSize()
2600 for i, btriple in enumerate(curve.getPoints()):
2601 knot = btriple.getPoints()
2602 morph.keys[i].arg = morph.interpolation
2603 morph.keys[i].time = (knot[0] - self.fstart) * self.fspeed
2604 morph.keys[i].value = curve.evaluate( knot[0] )
2605
2606
2607 floatdata.keys[i].arg = morph.interpolation
2608 floatdata.keys[i].time = (knot[0] - self.fstart) * self.fspeed
2609 floatdata.keys[i].value = curve.evaluate( knot[0] )
2610
2611
2612 ctrlStart = min(ctrlStart, morph.keys[i].time)
2613 ctrlStop = max(ctrlStop, morph.keys[i].time)
2614 morphctrl.flags = ctrlFlags
2615 morphctrl.startTime = ctrlStart
2616 morphctrl.stopTime = ctrlStop
2617
2618
2619
2628
2630 """Export the material alpha controller data."""
2631
2632 return
2633
2635 """Export the material UV controller data."""
2636
2637 b_ipo = b_material.ipo
2638 if not b_ipo:
2639 return
2640
2641 n_uvdata = NifFormat.NiUVData()
2642 n_times = []
2643 b_channels = (Blender.Ipo.MA_OFSX, Blender.Ipo.MA_OFSY,
2644 Blender.Ipo.MA_SIZEX, Blender.Ipo.MA_SIZEY)
2645 for b_channel, n_uvgroup in zip(b_channels, n_uvdata.uvGroups):
2646 b_curve = b_ipo[b_channel]
2647 if b_curve:
2648 self.logger.info("Exporting %s as NiUVData" % b_curve)
2649 n_uvgroup.numKeys = len(b_curve.bezierPoints)
2650
2651 n_uvgroup.interpolation = NifFormat.KeyType.LINEAR_KEY
2652 n_uvgroup.keys.updateSize()
2653 for b_point, n_key in zip(b_curve.bezierPoints, n_uvgroup.keys):
2654
2655 b_time, b_value = b_point.pt
2656 if b_channel in (Blender.Ipo.MA_OFSX, Blender.Ipo.MA_OFSY):
2657
2658 b_value = -b_value
2659 n_key.time = (b_time - 1) * self.fspeed
2660 n_key.value = b_value
2661
2662 n_times.append(n_key.time)
2663
2664
2665 if n_times:
2666 n_uvctrl = NifFormat.NiUVController()
2667
2668 n_uvctrl.flags = 8
2669 n_uvctrl.frequency = 1.0
2670 n_uvctrl.startTime = min(n_times)
2671 n_uvctrl.stopTime = max(n_times)
2672 n_uvctrl.data = n_uvdata
2673
2674 n_geom.addController(n_uvctrl)
2675
2677 """Export the bones of an armature."""
2678
2679
2680 assert( arm.getType() == 'Armature' )
2681
2682
2683
2684 bones = dict(arm.getData().bones.items())
2685 root_bones = []
2686 for root_bone in bones.values():
2687 while root_bone.parent in bones.values():
2688 root_bone = root_bone.parent
2689 if root_bones.count(root_bone) == 0:
2690 root_bones.append(root_bone)
2691
2692 if (arm.getAction()):
2693 bones_ipo = arm.getAction().getAllChannelIpos()
2694 else:
2695 bones_ipo = {}
2696
2697 bones_node = {}
2698
2699
2700
2701
2702
2703
2704 for bone in bones.values():
2705
2706 node = self.createBlock("NiNode", bone)
2707
2708 bones_node[bone.name] = node
2709
2710
2711 node.name = self.getFullName(bone.name)
2712 if self.EXPORT_VERSION in ('Oblivion', 'Fallout 3'):
2713
2714
2715 node.flags = 0x000E
2716 elif self.EXPORT_VERSION == 'Civilization IV':
2717 if bone.children:
2718
2719 node.flags = 0x0006
2720 else:
2721
2722 node.flags = 0x0016
2723 else:
2724 node.flags = 0x0002
2725 self.exportMatrix(bone, 'localspace', node)
2726
2727
2728
2729
2730 bonerestmat = self.getBoneRestMatrix(bone, 'BONESPACE',
2731 extra = False)
2732 try:
2733 bonexmat_inv = Blender.Mathutils.Matrix(
2734 self.getBoneExtraMatrixInv(bone.name))
2735 except KeyError:
2736 bonexmat_inv = Blender.Mathutils.Matrix()
2737 bonexmat_inv.identity()
2738 if bones_ipo.has_key(bone.name):
2739 self.exportKeyframes(
2740 bones_ipo[bone.name], 'localspace', node,
2741 bind_mat = bonerestmat, extra_mat_inv = bonexmat_inv)
2742
2743
2744 for constr in arm.getPose().bones[bone.name].constraints:
2745
2746 if constr.name[:9].lower() == "priority:":
2747 self.bonePriorities[node] = int(constr.name[9:])
2748
2749
2750 for bone in bones.values():
2751
2752 if bone.children:
2753 self.logger.debug("Linking children of bone %s" % bone.name)
2754 for child in bone.children:
2755
2756
2757 if child.parent.name == bone.name:
2758 bones_node[bone.name].addChild(bones_node[child.name])
2759
2760 if not bone.parent:
2761 parent_block.addChild(bones_node[bone.name])
2762
2763
2764
2766 """Export all children of blender object ob as children of
2767 parent_block."""
2768
2769 for ob_child in [ cld for cld in Blender.Object.Get()
2770 if cld.getParent() == obj ]:
2771
2772 if ob_child.getType() in ['Mesh', 'Empty', 'Armature']:
2773 if (obj.getType() != 'Armature'):
2774
2775 self.exportNode(ob_child, 'localspace',
2776 parent_block, ob_child.getName())
2777 else:
2778
2779
2780
2781
2782 parent_bone_name = ob_child.getParentBoneName()
2783 if parent_bone_name is None:
2784 self.exportNode(ob_child, 'localspace',
2785 parent_block, ob_child.getName())
2786 else:
2787
2788
2789
2790 nif_bone_name = self.getFullName(parent_bone_name)
2791 for bone_block in self.blocks:
2792 if isinstance(bone_block, NifFormat.NiNode) and \
2793 bone_block.name == nif_bone_name:
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803 self.exportNode(ob_child, 'localspace',
2804 bone_block, ob_child.getName())
2805 break
2806 else:
2807 assert(False)
2808
2809
2810
2812 """Set a block's transform matrix to an object's
2813 transformation matrix in rest pose."""
2814
2815 bscale, brot, btrans = self.getObjectSRT(obj, space)
2816
2817
2818 block.translation.x = btrans[0]
2819 block.translation.y = btrans[1]
2820 block.translation.z = btrans[2]
2821 block.rotation.m11 = brot[0][0]
2822 block.rotation.m12 = brot[0][1]
2823 block.rotation.m13 = brot[0][2]
2824 block.rotation.m21 = brot[1][0]
2825 block.rotation.m22 = brot[1][1]
2826 block.rotation.m23 = brot[1][2]
2827 block.rotation.m31 = brot[2][0]
2828 block.rotation.m32 = brot[2][1]
2829 block.rotation.m33 = brot[2][2]
2830 block.velocity.x = 0.0
2831 block.velocity.y = 0.0
2832 block.velocity.z = 0.0
2833 block.scale = bscale
2834
2835 return bscale, brot, btrans
2836
2838 """Get an object's matrix as NifFormat.Matrix44
2839
2840 Note: for objects parented to bones, this will return the transform
2841 relative to the bone parent head in nif coordinates (that is, including
2842 the bone correction); this differs from getMatrix which
2843 returns the transform relative to the armature."""
2844 bscale, brot, btrans = self.getObjectSRT(obj, space)
2845 mat = NifFormat.Matrix44()
2846
2847 mat.m41 = btrans[0]
2848 mat.m42 = btrans[1]
2849 mat.m43 = btrans[2]
2850
2851 mat.m11 = brot[0][0] * bscale
2852 mat.m12 = brot[0][1] * bscale
2853 mat.m13 = brot[0][2] * bscale
2854 mat.m21 = brot[1][0] * bscale
2855 mat.m22 = brot[1][1] * bscale
2856 mat.m23 = brot[1][2] * bscale
2857 mat.m31 = brot[2][0] * bscale
2858 mat.m32 = brot[2][1] * bscale
2859 mat.m33 = brot[2][2] * bscale
2860
2861 mat.m14 = 0.0
2862 mat.m24 = 0.0
2863 mat.m34 = 0.0
2864 mat.m44 = 1.0
2865
2866 return mat
2867
2869 """Find scale, rotation, and translation components of an object in
2870 the rest pose. Returns a triple (bs, br, bt), where bs
2871 is a scale float, br is a 3x3 rotation matrix, and bt is a
2872 translation vector. It should hold that
2873
2874 ob.getMatrix(space) == bs * br * bt
2875
2876 Note: for objects parented to bones, this will return the transform
2877 relative to the bone parent head including bone correction.
2878
2879 space is either 'none' (gives identity transform) or 'localspace'"""
2880
2881
2882 if (space == 'none'):
2883 return ( 1.0,
2884 Blender.Mathutils.Matrix([1,0,0],[0,1,0],[0,0,1]),
2885 Blender.Mathutils.Vector([0, 0, 0]) )
2886
2887 assert(space == 'localspace')
2888
2889
2890 if (not type(obj) is Blender.Armature.Bone):
2891 mat = Blender.Mathutils.Matrix(obj.getMatrix('localspace'))
2892 bone_parent_name = obj.getParentBoneName()
2893
2894
2895 if bone_parent_name:
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913 bone_parent = obj.getParent().getData().bones[
2914 bone_parent_name]
2915 boneinv = Blender.Mathutils.Matrix(
2916 bone_parent.matrix['ARMATURESPACE'])
2917 boneinv.invert()
2918 mat = mat * boneinv
2919
2920 try:
2921 extra = Blender.Mathutils.Matrix(
2922 self.getBoneExtraMatrixInv(bone_parent_name))
2923 extra.invert()
2924 mat = mat * extra
2925 except KeyError:
2926
2927 pass
2928 else:
2929
2930 mat = self.getBoneRestMatrix(obj, 'BONESPACE')
2931
2932 try:
2933 return self.decomposeSRT(mat)
2934 except NifExportError:
2935 self.logger.debug(str(mat))
2936 raise NifExportError("""\
2937 Non-uniform scaling on bone '%s' not supported.
2938 This could be a bug... No workaround. :-( Post your blend!""" % obj.name)
2939
2940
2941
2943 """Decompose Blender transform matrix as a scale, rotation matrix, and
2944 translation vector."""
2945
2946 b_scale_rot = mat.rotationPart()
2947 b_scale_rot_t = Blender.Mathutils.Matrix(b_scale_rot)
2948 b_scale_rot_t.transpose()
2949 b_scale_rot_2 = b_scale_rot * b_scale_rot_t
2950 b_scale = Blender.Mathutils.Vector(\
2951 b_scale_rot_2[0][0] ** 0.5,\
2952 b_scale_rot_2[1][1] ** 0.5,\
2953 b_scale_rot_2[2][2] ** 0.5)
2954
2955 if (b_scale_rot.determinant() < 0):
2956 b_scale.negate()
2957
2958
2959 if abs(b_scale[0]-b_scale[1]) + abs(b_scale[1]-b_scale[2]) > 0.02:
2960 raise NifExportError("""\
2961 Non-uniform scaling not supported.
2962 Workaround: apply size and rotation (CTRL-A).""")
2963 b_scale = b_scale[0]
2964
2965 b_rot = b_scale_rot * (1.0 / b_scale)
2966
2967 b_trans = mat.translationPart()
2968
2969 return b_scale, b_rot, b_trans
2970
2971
2972
2974 """Get bone matrix in rest position ("bind pose"). Space can be
2975 ARMATURESPACE or BONESPACE. This returns also a 4x4 matrix if space
2976 is BONESPACE (translation is bone head plus tail from parent bone).
2977 If tail is True then the matrix translation includes the bone tail."""
2978
2979 corrmat = Blender.Mathutils.Matrix()
2980 if extra:
2981 try:
2982 corrmat = Blender.Mathutils.Matrix(
2983 self.getBoneExtraMatrixInv(bone.name))
2984 except KeyError:
2985 corrmat.identity()
2986 else:
2987 corrmat.identity()
2988 if (space == 'ARMATURESPACE'):
2989 mat = Blender.Mathutils.Matrix(bone.matrix['ARMATURESPACE'])
2990 if tail:
2991 tail_pos = bone.tail['ARMATURESPACE']
2992 mat[3][0] = tail_pos[0]
2993 mat[3][1] = tail_pos[1]
2994 mat[3][2] = tail_pos[2]
2995 return corrmat * mat
2996 elif (space == 'BONESPACE'):
2997 if bone.parent:
2998
2999
3000
3001 parinv = self.getBoneRestMatrix(bone.parent, 'ARMATURESPACE',
3002 extra = True, tail = False)
3003 parinv.invert()
3004 return self.getBoneRestMatrix(bone,
3005 'ARMATURESPACE',
3006 extra = extra,
3007 tail = tail) * parinv
3008 else:
3009 return self.getBoneRestMatrix(bone, 'ARMATURESPACE',
3010 extra = extra, tail = tail)
3011 else:
3012 assert(False)
3013
3014
3015
3017 """Helper function to create a new block, register it in the list of
3018 exported blocks, and associate it with a Blender object.
3019
3020 @param blocktype: The nif block type (for instance "NiNode").
3021 @type blocktype: C{str}
3022 @param b_obj: The Blender object.
3023 @return: The newly created block."""
3024 try:
3025 block = getattr(NifFormat, blocktype)()
3026 except AttributeError:
3027 raise NifExportError("""\
3028 '%s': Unknown block type (this is probably a bug).""" % blocktype)
3029 return self.registerBlock(block, b_obj)
3030
3032 """Helper function to register a newly created block in the list of
3033 exported blocks and to associate it with a Blender object.
3034
3035 @param block: The nif block.
3036 @param b_obj: The Blender object.
3037 @return: C{block}"""
3038 if b_obj is None:
3039 self.logger.info("Exporting %s block"%block.__class__.__name__)
3040 else:
3041 self.logger.info("Exporting %s as %s block"
3042 % (b_obj, block.__class__.__name__))
3043 self.blocks[block] = b_obj
3044 return block
3045
3047 """Helper function to associate a nif block with a Blender object.
3048
3049 @param block: The nif block.
3050 @param b_obj: The Blender object.
3051 @return: C{block}"""
3052
3054 """Main function for adding collision object obj to a node."""
3055 if self.EXPORT_VERSION == 'Morrowind':
3056 if obj.rbShapeBoundType != Blender.Object.RBShapes['POLYHEDERON']:
3057 raise NifExportError("""\
3058 Morrowind only supports Polyhedron/Static TriangleMesh collisions.""")
3059 node = self.createBlock("RootCollisionNode", obj)
3060 parent_block.addChild(node)
3061 node.flags = 0x0003
3062 self.exportMatrix(obj, 'localspace', node)
3063 self.exportTriShapes(obj, 'none', node)
3064
3065 elif self.EXPORT_VERSION in ('Oblivion', 'Fallout 3'):
3066
3067 nodes = [ parent_block ]
3068 nodes.extend([ block for block in parent_block.children
3069 if block.name[:14] == 'collisiondummy' ])
3070 for node in nodes:
3071 try:
3072 self.exportCollisionHelper(obj, node)
3073 break
3074 except ValueError:
3075 continue
3076 else:
3077 node = self.createBlock("NiNode", obj)
3078 node.setTransform(self.IDENTITY44)
3079 node.name = 'collisiondummy%i' % parent_block.numChildren
3080 node.flags = 0x000E
3081 parent_block.addChild(node)
3082 self.exportCollisionHelper(obj, node)
3083
3084 else:
3085 self.logger.warning("Only Morrowind, Oblivion, and Fallout 3 collisions are supported, skipped collision object '%s'" % obj.name)
3086
3088 """Helper function to add collision objects to a node. This function
3089 exports the rigid body, and calls the appropriate function to export
3090 the collision geometry in the desired format.
3091
3092 @param obj: The object to export as collision.
3093 @param parent_block: The NiNode parent of the collision.
3094 """
3095
3096
3097 coll_ispacked = (obj.rbShapeBoundType
3098 == Blender.Object.RBShapes['POLYHEDERON'])
3099
3100
3101 material = self.EXPORT_OB_MATERIAL
3102 layer = self.EXPORT_OB_LAYER
3103 motionsys = self.EXPORT_OB_MOTIONSYSTEM
3104
3105 for prop in obj.getAllProperties():
3106 if prop.getName() == 'HavokMaterial':
3107 material = prop.getData()
3108 if isinstance(material, basestring):
3109
3110 material = getattr(NifFormat.HavokMaterial, material)
3111 elif prop.getName() == 'OblivionLayer':
3112 layer = getattr(NifFormat.OblivionLayer, prop.getData())
3113
3114
3115
3116
3117
3118
3119 if not parent_block.collisionObject:
3120
3121 if self.EXPORT_OB_LAYER == NifFormat.OblivionLayer.OL_BIPED:
3122
3123 colobj = self.createBlock("bhkBlendCollisionObject", obj)
3124 colobj.flags = 9
3125 colobj.unknownFloat1 = 1.0
3126 colobj.unknownFloat2 = 1.0
3127
3128 blendctrl = self.createBlock("bhkBlendController", obj)
3129 blendctrl.flags = 12
3130 blendctrl.frequency = 1.0
3131 blendctrl.phase = 0.0
3132 blendctrl.startTime = self.FLOAT_MAX
3133 blendctrl.stopTime = self.FLOAT_MIN
3134 parent_block.addController(blendctrl)
3135 else:
3136
3137 colobj = self.createBlock("bhkCollisionObject", obj)
3138 if self.EXPORT_OB_LAYER == NifFormat.OblivionLayer.OL_ANIM_STATIC:
3139
3140 colobj.flags = 41
3141 else:
3142
3143 colobj.flags = 1
3144 parent_block.collisionObject = colobj
3145 colobj.target = parent_block
3146
3147 colbody = self.createBlock("bhkRigidBody", obj)
3148 colobj.body = colbody
3149 colbody.layer = layer
3150 colbody.unknown5Floats[1] = 3.8139e+36
3151 colbody.unknown4Shorts[0] = 1
3152 colbody.unknown4Shorts[1] = 65535
3153 colbody.unknown4Shorts[2] = 35899
3154 colbody.unknown4Shorts[3] = 16336
3155 colbody.layerCopy = layer
3156 colbody.unknown7Shorts[1] = 21280
3157 colbody.unknown7Shorts[2] = 4581
3158 colbody.unknown7Shorts[3] = 62977
3159 colbody.unknown7Shorts[4] = 65535
3160 colbody.unknown7Shorts[5] = 44
3161 colbody.translation.x = 0.0
3162 colbody.translation.y = 0.0
3163 colbody.translation.z = 0.0
3164 colbody.rotation.w = 1.0
3165 colbody.rotation.x = 0.0
3166 colbody.rotation.y = 0.0
3167 colbody.rotation.z = 0.0
3168 colbody.mass = 1.0
3169 colbody.linearDamping = 0.1
3170 colbody.angularDamping = 0.05
3171 colbody.friction = 0.3
3172 colbody.restitution = 0.3
3173 colbody.maxLinearVelocity = 250.0
3174 colbody.maxAngularVelocity = 31.4159
3175 colbody.penetrationDepth = 0.15
3176 colbody.motionSystem = motionsys
3177 colbody.unknownByte1 = self.EXPORT_OB_UNKNOWNBYTE1
3178 colbody.unknownByte2 = self.EXPORT_OB_UNKNOWNBYTE2
3179 colbody.qualityType = self.EXPORT_OB_QUALITYTYPE
3180 colbody.unknownInt6 = 3216641024
3181 colbody.unknownInt7 = 3249467941
3182 colbody.unknownInt8 = 83276283
3183 colbody.unknownInt9 = self.EXPORT_OB_WIND
3184 else:
3185 colbody = parent_block.collisionObject.body
3186
3187 if coll_ispacked:
3188 self.exportCollisionPacked(obj, colbody, layer, material)
3189 else:
3190 if self.EXPORT_BHKLISTSHAPE:
3191 self.exportCollisionList(obj, colbody, layer, material)
3192 else:
3193 self.exportCollisionSingle(obj, colbody, layer, material)
3194
3196 """Add object ob as packed collision object to collision body colbody.
3197 If parent_block hasn't any collisions yet, a new packed list is created.
3198 If the current collision system is not a packed list of collisions
3199 (bhkPackedNiTriStripsShape), then a ValueError is raised."""
3200
3201 if not colbody.shape:
3202 colshape = self.createBlock("bhkPackedNiTriStripsShape", obj)
3203
3204 colmopp = self.createBlock("bhkMoppBvTreeShape", obj)
3205 colbody.shape = colmopp
3206 colmopp.material = material
3207 colmopp.unknown8Bytes[0] = 160
3208 colmopp.unknown8Bytes[1] = 13
3209 colmopp.unknown8Bytes[2] = 75
3210 colmopp.unknown8Bytes[3] = 1
3211 colmopp.unknown8Bytes[4] = 192
3212 colmopp.unknown8Bytes[5] = 207
3213 colmopp.unknown8Bytes[6] = 144
3214 colmopp.unknown8Bytes[7] = 11
3215 colmopp.unknownFloat = 1.0
3216
3217 colmopp.shape = colshape
3218
3219 colshape.unknownFloats[2] = 0.1
3220 colshape.unknownFloats[4] = 1.0
3221 colshape.unknownFloats[5] = 1.0
3222 colshape.unknownFloats[6] = 1.0
3223 colshape.unknownFloats[8] = 0.1
3224 colshape.scale = 1.0
3225 colshape.unknownFloats2[0] = 1.0
3226 colshape.unknownFloats2[1] = 1.0
3227 else:
3228 colmopp = colbody.shape
3229 if not isinstance(colmopp, NifFormat.bhkMoppBvTreeShape):
3230 raise ValueError('not a packed list of collisions')
3231 colshape = colmopp.shape
3232 if not isinstance(colshape, NifFormat.bhkPackedNiTriStripsShape):
3233 raise ValueError('not a packed list of collisions')
3234
3235 mesh = obj.data
3236 transform = Blender.Mathutils.Matrix(
3237 *self.getObjectMatrix(obj, 'localspace').asList())
3238 rotation = transform.rotationPart()
3239
3240 vertices = [vert.co * transform for vert in mesh.verts]
3241 triangles = []
3242 normals = []
3243 for face in mesh.faces:
3244 if len(face.v) < 3:
3245 continue
3246 triangles.append([face.v[i].index for i in (0, 1, 2)])
3247
3248 normals.append(Blender.Mathutils.Vector(face.no) * rotation)
3249 if len(face.v) == 4:
3250 triangles.append([face.v[i].index for i in (0, 2, 3)])
3251 normals.append(Blender.Mathutils.Vector(face.no) * rotation)
3252
3253 colshape.addShape(triangles, normals, vertices, layer, material)
3254
3255
3256
3258 """Add collision object to colbody.
3259 If colbody already has a collision shape, throw ValueError."""
3260 if colbody.shape:
3261 raise ValueError('collision body already has a shape')
3262 colbody.shape = self.exportCollisionObject(obj, layer, material)
3263
3264
3265
3267 """Add collision object obj to the list of collision objects of colbody.
3268 If colbody has no collisions yet, a new list is created.
3269 If the current collision system is not a list of collisions
3270 (bhkListShape), then a ValueError is raised."""
3271
3272
3273
3274
3275
3276
3277 if not colbody.shape:
3278 colshape = self.createBlock("bhkListShape")
3279 colbody.shape = colshape
3280 colshape.material = material
3281 else:
3282 colshape = colbody.shape
3283 if not isinstance(colshape, NifFormat.bhkListShape):
3284 raise ValueError('not a list of collisions')
3285
3286 colshape.addShape(self.exportCollisionObject(obj, layer, material))
3287
3288
3289
3291 """Export object obj as box, sphere, capsule, or convex hull.
3292 Note: polyheder is handled by exportCollisionPacked."""
3293
3294
3295 minx = min([vert[0] for vert in obj.data.verts])
3296 miny = min([vert[1] for vert in obj.data.verts])
3297 minz = min([vert[2] for vert in obj.data.verts])
3298 maxx = max([vert[0] for vert in obj.data.verts])
3299 maxy = max([vert[1] for vert in obj.data.verts])
3300 maxz = max([vert[2] for vert in obj.data.verts])
3301
3302 if obj.rbShapeBoundType in ( Blender.Object.RBShapes['BOX'],
3303 Blender.Object.RBShapes['SPHERE'] ):
3304
3305 coltf = self.createBlock("bhkConvexTransformShape", obj)
3306 coltf.material = material
3307 coltf.unknownFloat1 = 0.1
3308 coltf.unknown8Bytes[0] = 96
3309 coltf.unknown8Bytes[1] = 120
3310 coltf.unknown8Bytes[2] = 53
3311 coltf.unknown8Bytes[3] = 19
3312 coltf.unknown8Bytes[4] = 24
3313 coltf.unknown8Bytes[5] = 9
3314 coltf.unknown8Bytes[6] = 253
3315 coltf.unknown8Bytes[7] = 4
3316 hktf = Blender.Mathutils.Matrix(
3317 *self.getObjectMatrix(obj, 'localspace').asList())
3318
3319
3320 center = Blender.Mathutils.Vector((minx + maxx) / 2.0, (miny + maxy) / 2.0, (minz + maxz) / 2.0)
3321
3322 center *= hktf
3323 hktf[3][0] = center[0]
3324 hktf[3][1] = center[1]
3325 hktf[3][2] = center[2]
3326
3327 hktf.transpose()
3328 coltf.transform.setRows(*hktf)
3329
3330 coltf.transform.m14 /= 7.0
3331 coltf.transform.m24 /= 7.0
3332 coltf.transform.m34 /= 7.0
3333
3334 if obj.rbShapeBoundType == Blender.Object.RBShapes['BOX']:
3335 colbox = self.createBlock("bhkBoxShape", obj)
3336 coltf.shape = colbox
3337 colbox.material = material
3338 colbox.radius = 0.1
3339 colbox.unknown8Bytes[0] = 0x6b
3340 colbox.unknown8Bytes[1] = 0xee
3341 colbox.unknown8Bytes[2] = 0x43
3342 colbox.unknown8Bytes[3] = 0x40
3343 colbox.unknown8Bytes[4] = 0x3a
3344 colbox.unknown8Bytes[5] = 0xef
3345 colbox.unknown8Bytes[6] = 0x8e
3346 colbox.unknown8Bytes[7] = 0x3e
3347
3348 colbox.dimensions.x = (maxx - minx) / 14.0
3349 colbox.dimensions.y = (maxy - miny) / 14.0
3350 colbox.dimensions.z = (maxz - minz) / 14.0
3351 colbox.minimumSize = min(colbox.dimensions.x, colbox.dimensions.y, colbox.dimensions.z)
3352 elif obj.rbShapeBoundType == Blender.Object.RBShapes['SPHERE']:
3353 colsphere = self.createBlock("bhkSphereShape", obj)
3354 coltf.shape = colsphere
3355 colsphere.material = material
3356
3357
3358 colsphere.radius = (maxx + maxy + maxz - minx - miny -minz) / 42.0
3359
3360 return coltf
3361
3362 elif obj.rbShapeBoundType == Blender.Object.RBShapes['CYLINDER']:
3363 colcaps = self.createBlock("bhkCapsuleShape", obj)
3364 colcaps.material = material
3365
3366 localradius = (maxx + maxy - minx - miny) / 4.0
3367 transform = Blender.Mathutils.Matrix(
3368 *self.getObjectMatrix(obj, 'localspace').asList())
3369 vert1 = Blender.Mathutils.Vector( [ (maxx + minx)/2.0,
3370 (maxy + miny)/2.0,
3371 minz + localradius ] )
3372 vert2 = Blender.Mathutils.Vector( [ (maxx + minx) / 2.0,
3373 (maxy + miny) / 2.0,
3374 maxz - localradius ] )
3375 vert1 *= transform
3376 vert2 *= transform
3377 colcaps.firstPoint.x = vert1[0] / 7.0
3378 colcaps.firstPoint.y = vert1[1] / 7.0
3379 colcaps.firstPoint.z = vert1[2] / 7.0
3380 colcaps.secondPoint.x = vert2[0] / 7.0
3381 colcaps.secondPoint.y = vert2[1] / 7.0
3382 colcaps.secondPoint.z = vert2[2] / 7.0
3383
3384 sizex, sizey, sizez = obj.getSize()
3385 colcaps.radius = localradius * (sizex + sizey) * 0.5
3386 colcaps.radius1 = colcaps.radius
3387 colcaps.radius2 = colcaps.radius
3388
3389 colcaps.radius /= 7.0
3390 colcaps.radius1 /= 7.0
3391 colcaps.radius2 /= 7.0
3392
3393 return colcaps
3394
3395 elif obj.rbShapeBoundType == 5:
3396
3397
3398 mesh = obj.data
3399 transform = Blender.Mathutils.Matrix(
3400 *self.getObjectMatrix(obj, 'localspace').asList())
3401 rotation = transform.rotationPart()
3402 scale = rotation.determinant()
3403 if scale < 0:
3404 scale = - (-scale) ** (1.0 / 3)
3405 else:
3406 scale = scale ** (1.0 / 3)
3407 rotation *= 1.0 / scale
3408
3409
3410 vertlist = [ vert.co * transform for vert in mesh.verts ]
3411 fnormlist = [ Blender.Mathutils.Vector(face.no) * rotation
3412 for face in mesh.faces]
3413 fdistlist = [
3414 Blender.Mathutils.DotVecs(
3415 -face.v[0].co * transform,
3416 Blender.Mathutils.Vector(face.no) * rotation)
3417 for face in mesh.faces ]
3418
3419
3420 vertdict = {}
3421 for i, vert in enumerate(vertlist):
3422 vertdict[(int(vert[0]*200),
3423 int(vert[1]*200),
3424 int(vert[2]*200))] = i
3425 fdict = {}
3426 for i, (norm, dist) in enumerate(zip(fnormlist, fdistlist)):
3427 fdict[(int(norm[0]*200),
3428 int(norm[1]*200),
3429 int(norm[2]*200),
3430 int(dist*200))] = i
3431
3432 vertkeys = sorted(vertdict.keys())
3433 fkeys = sorted(fdict.keys())
3434 vertlist = [ vertlist[vertdict[hsh]] for hsh in vertkeys ]
3435 fnormlist = [ fnormlist[fdict[hsh]] for hsh in fkeys ]
3436 fdistlist = [ fdistlist[fdict[hsh]] for hsh in fkeys ]
3437
3438 if len(fnormlist) > 65535 or len(vertlist) > 65535:
3439 raise NifExportError("""
3440 ERROR%t|Too many faces/vertices. Decimate/split your mesh and try again.""")
3441
3442 colhull = self.createBlock("bhkConvexVerticesShape", obj)
3443 colhull.material = material
3444 colhull.radius = 0.1
3445 colhull.unknown6Floats[2] = -0.0
3446 colhull.unknown6Floats[5] = -0.0
3447
3448 colhull.numVertices = len(vertlist)
3449 colhull.vertices.updateSize()
3450 for vhull, vert in zip(colhull.vertices, vertlist):
3451 vhull.x = vert[0] / 7.0
3452 vhull.y = vert[1] / 7.0
3453 vhull.z = vert[2] / 7.0
3454
3455 colhull.numNormals = len(fnormlist)
3456 colhull.normals.updateSize()
3457 for nhull, norm, dist in zip(colhull.normals, fnormlist, fdistlist):
3458 nhull.x = norm[0]
3459 nhull.y = norm[1]
3460 nhull.z = norm[2]
3461 nhull.w = dist / 7.0
3462
3463 return colhull
3464
3465 else:
3466 raise NifExportError(
3467 'cannot export collision type %s to collision shape list'
3468 % obj.rbShapeBoundType)
3469
3471 """Export the constraints of an object.
3472
3473 @param b_obj: The object whose constraints to export.
3474 @param root_block: The root of the nif tree (required for updateAB)."""
3475 if isinstance(b_obj, Blender.Armature.Bone):
3476
3477
3478
3479
3480 return
3481
3482 if not hasattr(b_obj, "constraints"):
3483
3484 return
3485
3486 for b_constr in b_obj.constraints:
3487
3488 if b_constr.type == Blender.Constraint.Type.RIGIDBODYJOINT:
3489 if self.EXPORT_VERSION not in ("Oblivion", "Fallout 3"):
3490 self.logger.warning("""\
3491 Only Oblivion/Fallout 3 rigid body constraints can be exported
3492 skipped %s""" % b_constr)
3493 continue
3494
3495 for otherbody, otherobj in self.blocks.iteritems():
3496 if isinstance(otherbody, NifFormat.bhkRigidBody) \
3497 and otherobj is b_obj:
3498 hkbody = otherbody
3499 break
3500 else:
3501
3502 raise NifExportError("""\
3503 Object %s has a rigid body constraint,
3504 but is not exported as collision object""")
3505
3506
3507 if b_constr[Blender.Constraint.Settings.CONSTR_RB_TYPE] == 1:
3508
3509 if not self.EXPORT_OB_MALLEABLECONSTRAINT:
3510 hkconstraint = self.createBlock(
3511 "bhkRagdollConstraint", b_constr)
3512 else:
3513 hkconstraint = self.createBlock(
3514 "bhkMalleableConstraint", b_constr)
3515 hkconstraint.type = 7
3516 hkdescriptor = hkconstraint.ragdoll
3517 elif b_constr[Blender.Constraint.Settings.CONSTR_RB_TYPE] == 2:
3518
3519 if not self.EXPORT_OB_MALLEABLECONSTRAINT:
3520 hkconstraint = self.createBlock(
3521 "bhkLimitedHingeConstraint", b_constr)
3522 else:
3523 hkconstraint = self.createBlock(
3524 "bhkMalleableConstraint", b_constr)
3525 hkconstraint.type = 2
3526 hkdescriptor = hkconstraint.limitedHinge
3527 else:
3528 raise NifExportError("""\
3529 Unsupported rigid body joint type (%i), only ball and hinge are supported.""" \
3530 % b_constr[Blender.Constraint.Settings.CONSTR_RB_TYPE])
3531
3532
3533 hkbody.numConstraints += 1
3534 hkbody.constraints.updateSize()
3535 hkbody.constraints[-1] = hkconstraint
3536
3537
3538 hkconstraint.numEntities = 2
3539 hkconstraint.entities.updateSize()
3540 hkconstraint.entities[0] = hkbody
3541
3542 targetobj = b_constr[Blender.Constraint.Settings.TARGET]
3543 if not targetobj:
3544 self.logger.warning("Constraint %s has no target, skipped")
3545 continue
3546
3547 for otherbody, otherobj in self.blocks.iteritems():
3548 if isinstance(otherbody, NifFormat.bhkRigidBody) \
3549 and otherobj == targetobj:
3550 hkconstraint.entities[1] = otherbody
3551 break
3552 else:
3553
3554 raise NifExportError("""\
3555 Rigid body target not exported in nif tree,
3556 check that %s is selected during export.""" % targetobj)
3557
3558 hkconstraint.priority = 1
3559
3560 if isinstance(hkconstraint, NifFormat.bhkMalleableConstraint):
3561
3562 hkconstraint.unknownInt2 = 2
3563 hkconstraint.unknownInt3 = 1
3564
3565
3566 hkconstraint.tau = 0.5
3567
3568
3569 hkconstraint.damping = 0.5
3570
3571
3572 pivot = Blender.Mathutils.Vector(
3573 b_constr[Blender.Constraint.Settings.CONSTR_RB_PIVX],
3574 b_constr[Blender.Constraint.Settings.CONSTR_RB_PIVY],
3575 b_constr[Blender.Constraint.Settings.CONSTR_RB_PIVZ])
3576 constr_matrix = Blender.Mathutils.Euler(
3577 b_constr[Blender.Constraint.Settings.CONSTR_RB_AXX],
3578 b_constr[Blender.Constraint.Settings.CONSTR_RB_AXY],
3579 b_constr[Blender.Constraint.Settings.CONSTR_RB_AXZ])
3580 constr_matrix = constr_matrix.toMatrix()
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604 transform = Blender.Mathutils.Matrix(
3605 *self.getObjectMatrix(b_obj, 'localspace').asList())
3606 pivot = pivot * transform
3607 constr_matrix = constr_matrix * transform.rotationPart()
3608
3609
3610 hkdescriptor.pivotA.x = pivot[0] / 7.0
3611 hkdescriptor.pivotA.y = pivot[1] / 7.0
3612 hkdescriptor.pivotA.z = pivot[2] / 7.0
3613
3614
3615 axis_x = Blender.Mathutils.Vector(1,0,0) * constr_matrix
3616 axis_y = Blender.Mathutils.Vector(0,1,0) * constr_matrix
3617 axis_z = Blender.Mathutils.Vector(0,0,1) * constr_matrix
3618 if isinstance(hkdescriptor, NifFormat.RagdollDescriptor):
3619
3620 hkdescriptor.twistA.x = axis_z[0]
3621 hkdescriptor.twistA.y = axis_z[1]
3622 hkdescriptor.twistA.z = axis_z[2]
3623
3624 hkdescriptor.planeA.x = axis_x[0]
3625 hkdescriptor.planeA.y = axis_x[1]
3626 hkdescriptor.planeA.z = axis_x[2]
3627
3628
3629 hkdescriptor.twistMinAngle = -0.8
3630 hkdescriptor.twistMaxAngle = +0.8
3631 hkdescriptor.planeMinAngle = -0.8
3632 hkdescriptor.planeMaxAngle = +0.8
3633
3634 hkdescriptor.coneMaxAngle = +0.8
3635 elif isinstance(hkdescriptor, NifFormat.LimitedHingeDescriptor):
3636
3637 hkdescriptor.perp2AxleInA1.x = axis_y[0]
3638 hkdescriptor.perp2AxleInA1.y = axis_y[1]
3639 hkdescriptor.perp2AxleInA1.z = axis_y[2]
3640
3641 hkdescriptor.axleA.x = axis_x[0]
3642 hkdescriptor.axleA.y = axis_x[1]
3643 hkdescriptor.axleA.z = axis_x[2]
3644
3645
3646 hkdescriptor.perp2AxleInA2.x = axis_z[0]
3647 hkdescriptor.perp2AxleInA2.y = axis_z[1]
3648 hkdescriptor.perp2AxleInA2.z = axis_z[2]
3649
3650
3651
3652 hkdescriptor.minAngle = 0.0
3653
3654
3655 hkdescriptor.maxAngle = 1.5
3656
3657 else:
3658 raise ValueError("unknown descriptor %s"
3659 % hkdescriptor.__class__.__name__)
3660
3661
3662 if isinstance(hkconstraint,
3663 NifFormat.bhkMalleableConstraint):
3664
3665
3666 hkdescriptor.maxFriction = 0.0
3667 else:
3668
3669 hkdescriptor.maxFriction = 10.0
3670
3671
3672 hkconstraint.updateAB(root_block)
3673
3674
3676 """Return existing alpha property with given flags, or create new one
3677 if an alpha property with required flags is not found."""
3678
3679 for block in self.blocks:
3680 if isinstance(block, NifFormat.NiAlphaProperty) \
3681 and block.flags == flags and block.threshold == threshold:
3682 return block
3683
3684 alphaprop = self.createBlock("NiAlphaProperty")
3685 alphaprop.flags = flags
3686 alphaprop.threshold = threshold
3687 return alphaprop
3688
3690 """Return existing specular property with given flags, or create new one
3691 if a specular property with required flags is not found."""
3692
3693 for block in self.blocks:
3694 if isinstance(block, NifFormat.NiSpecularProperty) \
3695 and block.flags == flags:
3696 return block
3697
3698 specprop = self.createBlock("NiSpecularProperty")
3699 specprop.flags = flags
3700 return specprop
3701
3703 """Return existing wire property with given flags, or create new one
3704 if an wire property with required flags is not found."""
3705
3706 for block in self.blocks:
3707 if isinstance(block, NifFormat.NiWireframeProperty) \
3708 and block.flags == flags:
3709 return block
3710
3711 wireprop = self.createBlock("NiWireframeProperty")
3712 wireprop.flags = flags
3713 return wireprop
3714
3716 """Return existing stencil property with given flags, or create new one
3717 if an identical stencil property."""
3718
3719 for block in self.blocks:
3720 if isinstance(block, NifFormat.NiStencilProperty):
3721
3722
3723 return block
3724
3725 stencilprop = self.createBlock("NiStencilProperty")
3726 return stencilprop
3727
3728 - def exportMaterialProperty(self, name='', flags=0x0001,
3729 ambient=(1.0, 1.0, 1.0),
3730 diffuse=(1.0, 1.0, 1.0),
3731 specular=(0.0, 0.0, 0.0),
3732 emissive=(0.0, 0.0, 0.0),
3733 glossiness=10.0,
3734 alpha=1.0,
3735 emitmulti=1.0):
3736 """Return existing material property with given settings, or create
3737 a new one if a material property with these settings is not found."""
3738
3739
3740 matprop = NifFormat.NiMaterialProperty()
3741
3742
3743
3744
3745 specialnames = ("EnvMap2", "EnvMap", "skin", "Hair",
3746 "dynalpha", "HideSecret", "Lava")
3747
3748
3749
3750 if self.EXPORT_VERSION in ('Oblivion', 'Fallout 3'):
3751 for specialname in specialnames:
3752 if (name.lower() == specialname.lower()
3753 or name.lower().startswith(specialname.lower() + ".")):
3754 if name != specialname:
3755 self.logger.warning("Renaming material '%s' to '%s'"
3756 % (name, specialname))
3757 name = specialname
3758
3759
3760 if name.lower().startswith("noname"):
3761 self.logger.warning("Renaming material '%s' to ''" % name)
3762 name = ""
3763
3764 matprop.name = name
3765 matprop.flags = flags
3766 matprop.ambientColor.r = ambient[0]
3767 matprop.ambientColor.g = ambient[1]
3768 matprop.ambientColor.b = ambient[2]
3769 matprop.diffuseColor.r = diffuse[0]
3770 matprop.diffuseColor.g = diffuse[1]
3771 matprop.diffuseColor.b = diffuse[2]
3772 matprop.specularColor.r = specular[0]
3773 matprop.specularColor.g = specular[1]
3774 matprop.specularColor.b = specular[2]
3775 matprop.emissiveColor.r = emissive[0]
3776 matprop.emissiveColor.g = emissive[1]
3777 matprop.emissiveColor.b = emissive[2]
3778 matprop.glossiness = glossiness
3779 matprop.alpha = alpha
3780 matprop.emitMulti = emitmulti
3781
3782
3783
3784
3785 for block in self.blocks:
3786 if not isinstance(block, NifFormat.NiMaterialProperty):
3787 continue
3788
3789 if self.EXPORT_OPTIMIZE_MATERIALS:
3790 ignore_strings = not(block.name in specialnames)
3791 else:
3792 ignore_strings = False
3793
3794 if (block.getHash(ignore_strings=ignore_strings) ==
3795 matprop.getHash(
3796 ignore_strings=ignore_strings)):
3797 self.logger.warning("Merging materials '%s' and '%s' \
3798 (they are identical in nif)"
3799 % (matprop.name, block.name))
3800 return block
3801
3802
3803
3804 return self.registerBlock(matprop)
3805
3806 - def exportTexDesc(self, texdesc=None, uvlayers=None, mtex=None):
3807 """Helper function for exportTexturingProperty to export each texture
3808 slot."""
3809 try:
3810 texdesc.uvSet = uvlayers.index(mtex.uvlayer) if mtex.uvlayer else 0
3811 except ValueError:
3812 self.logger.warning("""Bad uv layer name '%s' in texture '%s'. Falling back on first uv layer""" % (mtex.uvlayer, mtex.tex.getName()))
3813 texdesc.uvSet = 0
3814
3815 texdesc.source = self.exportSourceTexture(mtex.tex)
3816
3817 - def exportTexturingProperty(
3818 self, flags=0x0001, applymode=None, uvlayers=None,
3819 basemtex=None, glowmtex=None, bumpmtex=None, glossmtex=None,
3820 darkmtex=None, detailmtex=None, refmtex=None):
3821 """Export texturing property. The parameters basemtex, glowmtex,
3822 bumpmtex, ... are the Blender material textures (MTex, not Texture)
3823 that correspond to the base, glow, bump map, ... textures. The uvlayers
3824 parameter is a list of uvlayer strings, that is, mesh.getUVLayers()."""
3825
3826 texprop = NifFormat.NiTexturingProperty()
3827
3828 texprop.flags = flags
3829 texprop.applyMode = applymode
3830 texprop.textureCount = 7
3831
3832 if self.EXPORT_EXTRA_SHADER_TEXTURES:
3833 if self.EXPORT_VERSION == "Sid Meier's Railroads":
3834
3835
3836
3837 texprop.numShaderTextures = 5
3838 texprop.shaderTextures.updateSize()
3839 for mapindex, shadertexdesc in enumerate(texprop.shaderTextures):
3840
3841 shadertexdesc.isUsed = False
3842 shadertexdesc.mapIndex = mapindex
3843
3844
3845 shadertexdesc_envmap = texprop.shaderTextures[0]
3846 shadertexdesc_envmap.isUsed = True
3847 shadertexdesc_envmap.textureData.source = \
3848 self.exportSourceTexture(filename="RRT_Engine_Env_map.dds")
3849
3850 shadertexdesc_cubelightmap = texprop.shaderTextures[4]
3851 shadertexdesc_cubelightmap.isUsed = True
3852 shadertexdesc_cubelightmap.textureData.source = \
3853 self.exportSourceTexture(filename="RRT_Cube_Light_map_128.dds")
3854
3855
3856
3857 elif self.EXPORT_VERSION == "Civilization IV":
3858
3859
3860 texprop.numShaderTextures = 4
3861 texprop.shaderTextures.updateSize()
3862 for mapindex, shadertexdesc in enumerate(texprop.shaderTextures):
3863
3864 shadertexdesc.isUsed = False
3865 shadertexdesc.mapIndex = mapindex
3866
3867 if basemtex:
3868 texprop.hasBaseTexture = True
3869 self.exportTexDesc(texdesc = texprop.baseTexture,
3870 uvlayers = uvlayers,
3871 mtex = basemtex)
3872
3873 try:
3874 fliptxt = Blender.Text.Get(basemtex.tex.getName())
3875 except NameError:
3876 pass
3877 else:
3878
3879 self.exportFlipController(fliptxt, basemtex.tex, texprop, 0)
3880
3881 if glowmtex:
3882 texprop.hasGlowTexture = True
3883 self.exportTexDesc(texdesc = texprop.glowTexture,
3884 uvlayers = uvlayers,
3885 mtex = glowmtex)
3886
3887 if bumpmtex:
3888 if self.EXPORT_VERSION not in self.USED_EXTRA_SHADER_TEXTURES:
3889 texprop.hasBumpMapTexture = True
3890 self.exportTexDesc(texdesc = texprop.bumpMapTexture,
3891 uvlayers = uvlayers,
3892 mtex = bumpmtex)
3893 texprop.bumpMapLumaScale = 1.0
3894 texprop.bumpMapLumaOffset = 0.0
3895 texprop.bumpMapMatrix.m11 = 1.0
3896 texprop.bumpMapMatrix.m12 = 0.0
3897 texprop.bumpMapMatrix.m21 = 0.0
3898 texprop.bumpMapMatrix.m22 = 1.0
3899 elif self.EXPORT_EXTRA_SHADER_TEXTURES:
3900 shadertexdesc = texprop.shaderTextures[1]
3901 shadertexdesc.isUsed = True
3902 shadertexdesc.textureData.source = \
3903 self.exportSourceTexture(texture=bumpmtex.tex)
3904
3905 if glossmtex:
3906 if self.EXPORT_VERSION not in self.USED_EXTRA_SHADER_TEXTURES:
3907 texprop.hasGlossTexture = True
3908 self.exportTexDesc(texdesc = texprop.glossTexture,
3909 uvlayers = uvlayers,
3910 mtex = glossmtex)
3911 elif self.EXPORT_EXTRA_SHADER_TEXTURES:
3912 shadertexdesc = texprop.shaderTextures[2]
3913 shadertexdesc.isUsed = True
3914 shadertexdesc.textureData.source = \
3915 self.exportSourceTexture(texture=glossmtex.tex)
3916
3917 if darkmtex:
3918 texprop.hasDarkTexture = True
3919 self.exportTexDesc(texdesc = texprop.darkTexture,
3920 uvlayers = uvlayers,
3921 mtex = darkmtex)
3922
3923 if detailmtex:
3924 texprop.hasDetailTexture = True
3925 self.exportTexDesc(texdesc = texprop.detailTexture,
3926 uvlayers = uvlayers,
3927 mtex = detailmtex)
3928
3929 if refmtex:
3930 if self.EXPORT_VERSION not in self.USED_EXTRA_SHADER_TEXTURES:
3931 self.logger.warn(
3932 "Cannot export reflection texture for this game.")
3933
3934
3935
3936
3937 else:
3938 shadertexdesc = texprop.shaderTextures[3]
3939 shadertexdesc.isUsed = True
3940 shadertexdesc.textureData.source = \
3941 self.exportSourceTexture(texture=refmtex.tex)
3942
3943
3944 for block in self.blocks:
3945 if isinstance(block, NifFormat.NiTexturingProperty) \
3946 and block.getHash() == texprop.getHash():
3947 return block
3948
3949
3950
3951 return self.registerBlock(texprop)
3952
3955 """Export a Bethesda shader property block."""
3956
3957
3958 bsshader = NifFormat.BSShaderPPLightingProperty()
3959
3960 bsshader.shaderType = self.EXPORT_FO3_SHADER_TYPE
3961 bsshader.shaderFlags.zbufferTest = self.EXPORT_FO3_SF_ZBUF
3962 bsshader.shaderFlags.shadowMap = self.EXPORT_FO3_SF_SMAP
3963 bsshader.shaderFlags.shadowFrustum = self.EXPORT_FO3_SF_SFRU
3964 bsshader.shaderFlags.windowEnvironmentMapping = self.EXPORT_FO3_SF_WINDOW_ENVMAP
3965 bsshader.shaderFlags.empty = self.EXPORT_FO3_SF_EMPT
3966 bsshader.shaderFlags.unknown31 = self.EXPORT_FO3_SF_UN31
3967
3968 texset = NifFormat.BSShaderTextureSet()
3969 bsshader.textureSet = texset
3970 if basemtex:
3971 texset.textures[0] = self.exportTextureFilename(basemtex.tex)
3972 if bumpmtex:
3973 texset.textures[1] = self.exportTextureFilename(bumpmtex.tex)
3974 if glowmtex:
3975 texset.textures[2] = self.exportTextureFilename(glowmtex.tex)
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985 return self.registerBlock(bsshader)
3986
3987 - def exportTextureEffect(self, mtex = None):
3988 """Export a texture effect block from material texture mtex (MTex, not
3989 Texture)."""
3990 texeff = NifFormat.NiTextureEffect()
3991 texeff.flags = 4
3992 texeff.rotation.setIdentity()
3993 texeff.scale = 1.0
3994 texeff.modelProjectionMatrix.setIdentity()
3995 texeff.textureFiltering = NifFormat.TexFilterMode.FILTER_TRILERP
3996 texeff.textureClamping = NifFormat.TexClampMode.WRAP_S_WRAP_T
3997 texeff.textureType = NifFormat.EffectType.EFFECT_ENVIRONMENT_MAP
3998 texeff.coordinateGenerationType = NifFormat.CoordGenType.CG_SPHERE_MAP
3999 if mtex:
4000 texeff.sourceTexture = self.exportSourceTexture(mtex.tex)
4001 if self.EXPORT_VERSION == 'Morrowind':
4002 texeff.numAffectedNodeListPointers += 1
4003 texeff.affectedNodeListPointers.updateSize()
4004 texeff.unknownVector.x = 1.0
4005 return self.registerBlock(texeff)
4006
4008 """Export an Oblivion bounding box."""
4009 bbox = self.createBlock("BSBound")
4010
4011
4012
4013
4014 block_parent.numExtraDataList += 1
4015 block_parent.extraDataList.updateSize()
4016 block_parent.extraDataList[-1] = bbox
4017
4018 bbox.name = "BBX"
4019
4020 objbbox = obj.getBoundBox()
4021 minx = min(vert[0] for vert in objbbox)
4022 miny = min(vert[1] for vert in objbbox)
4023 minz = min(vert[2] for vert in objbbox)
4024 maxx = max(vert[0] for vert in objbbox)
4025 maxy = max(vert[1] for vert in objbbox)
4026 maxz = max(vert[2] for vert in objbbox)
4027
4028 bbox.center.x = (minx + maxx) * 0.5
4029 bbox.center.y = (miny + maxy) * 0.5
4030 bbox.center.z = (minz + maxz) * 0.5
4031 bbox.dimensions.x = maxx - minx
4032 bbox.dimensions.y = maxy - miny
4033 bbox.dimensions.z = maxz - minz
4034
4036 """Add extra data blocks for shader indices."""
4037 for shaderindex in self.USED_EXTRA_SHADER_TEXTURES[self.EXPORT_VERSION]:
4038 shadername = self.EXTRA_SHADER_TEXTURES[shaderindex]
4039 trishape.addIntegerExtraData(shadername, shaderindex)
4040
4042 self.egmdata = EgmFormat.Data(num_vertices=len(keyblocks[0].data))
4043 for keyblock in keyblocks:
4044 if keyblock.name.startswith("EGM SYM"):
4045 morph = self.egmdata.add_sym_morph()
4046 elif keyblock.name.startswith("EGM ASYM"):
4047 morph = self.egmdata.add_asym_morph()
4048 else:
4049 continue
4050 self.logger.info("Exporting morph %s to egm" % keyblock.name)
4051 relative_vertices = []
4052
4053 for vert, key_vert in izip(keyblocks[0].data, keyblock.data):
4054 relative_vertices.append(key_vert - vert)
4055 morph.set_relative_vertices(relative_vertices)
4056
4058 """Called when config script is done. Starts and times import."""
4059 starttime = Blender.sys.time()
4060
4061 exporter = NifExport(**config)
4062
4063 exporter.logger.info('Finished in %.2f seconds'
4064 % (Blender.sys.time() - starttime))
4065 Blender.Window.WaitCursor(0)
4066
4071
4072 if __name__ == '__main__':
4073
4074 _CONFIG = NifConfig()
4075
4076 Blender.Window.FileSelector(
4077 fileselect_callback, "Export NIF/KF", _CONFIG.config["EXPORT_FILE"])
4078