Module export_nif
[hide private]
[frames] | no frames]

Source Code for Module export_nif

   1  #!BPY 
   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 # for all the Ipo curve constants 
  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  # ***** BEGIN LICENSE BLOCK ***** 
  33  #  
  34  # BSD License 
  35  #  
  36  # Copyright (c) 2005-2009, NIF File Format Library and Tools 
  37  # All rights reserved. 
  38  #  
  39  # Redistribution and use in source and binary forms, with or without 
  40  # modification, are permitted provided that the following conditions 
  41  # are met: 
  42  # 1. Redistributions of source code must retain the above copyright 
  43  #    notice, this list of conditions and the following disclaimer. 
  44  # 2. Redistributions in binary form must reproduce the above copyright 
  45  #    notice, this list of conditions and the following disclaimer in the 
  46  #    documentation and/or other materials provided with the distribution. 
  47  # 3. The name of the NIF File Format Library and Tools project may not be 
  48  #    used to endorse or promote products derived from this software 
  49  #    without specific prior written permission. 
  50  #  
  51  # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 
  52  # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
  53  # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
  54  # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
  55  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
  56  # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
  57  # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
  58  # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
  59  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
  60  # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  61  # 
  62  # ***** END LICENSE BLOCK ***** 
  63  # -------------------------------------------------------------------------- 
  64   
65 -class NifExportError(StandardError):
66 pass
67 68 # main export class
69 -class NifExport(NifImportExport):
70 IDENTITY44 = NifFormat.Matrix44() 71 IDENTITY44.setIdentity() 72 # map blending modes to apply modes 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 # Blender bone names are unique so we can use them as keys. 88 for ln in bonetxt.asLines(): 89 if len(ln)>0: 90 # reconstruct matrix from text 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 # Check if matrices are clean, and if necessary fix them. 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 # Matrices are stored inverted for easier math later on. 114 mat.invert() 115 self.setBoneExtraMatrixInv(b, mat)
116
117 - def setBoneExtraMatrixInv(self, bonename, mat):
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
123 - def getBoneExtraMatrixInv(self, bonename):
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
129 - def rebuildFullNames(self):
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
142 - def getUniqueName(self, blender_name):
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 # blender bone naming -> nif bone naming 149 unique_name = self.get_bone_name_for_nif(unique_name) 150 # ensure uniqueness 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
161 - def getFullName(self, blender_name):
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
169 - def getExportedObjects(self):
170 """Return a list of exported objects.""" 171 exported_objects = [] 172 # iterating over self.blocks.itervalues() will count some objects 173 # twice 174 for obj in self.blocks.itervalues(): 175 # skip empty objects 176 if obj is None: 177 continue 178 # detect doubles 179 if obj in exported_objects: 180 continue 181 # append new object 182 exported_objects.append(obj) 183 # return the list of unique exported objects 184 return exported_objects
185
186 - def __init__(self, **config):
187 """Main export function.""" 188 189 # preparation: 190 #-------------- 191 self.msg_progress("Initializing", progbar=0) 192 193 # store configuration in self 194 for name, value in config.iteritems(): 195 setattr(self, name, value) 196 if self.EXPORT_MW_NIFXNIFKF and self.EXPORT_VERSION == 'Morrowind': 197 # if exporting in nif+xnif+kf mode, then first export 198 # the nif with geometry + animation, which is done by: 199 self.EXPORT_ANIMATION = 0 200 201 # shortcut to export logger 202 self.logger = logging.getLogger("niftools.blender.export") 203 204 # save file name 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 # variables 211 # dictionary mapping exported blocks to either None or to an 212 # associated Blender object 213 self.blocks = {} 214 # maps Blender names to previously imported names from the FullNames 215 # buffer (see self.rebuildFullNames()) 216 self.names = {} 217 # keeps track of names of exported blocks, to make sure they are unique 218 self.blockNames = [] 219 220 # dictionary of bones, maps Blender bone name to matrix that maps the 221 # NIF bone matrix on the Blender bone matrix 222 # Recall from the import script 223 # B' = X * B, 224 # where B' is the Blender bone matrix, and B is the NIF bone matrix, 225 # both in armature space. So to restore the NIF matrices we need to do 226 # B = X^{-1} * B' 227 # Hence, we will restore the X's, invert them, and store those inverses in the 228 # following dictionary. 229 self.bonesExtraMatrixInv = {} 230 231 # store bone priorities (from NULL constraints) as the armature bones 232 # are parsed, so they are available when writing the kf file 233 # maps bone NiNode to priority value 234 self.bonePriorities = {} 235 236 # if an egm is exported, this will contain the data 237 self.egmdata = None 238 239 try: # catch export errors 240 241 # find nif version to write 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 # select highest nif version that the game supports 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 # for morrowind: everything except keyframe controllers 255 self.logger.info("Exporting geometry only") 256 elif self.EXPORT_ANIMATION == 2: 257 # for morrowind: only keyframe controllers 258 self.logger.info("Exporting animation only (as .kf file)") 259 260 for ob in Blender.Object.Get(): 261 # armatures should not be in rest position 262 if ob.getType() == 'Armature': 263 # ensure we get the mesh vertices in animation mode, 264 # and not in rest position! 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 # check for non-uniform transforms 273 # (lattices are not exported so ignore them as they often tend 274 # to have non-uniform scaling) 275 if ob.getType() != 'Lattice': 276 try: 277 self.decomposeSRT(ob.getMatrix('localspace')) 278 except NifExportError: # non-uniform scaling 279 raise NifExportError("""\ 280 Non-uniform scaling not supported. 281 Workaround: apply size and rotation (CTRL-A) on '%s'.""" % ob.name) 282 283 # extract some useful scene info 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 # oblivion, Fallout 3 and civ4 291 if (self.EXPORT_VERSION 292 in ('Civilization IV', 'Oblivion', 'Fallout 3')): 293 root_name = 'Scene Root' 294 # other games 295 else: 296 root_name = self.filebase 297 298 # get the root object from selected object 299 # only export empties, meshes, and armatures 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 # smoothen seams of objects 316 if self.EXPORT_SMOOTHOBJECTSEAMS: 317 # get shared vertices 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 #for v in mesh.verts: 324 # v.sel = False 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 # set normals on shared vertices 335 nv = 0 336 for vlist in vdict.itervalues(): 337 if len(vlist) <= 1: continue # not shared 338 meshes = set([mesh for v, f, mesh in vlist]) 339 if len(meshes) <= 1: continue # not shared 340 # take average of all face normals of faces that have this 341 # vertex 342 norm = Blender.Mathutils.Vector(0,0,0) 343 for v, f, mesh in vlist: 344 norm += f.no 345 norm.normalize() 346 # remove outliers (fixes better bodies issue) 347 # first calculate fitness of each face 348 fitlist = [Blender.Mathutils.DotVecs(f.no, norm) 349 for v, f, mesh in vlist] 350 bestfit = max(fitlist) 351 # recalculate normals only taking into account 352 # well-fitting faces 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 # save normal of this vertex 359 for v, f, mesh in vlist: 360 v.no = norm 361 #v.sel = True 362 nv += 1 363 self.logger.info("Fixed normals on %i vertices." % nv) 364 365 ## TODO use Blender actions for animation groups 366 # check for animation groups definition in a text buffer 'Anim' 367 try: 368 animtxt = Blender.Text.Get("Anim") 369 except NameError: 370 animtxt = None 371 372 # rebuild the bone extra matrix dictionary from the 'BoneExMat' text buffer 373 self.rebuildBonesExtraMatrices() 374 375 # rebuild the full name dictionary from the 'FullNames' text buffer 376 self.rebuildFullNames() 377 378 # export nif: 379 #------------ 380 self.msg_progress("Exporting") 381 382 # create a nif object 383 384 # export the root node (the name is fixed later to avoid confusing the 385 # exporter with duplicate names) 386 root_block = self.exportNode(None, 'none', None, '') 387 388 # export objects 389 self.logger.info("Exporting objects") 390 for root_object in root_objects: 391 # export the root objects as a NiNodes; their children are 392 # exported as well 393 # note that localspace = worldspace, because root objects have 394 # no parents 395 self.exportNode(root_object, 'localspace', 396 root_block, root_object.getName()) 397 398 # post-processing: 399 #----------------- 400 401 # if we exported animations, but no animation groups are defined, 402 # define a default animation group 403 self.logger.info("Checking animation groups") 404 if not animtxt: 405 has_controllers = False 406 for block in self.blocks: 407 # has it a controller field? 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 # write the animation group text buffer 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 # animations without keyframe animations crash the TESCS 419 # if we are in that situation, add a trivial keyframe animation 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 # add a trivial keyframe controller on the scene root 430 self.exportKeyframes(None, 'localspace', root_block) 431 432 # oblivion skeleton export: check that all bones have a 433 # transform controller and transform interpolator 434 if self.EXPORT_VERSION in ("Oblivion", "Fallout 3") \ 435 and self.filebase.lower() in ('skeleton', 'skeletonbeast'): 436 # here comes everything that is Oblivion skeleton export 437 # specific 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 # here comes everything that should be exported EXCEPT 465 # for Oblivion skeleton exports 466 # export animation groups (not for skeleton.nif export!) 467 if animtxt: 468 anim_textextra = self.exportAnimGroups(animtxt, root_block) 469 else: 470 anim_textextra = None 471 472 # oblivion and Fallout 3 furniture markers 473 if (self.EXPORT_VERSION in ('Oblivion', 'Fallout 3') 474 and self.filebase[:15].lower() == 'furnituremarker'): 475 # exporting a furniture marker for Oblivion/FO3 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 # name scene root name the file base name 484 root_name = self.filebase 485 # create furniture marker block 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 # create extra string data sgoKeep 493 sgokeep = self.createBlock("NiStringExtraData") 494 sgokeep.name = "UBP" 495 sgokeep.stringData = "sgoKeep" 496 # add extra blocks 497 root_block.addExtraData(furnmark) 498 root_block.addExtraData(sgokeep) 499 500 self.logger.info("Checking collision") 501 # activate oblivion/Fallout 3 collision and physics 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 # enable collision 510 bsx = self.createBlock("BSXFlags") 511 bsx.name = 'BSX' 512 bsx.integerData = self.EXPORT_OB_BSXFLAGS 513 root_block.addExtraData(bsx) 514 # update rigid body center of gravity and mass 515 # first calculate distribution of mass 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 # to avoid zero division error later 524 # (if mass is zero then this does not matter 525 # anyway) 526 total_mass = 1 527 # now update the mass ensuring that total mass is 528 # self.EXPORT_OB_MASS 529 for block in self.blocks: 530 if isinstance(block, NifFormat.bhkRigidBody): 531 mass = self.EXPORT_OB_MASS * block.mass / total_mass 532 # lower bound on mass 533 if mass < 0.0001: 534 mass = 0.05 535 block.updateMassCenterInertia( 536 mass = mass, 537 solid = self.EXPORT_OB_SOLID) 538 539 # many Oblivion nifs have a UPB, but export is disabled as 540 # they do not seem to affect anything in the game 541 #upb = self.createBlock("NiStringExtraData") 542 #upb.name = 'UPB' 543 #upb.stringData = 'Mass = 0.000000\r\nEllasticity = 0.300000\r\nFriction = 0.300000\r\nUnyielding = 0\r\nSimulation_Geometry = 2\r\nProxy_Geometry = <None>\r\nUse_Display_Proxy = 0\r\nDisplay_Children = 1\r\nDisable_Collisions = 0\r\nInactive = 0\r\nDisplay_Proxy = <None>\r\n' 544 #root_block.addExtraData(upb) 545 546 # export constraints 547 for b_obj in self.getExportedObjects(): 548 self.exportConstraints(b_obj, root_block) 549 550 # export weapon location 551 if self.EXPORT_VERSION in ("Oblivion", "Fallout 3"): 552 if self.EXPORT_OB_PRN != "NONE": 553 # add string extra data 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 # add vertex color and zbuffer properties for civ4 and railroads 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 # (warning: trouble if armatures parent other armatures or 573 # if bones parent geometries, or if object is animated) 574 # flatten skins 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 # remove NiNodes that do not affect skin 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 # apply scale 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 # also scale egm 606 if self.egmdata: 607 self.egmdata.apply_scale(self.EXPORT_SCALE_CORRECTION) 608 609 # generate mopps (must be done after applying scale!) 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 #print "=== DEBUG: MOPP TREE ===" 616 #block.parseMopp(verbose = True) 617 #print "=== END OF MOPP TREE ===" 618 619 # delete original scene root if a scene root object was already 620 # defined 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 # remove root_block from self.blocks 626 self.blocks.pop(root_block) 627 # set new root block 628 old_root_block = root_block 629 root_block = old_root_block.children[0] 630 # copy extra data and properties 631 for extra in old_root_block.getExtraDatas(): 632 # delete links in extras to avoid parentship problems 633 extra.nextExtraData = None 634 # now add it 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 # making root block a fade node 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 # figure out user version and user version 2 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 # export nif file: 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 # make sure we have the right file extension 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 # create and export keyframe file and xnif file: 692 #----------------------------------------------- 693 694 # convert root_block tree into a keyframe tree 695 if self.EXPORT_ANIMATION == 2 or self.EXPORT_MW_NIFXNIFKF: 696 self.logger.info("Creating keyframe tree") 697 # find all nodes and relevant controllers 698 node_kfctrls = {} 699 for node in root_block.tree(): 700 if not isinstance(node, NifFormat.NiAVObject): 701 continue 702 # get list of all controllers for this node 703 ctrls = node.getControllers() 704 for ctrl in ctrls: 705 if self.EXPORT_VERSION == "Morrowind": 706 # morrowind: only keyframe controllers 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 # morrowind 714 if self.EXPORT_VERSION == "Morrowind": 715 # create kf root header 716 kf_root = self.createBlock("NiSequenceStreamHelper") 717 kf_root.addExtraData(anim_textextra) 718 # reparent controller tree 719 for node, ctrls in node_kfctrls.iteritems(): 720 for ctrl in ctrls: 721 # create node reference by name 722 nodename_extra = self.createBlock( 723 "NiStringExtraData") 724 nodename_extra.bytesRemaining = len(node.name) + 4 725 nodename_extra.stringData = node.name 726 727 # break the controller chain 728 ctrl.nextController = None 729 730 # add node reference and controller 731 kf_root.addExtraData(nodename_extra) 732 kf_root.addController(ctrl) 733 # wipe controller target 734 ctrl.target = None 735 # oblivion 736 elif self.EXPORT_VERSION in ("Oblivion", "Fallout 3", 737 "Civilization IV", "Zoo Tycoon 2"): 738 # create kf root header 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 # quick hack to set correct target name 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 # export a block for every interpolator in every 763 # controller 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 # create ControlledLink for each 780 # interpolator 781 controlledblock = kf_root.addControlledBlock() 782 if self.version < 0x0A020000: 783 # older versions need the actual controller 784 # blocks 785 controlledblock.targetName = node.name 786 controlledblock.controller = ctrl 787 # erase reference to target node 788 ctrl.target = None 789 else: 790 # newer versions need the interpolator 791 # blocks 792 controlledblock.interpolator = interpolator 793 # get bone animation priority (previously 794 # fetched from the constraints during 795 # exportBones) 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 # set palette, and node and controller type 805 # names, and variables 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 # write kf (and xnif if asked) 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 # detach the keyframe controllers from the nif (for xnif) 840 for node in root_block.tree(): 841 if not isinstance(node, NifFormat.NiNode): 842 continue 843 # remove references to keyframe controllers from node 844 # (for xnif) 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 # detach animation text keys 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" # we are in morrowind 'nifxnifkf mode' 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 # export egm file: 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: # export error: raise a menu instead of an exception 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: # IO error: raise a menu instead of an exception 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: # other error: raise a menu and an exception 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 # clear progress bar 914 self.msg_progress("Finished", progbar = 1) 915 916 # save exported file (this is used by the test suite) 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 # ob_type: determine the block type 933 # (None, 'Mesh', 'Empty' or 'Armature') 934 # ob_ipo: object animation ipo 935 # node: contains new NifFormat.NiNode instance 936 if (ob == None): 937 # -> root node 938 assert(parent_block == None) # debug 939 node = self.createBlock("NiNode") 940 ob_type = None 941 ob_ipo = None 942 else: 943 # -> empty, mesh, or armature 944 ob_type = ob.getType() 945 assert(ob_type in ['Empty', 'Mesh', 'Armature']) # debug 946 assert(parent_block) # debug 947 ob_ipo = ob.getIpo() # get animation data 948 ob_children = [child for child in Blender.Object.Get() if child.parent == ob] 949 950 if (node_name == 'RootCollisionNode'): 951 # -> root collision node (can be mesh or empty) 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 # done; stop here 957 elif ob_type == 'Mesh' and ob.name.lower()[:7] == 'bsbound': 958 # add a bounding box 959 self.exportBSBound(ob, parent_block) 960 return None # done; stop here 961 elif ob_type == 'Mesh': 962 # -> mesh data. 963 # If this has children or animations or more than one material 964 # it gets wrapped in a purpose made NiNode. 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 # determine if object tracks camera 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 # done; stop here 979 elif has_ipo or has_children or is_multimaterial or has_track: 980 # -> mesh ninode for the hierarchy to work out 981 if not has_track: 982 node = self.createBlock('NiNode', ob) 983 else: 984 node = self.createBlock('NiBillboardNode', ob) 985 else: 986 # don't create intermediate ninode for this guy 987 self.exportTriShapes(ob, space, parent_block, node_name) 988 # we didn't create a ninode, return nothing 989 return None 990 else: 991 # -> everything else (empty/armature) is a regular node 992 node = self.createBlock("NiNode", ob) 993 994 # set transform on trishapes rather than on NiNode for skinned meshes 995 # this fixes an issue with clothing slots 996 if ob_type == 'Mesh': 997 ob_parent = ob.getParent() 998 if ob_parent and ob_parent.getType() == 'Armature': 999 if ob_ipo: 1000 # mesh with armature parent should not have animation! 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 # make it child of its parent in the nif, if it has one 1012 if parent_block: 1013 parent_block.addChild(node) 1014 1015 # and fill in this node's non-trivial values 1016 node.name = self.getFullName(node_name) 1017 1018 # default node flags 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 # morrowind 1026 node.flags = 0x000C 1027 1028 self.exportMatrix(ob, space, node) 1029 1030 if (ob != None): 1031 # export animation 1032 if (ob_ipo != None): 1033 self.exportKeyframes(ob_ipo, space, node) 1034 1035 # if it is a mesh, export the mesh as trishape children of this ninode 1036 if (ob.getType() == 'Mesh'): 1037 self.exportTriShapes(ob, trishape_space, node) # see definition of trishape_space above 1038 1039 # if it is an armature, export the bones as ninode children of this ninode 1040 elif (ob.getType() == 'Armature'): 1041 self.exportBones(ob, node) 1042 1043 # export all children of this empty/mesh/armature/bone object as children of this NiNode 1044 self.exportChildren(ob, node) 1045 1046 return node
1047 1048 1049 1050 # 1051 # Export the animation of blender Ipo as keyframe controller and 1052 # keyframe data. Extra quaternion is multiplied prior to keyframe 1053 # rotation, and dito for translation. These extra fields come in handy 1054 # when exporting bone ipo's, which are relative to the rest pose, so 1055 # we can pass the rest pose through these extra transformations. 1056 # 1057 # bind_mat is the original Blender bind matrix (the B' matrix below) 1058 # extra_mat_inv is the inverse matrix which transforms the Blender bone matrix 1059 # to the NIF bone matrix (the inverse of the X matrix below) 1060 # 1061 # Explanation of extra transformations: 1062 # Final transformation matrix is vec * Rchannel * Tchannel * Rbind * Tbind 1063 # So we export: 1064 # [ SRchannel 0 ] [ SRbind 0 ] [ SRchannel * SRbind 0 ] 1065 # [ Tchannel 1 ] * [ Tbind 1 ] = [ Tchannel * SRbind + Tbind 1 ] 1066 # or, in detail, 1067 # Stotal = Schannel * Sbind 1068 # Rtotal = Rchannel * Rbind 1069 # Ttotal = Tchannel * Sbind * Rbind + Tbind 1070 # We also need the conversion of the new bone matrix to the original matrix, say X, 1071 # B' = X * B 1072 # (with B' the Blender matrix and B the NIF matrix) because we need that 1073 # C' * B' = X * C * B 1074 # and therefore 1075 # C * B = inverse(X) * C' * B' 1076 # (we need to write out C * B, the NIF format stores total transformation in keyframes). 1077 # In detail: 1078 # [ SRX 0 ] [ SRC' 0 ] [ SRB' 0 ] 1079 # inverse( [ TX 1 ] ) * [ TC' 1 ] * [ TB' 1 ] = 1080 # [ inverse(SRX) 0 ] [ SRC' * SRB' 0 ] 1081 # [ -TX * inverse(SRX) 1 ] * [ TC' * SRB' + TB' 1 ] = 1082 # [ inverse(SRX) * SRC' * SRB' 0 ] 1083 # [ (-TX * inverse(SRX) * SRC' + TC') * SRB' + TB' 1 ] 1084 # Hence 1085 # S = SC' * SB' / SX 1086 # R = inverse(RX) * RC' * RB' 1087 # T = - TX * inverse(RX) * RC' * RB' * SC' * SB' / SX + TC' * SB' * RB' + TB' 1088 # 1089 # Finally, note that 1090 # - TX * inverse(RX) / SX = translation part of inverse(X) 1091 # inverse(RX) = rotation part of inverse(X) 1092 # 1 / SX = scale part of inverse(X) 1093 # so having inverse(X) around saves on calculations
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 # keyframe controllers are not present in geometry only files 1098 # for more recent versions, the controller and interpolators are 1099 # present, only the data is not present (see further on) 1100 return 1101 1102 # only localspace keyframes need to be exported 1103 assert(space == 'localspace') 1104 1105 # make sure the parent is of the right type 1106 assert(isinstance(parent_block, NifFormat.NiNode)) 1107 1108 # add a keyframecontroller block, and refer to this block in the 1109 # parent's time controller 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 # link interpolator from the controller 1116 kfc.interpolator = kfi 1117 # set interpolator default data 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 # determine cycle mode for this controller 1132 # this is stored in the blender ipo curves 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 # dummy ipo 1144 extend = Blender.IpoCurve.ExtendTypes.CYCLIC 1145 1146 # fill in the non-trivial values 1147 # 0x0008 = active + clamp mode 1148 # 0x0012 = active + cycle mode 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 # keyframe data is not present in geometry files 1157 return 1158 1159 # -> get keyframe information 1160 1161 # some calculations 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 # sometimes we need to export an empty keyframe... this will take care of that 1181 if (ipo == None): 1182 scale_curve = {} 1183 rot_curve = {} 1184 trans_curve = {} 1185 # the usual case comes now... 1186 else: 1187 # merge the animation curves into a rotation vector and translation vector curve 1188 scale_curve = {} 1189 rot_curve = {} 1190 trans_curve = {} 1191 # the following code makes these assumptions 1192 assert(Ipo.PO_SCALEX == Ipo.OB_SCALEX) 1193 assert(Ipo.PO_LOCX == Ipo.OB_LOCX) 1194 # check validity of curves 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 # skip invalid curves 1201 try: 1202 ipo[curvecollection[0]] 1203 except KeyError: 1204 continue 1205 # check that if any curve is defined in the collection 1206 # then all curves are defined in the collection 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 # go over all curves 1217 ipo_curves = ipo.curveConsts.values() 1218 for curve in ipo_curves: 1219 # skip empty curves 1220 if ipo[curve] is None: 1221 continue 1222 # non-empty curve: go over all frames of the curve 1223 for btriple in ipo[curve].bezierPoints: 1224 frame = btriple.pt[0] 1225 if (frame < self.fstart) or (frame > self.fend): 1226 continue 1227 # PO_SCALEX == OB_SCALEX, so this does both pose and object 1228 # scale 1229 if curve in (Ipo.PO_SCALEX, Ipo.PO_SCALEY, Ipo.PO_SCALEZ): 1230 # support only uniform scaling... take the mean 1231 scale_curve[frame] = (ipo[Ipo.PO_SCALEX][frame] 1232 + ipo[Ipo.PO_SCALEY][frame] 1233 + ipo[Ipo.PO_SCALEZ][frame]) / 3.0 1234 # SC' * SB' / SX 1235 scale_curve[frame] = \ 1236 scale_curve[frame] * bind_scale * extra_scale_inv 1237 # object rotation 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 # use quat if we have bind matrix and/or extra matrix 1244 # XXX maybe we should just stick with eulers?? 1245 if bind_mat or extra_mat_inv: 1246 rot_curve[frame] = rot_curve[frame].toQuat() 1247 # beware, CrossQuats takes arguments in a counter-intuitive order: 1248 # q1.toMatrix() * q2.toMatrix() == CrossQuats(q2, q1).toMatrix() 1249 rot_curve[frame] = Blender.Mathutils.CrossQuats(Blender.Mathutils.CrossQuats(bind_quat, rot_curve[frame]), extra_quat_inv) # inverse(RX) * RC' * RB' 1250 # pose rotation 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 # beware, CrossQuats takes arguments in a counter-intuitive order: 1259 # q1.toMatrix() * q2.toMatrix() == CrossQuats(q2, q1).toMatrix() 1260 rot_curve[frame] = Blender.Mathutils.CrossQuats(Blender.Mathutils.CrossQuats(bind_quat, rot_curve[frame]), extra_quat_inv) # inverse(RX) * RC' * RB' 1261 # PO_LOCX == OB_LOCX, so this does both pose and object 1262 # location 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 # T = - TX * inverse(RX) * RC' * RB' * SC' * SB' / SX + TC' * SB' * RB' + TB' 1269 trans_curve[frame] *= bind_scale 1270 trans_curve[frame] *= bind_rot 1271 trans_curve[frame] += bind_trans 1272 # we need RC' and SC' 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 # note, PO_SCALEX == OB_SCALEX, so this does both 1288 if ipo[Ipo.PO_SCALEX]: 1289 # support only uniform scaling... take the mean 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 # -> now comes the real export 1300 1301 if (max(len(rot_curve), len(trans_curve), len(scale_curve)) <= 1 1302 and self.version >= 0x0A020000): 1303 # only add data if number of keys is > 1 1304 # (see importer comments with importKfRoot: a single frame 1305 # keyframe denotes an interpolator without further data) 1306 # insufficient keys, so set the data and we're done! 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 # XXX blender weirdness... Euler() is a function!! 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 # ignore scale for now... 1322 kfi.scale = 1.0 1323 # done! 1324 return 1325 1326 # add the keyframe data 1327 if self.version < 0x0A020000: 1328 kfd = self.createBlock("NiKeyframeData", ipo) 1329 kfc.data = kfd 1330 else: 1331 # number of frames is > 1, so add transform data 1332 kfd = self.createBlock("NiTransformData", ipo) 1333 kfi.data = kfd 1334 1335 frames = rot_curve.keys() 1336 frames.sort() 1337 # XXX blender weirdness... Euler() is a function!! 1338 if (frames 1339 and isinstance(rot_curve.values()[0], 1340 Blender.Mathutils.Euler().__class__)): 1341 # eulers 1342 kfd.rotationType = NifFormat.KeyType.XYZ_ROTATION_KEY 1343 kfd.numRotationKeys = 1 # *NOT* len(frames) this crashes the engine! 1344 kfd.xyzRotations[0].numKeys = len(frames) 1345 kfd.xyzRotations[1].numKeys = len(frames) 1346 kfd.xyzRotations[2].numKeys = len(frames) 1347 # XXX todo: quadratic interpolation? 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 # XXX todo: speed up by not recalculating stuff 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 # quaternions 1367 # XXX todo: quadratic interpolation? 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
1401 - def exportVertexColorProperty(self, block_parent, 1402 flags = 1, 1403 vertex_mode = 0, lighting_mode = 1):
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 # create new vertex color property block 1414 vcolprop = self.createBlock("NiVertexColorProperty") 1415 1416 # make it a property of the parent 1417 block_parent.addProperty(vcolprop) 1418 1419 # and now export the parameters 1420 vcolprop.flags = flags 1421 vcolprop.vertexMode = vertex_mode 1422 vcolprop.lightingMode = lighting_mode 1423 1424 return vcolprop
1425
1426 - def exportZBufferProperty(self, block_parent, 1427 flags = 15, function = 3):
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 # create new z-buffer property block 1437 zbuf = self.createBlock("NiZBufferProperty") 1438 1439 # make it a property of the parent 1440 block_parent.addProperty(zbuf) 1441 1442 # and now export the parameters 1443 zbuf.flags = 15 1444 zbuf.function = 3 1445 1446 return zbuf
1447
1448 - def exportAnimGroups(self, animtxt, block_parent):
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 # animation group extra data is not present in geometry only files 1454 return 1455 1456 self.logger.info("Exporting animation groups") 1457 # -> get animation groups information 1458 1459 # parse the anim text descriptor 1460 1461 # the format is: 1462 # frame/string1[/string2[.../stringN]] 1463 1464 # example: 1465 # 001/Idle: Start/Idle: Stop/Idle2: Start/Idle2: Loop Start 1466 # 051/Idle2: Stop/Idle3: Start 1467 # 101/Idle3: Loop Start/Idle3: Stop 1468 1469 slist = animtxt.asLines() 1470 flist = [] 1471 dlist = [] 1472 for s in slist: 1473 # ignore empty lines 1474 if not s: 1475 continue 1476 # parse line 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 #print 'frame %d'%f + ' -> \'%s\''%d # debug 1489 flist.append(f) 1490 dlist.append(d) 1491 1492 # -> now comes the real export 1493 1494 # add a NiTextKeyExtraData block, and refer to this block in the 1495 # parent node (we choose the root block) 1496 textextra = self.createBlock("NiTextKeyExtraData", animtxt) 1497 block_parent.addExtraData(textextra) 1498 1499 # create a text key for each frame descriptor 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 # this works for morrowind only 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 # get filename from image 1523 1524 # check that image is loaded 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 # warn if packed flag is enabled 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 # try and find a DDS alternative, force it if required 1540 ddsfilename = "%s%s" % (filename[:-4], '.dds') 1541 if Blender.sys.exists(ddsfilename) or self.EXPORT_FORCEDDS: 1542 filename = ddsfilename 1543 1544 # sanitize file path 1545 if not self.EXPORT_VERSION in ('Morrowind', 'Oblivion', 1546 'Fallout 3'): 1547 # strip texture file path 1548 filename = Blender.sys.basename(filename) 1549 else: 1550 # strip the data files prefix from the texture's file name 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 # for linux export: fix path seperators 1559 return filename.replace('/', '\\') 1560 else: 1561 # texture must be of type IMAGE or ENVMAP 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 # create NiSourceTexture 1577 srctex = NifFormat.NiSourceTexture() 1578 srctex.useExternal = True 1579 if not filename is None: 1580 # preset filename 1581 srctex.fileName = filename 1582 elif not texture is None: 1583 srctex.fileName = self.exportTextureFilename(texture) 1584 else: 1585 # this probably should not happen 1586 logger.warning( 1587 "Exporting source texture without texture or filename (bug?).") 1588 1589 # fill in default values (TODO: can we use 6 for everything?) 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 # search for duplicate 1599 for block in self.blocks: 1600 if isinstance(block, NifFormat.NiSourceTexture) and block.getHash() == srctex.getHash(): 1601 return block 1602 1603 # no identical source texture found, so use and register 1604 # the new one 1605 return self.registerBlock(srctex, texture)
1606 1607 1608 1609 ## TODO port code to use native Blender texture flipping system 1610 # 1611 # export a NiFlipController 1612 # 1613 # fliptxt is a blender text object containing the flip definitions 1614 # texture is the texture object in blender ( texture is used to checked for pack and mipmap flags ) 1615 # target is the NiTexturingProperty 1616 # target_tex is the texture to flip ( 0 = base texture, 4 = glow texture ) 1617 # 1618 # returns exported NiFlipController 1619 #
1620 - def exportFlipController(self, fliptxt, texture, target, target_tex):
1621 tlist = fliptxt.asLines() 1622 1623 # create a NiFlipController 1624 flip = self.createBlock("NiFlipController", fliptxt) 1625 target.addController(flip) 1626 1627 # fill in NiFlipController's values 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 # skip empty lines 1636 # create a NiSourceTexture for each flip 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 # Export a blender object ob of the type mesh, child of nif block 1650 # parent_block, as NiTriShape and NiTriShapeData blocks, possibly 1651 # along with some NiTexturingProperty, NiSourceTexture, 1652 # NiMaterialProperty, and NiAlphaProperty blocks. We export one 1653 # trishape block per mesh material. We also export vertex weights. 1654 # 1655 # The parameter trishape_name passes on the name for meshes that 1656 # should be exported as a single mesh. 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 # get mesh from ob 1664 mesh = ob.getData(mesh=1) # get mesh data 1665 1666 # getVertsFromGroup fails if the mesh has no vertices 1667 # (this happens when checking for fallout 3 body parts) 1668 # so quickly catch this (rare!) case 1669 if len(ob.data.verts) == 0: 1670 # do not export anything 1671 self.logger.warn("%s has no vertices, skipped." % ob) 1672 return 1673 1674 # get the mesh's materials, this updates the mesh material list 1675 if not isinstance(parent_block, NifFormat.RootCollisionNode): 1676 mesh_mats = mesh.materials 1677 else: 1678 # ignore materials on collision trishapes 1679 mesh_mats = [] 1680 # if the mesh has no materials, all face material indices should be 0, so it's ok to fake one material in the material list 1681 if (mesh_mats == []): 1682 mesh_mats = [None] 1683 1684 # is mesh double sided? 1685 mesh_doublesided = (mesh.mode & Blender.Mesh.Modes.TWOSIDED) 1686 1687 # let's now export one trishape for every mesh material 1688 ### TODO: needs refactoring - move material, texture, etc. 1689 ### to separate function 1690 for materialIndex, mesh_mat in enumerate(mesh_mats): 1691 # -> first, extract valuable info from our ob 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 = [] # uv layers used by this material 1702 mesh_hasalpha = False # mesh has transparency 1703 mesh_haswire = False # mesh rendered as wireframe 1704 mesh_hasspec = False # mesh has specular properties 1705 mesh_hasvcol = False 1706 mesh_hasnormals = False 1707 if (mesh_mat != None): 1708 mesh_hasnormals = True # for proper lighting 1709 # for non-textured materials, vertex colors are used to color 1710 # the mesh 1711 # for textured materials, they represent lighting details 1712 mesh_hasvcol = mesh.vertexColors 1713 # read the Blender Python API documentation to understand this hack 1714 mesh_mat_ambient = mesh_mat.getAmb() # 'Amb' scrollbar in blender (MW -> 1.0 1.0 1.0) 1715 mesh_mat_diffuse_color = mesh_mat.getRGBCol() # 'Col' colour in Blender (MW -> 1.0 1.0 1.0) 1716 mesh_mat_specular_color = mesh_mat.getSpecCol() # 'Spe' colour in Blender (MW -> 0.0 0.0 0.0) 1717 specval = mesh_mat.getSpec() # 'Spec' slider in Blender 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() # 'Emit' scrollbar in Blender (MW -> 0.0 0.0 0.0) 1729 mesh_mat_glossiness = mesh_mat.getHardness() / 4.0 # 'Hardness' scrollbar in Blender, takes values between 1 and 511 (MW -> 0.0 - 128.0) 1730 mesh_mat_transparency = mesh_mat.getAlpha() # 'A(lpha)' scrollbar in Blender (MW -> 1.0) 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 # default 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 # special case for Fallout 3 (it does not store diffuse color) 1747 # if emit is non-zero, set emissive color to diffuse 1748 # (otherwise leave the color to zero) 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 # the base texture = first material texture 1753 # note that most morrowind files only have a base texture, so let's for now only support single textured materials 1754 for mtex in mesh_mat.getTextures(): 1755 if not mtex: 1756 # skip empty texture slots 1757 continue 1758 1759 # check REFL-mapped textures 1760 # (used for "NiTextureEffect" materials) 1761 if mtex.texco == Blender.Texture.TexCo.REFL: 1762 # of course the user should set all kinds of other 1763 # settings to make the environment mapping come out 1764 # (MapTo "COL", blending mode "Add") 1765 # but let's not care too much about that 1766 # only do some simple checks 1767 if (mtex.mapto & Blender.Texture.MapTo.COL) == 0: 1768 # it should map to colour 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 # it should have "ADD" blending mode 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 # an envmap image should have an empty... don't care 1780 mesh_texeff_mtex = mtex 1781 1782 # check UV-mapped textures 1783 elif mtex.texco == Blender.Texture.TexCo.UV: 1784 # update set of uv layers that must be exported 1785 uvlayer = ( mtex.uvlayer if mtex.uvlayer 1786 else mesh.activeUVLayer ) 1787 if not uvlayer in mesh_uvlayers: 1788 mesh_uvlayers.append(uvlayer) 1789 # check which texture slot this mtex belongs to 1790 if mtex.mapto & Blender.Texture.MapTo.EMIT: 1791 # got the glow tex 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 # check if calculation of alpha channel is enabled 1797 # for this texture 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 # got the gloss map 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 # got the normal map 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 # got the dark map 1822 mesh_dark_mtex = mtex 1823 elif mtex.mapto & Blender.Texture.MapTo.COL and \ 1824 not mesh_base_mtex: 1825 # anything else that maps to COL is considered 1826 # as base texture 1827 mesh_base_mtex = mtex 1828 # check if alpha channel is enabled for this texture 1829 if (mesh_base_mtex.tex.imageFlags & Blender.Texture.ImageFlags.USEALPHA != 0) and (mtex.mapto & Blender.Texture.MapTo.ALPHA != 0): 1830 # in this case, Blender replaces the texture transparant parts with the underlying material color... 1831 # in NIF, material alpha is multiplied with texture alpha channel... 1832 # how can we emulate the NIF alpha system (simply multiplying material alpha with texture alpha) when MapTo.ALPHA is turned on? 1833 # require the Blender material alpha to be 0.0 (no material color can show up), and use the "Var" slider in the texture blending mode tab! 1834 # but... 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 # we must use the "Var" value 1845 mesh_hasalpha = True 1846 elif mtex.mapto & Blender.Texture.MapTo.COL and \ 1847 not mesh_detail_mtex: 1848 # extra COL channel is considered 1849 # as detail texture 1850 mesh_detail_mtex = mtex 1851 elif mtex.mapto & Blender.Texture.MapTo.REF: 1852 # got the reflection map 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 # unknown map 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 # nif only support UV-mapped textures 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 # list of body part (name, index, vertices) in this mesh 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 # -> now comes the real export 1887 1888 # We now extract vertices, uv-vertices, normals, and vertex 1889 # colors from the mesh's face list. NIF has one uv vertex and 1890 # one normal per vertex, unlike blender's uv vertices and 1891 # normals per face... therefore some vertices must be 1892 # duplicated. The following algorithm extracts all unique 1893 # (vert, uv-vert, normal, vcol) quads, and uses this list to 1894 # produce the list of vertices, uv-vertices, normals, vertex 1895 # colors, and face indices. 1896 1897 # Blender only supports one set of uv coordinates per mesh; 1898 # therefore, we shall have trouble when importing 1899 # multi-textured trishapes in blender. For this export script, 1900 # no problem: we must simply duplicate the uv vertex list. 1901 1902 # NIF uses the normal table for lighting. So, smooth faces 1903 # should use Blender's vertex normals, and solid faces should 1904 # use Blender's face normals. 1905 1906 vertquad_list = [] # (vertex, uv coordinate, normal, vertex color) list 1907 vertmap = [None for i in xrange(len(mesh.verts))] # blender vertex -> nif vertices 1908 vertlist = [] 1909 normlist = [] 1910 vcollist = [] 1911 uvlist = [] 1912 trilist = [] 1913 # for each face in trilist, a body part index 1914 bodypartfacemap = [] 1915 faces_without_bodypart = [] 1916 for f in mesh.faces: 1917 # does the face belong to this trishape? 1918 if (mesh_mat != None): # we have a material 1919 if (f.mat != materialIndex): # but this face has another material 1920 continue # so skip this face 1921 f_numverts = len(f.v) 1922 if (f_numverts < 3): continue # ignore degenerate faces 1923 assert((f_numverts == 3) or (f_numverts == 4)) # debug 1924 if mesh_uvlayers: 1925 # if we have uv coordinates 1926 # double check that we have uv data 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 # find (vert, uv-vert, normal, vcol) quad, and if not found, create it 1931 f_index = [ -1 ] * f_numverts 1932 for i in range(f_numverts): 1933 fv = f.v[i].co 1934 # get vertex normal for lighting (smooth = Blender vertex normal, non-smooth = Blender face normal) 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 # NIF stores the colour values as floats 1953 fcol = f.col[i] 1954 else: 1955 fcol = None 1956 1957 vertquad = ( fv, fuv, fn, fcol ) 1958 1959 # do we already have this quad? (optimized by m4444x) 1960 f_index[i] = len(vertquad_list) 1961 v_index = f.v[i].index 1962 if vertmap[v_index]: 1963 # iterate only over vertices with the same vertex index 1964 # and check if they have the same uvs, normals and colors (wow is that fast!) 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 # all tests passed: so yes, we already have it! 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 # first: add it to the vertex map 1996 if not vertmap[v_index]: 1997 vertmap[v_index] = [] 1998 vertmap[v_index].append(len(vertquad_list)) 1999 # new (vert, uv-vert, normal, vcol) quad: add it 2000 vertquad_list.append(vertquad) 2001 # add the vertex 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 # now add the (hopefully, convex) face, in triangles 2007 for i in range(f_numverts - 2): 2008 if True: #TODO: #(ob_scale > 0): 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 # add body part number 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 # this signals an error 2026 faces_without_bodypart.append(f) 2027 2028 # check that there are no missing body part faces 2029 if faces_without_bodypart: 2030 Blender.Window.EditMode(0) 2031 # select mesh object 2032 for bobj in self.scene.objects: 2033 bobj.sel = False 2034 self.scene.objects.active = ob 2035 ob.sel = 1 2036 # select bad faces 2037 for face in mesh.faces: 2038 face.sel = 0 2039 for face in faces_without_bodypart: 2040 face.sel = 1 2041 # switch to edit mode and raise exception 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 # m4444x: skip 'empty' material indices 2053 2054 # note: we can be in any of the following five situations 2055 # material + base texture -> normal object 2056 # material + base tex + glow tex -> normal glow mapped object 2057 # material + glow texture -> (needs to be tested) 2058 # material, but no texture -> uniformly coloured object 2059 # no material -> typically, collision mesh 2060 2061 # create a trishape block 2062 if not self.EXPORT_STRIPIFY: 2063 trishape = self.createBlock("NiTriShape", ob) 2064 else: 2065 trishape = self.createBlock("NiTriStrips", ob) 2066 2067 # add texture effect block (must be added as preceeding child of 2068 # the trishape) 2069 if self.EXPORT_VERSION == "Morrowind" and mesh_texeff_mtex: 2070 # create a new parent block for this shape 2071 extra_node = self.createBlock("NiNode", mesh_texeff_mtex) 2072 parent_block.addChild(extra_node) 2073 # set default values for this ninode 2074 extra_node.rotation.setIdentity() 2075 extra_node.scale = 1.0 2076 extra_node.flags = 0x000C # morrowind 2077 # create texture effect block and parent the 2078 # texture effect and trishape to it 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 # refer to this block in the parent's 2085 # children list 2086 parent_block.addChild(trishape) 2087 2088 # fill in the NiTriShape's non-trivial values 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 # multimaterial meshes: add material index 2100 # (Morrowind's child naming convention) 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 # morrowind 2110 if ob.getDrawType() != 2: # not wire 2111 trishape.flags = 0x0004 # use triangles as bounding box 2112 else: 2113 trishape.flags = 0x0005 # use triangles as bounding box + hide 2114 2115 # extra shader for Sid Meier's Railroads 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 # default 2120 2121 self.exportMatrix(ob, space, trishape) 2122 2123 if mesh_base_mtex or mesh_glow_mtex: 2124 # add NiTriShape's texturing property 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 #glossmtex = mesh_gloss_mtex, 2131 #darkmtex = mesh_dark_mtex, 2132 #detailmtex = mesh_detail_mtex)) 2133 else: 2134 if (self.EXPORT_VERSION in self.USED_EXTRA_SHADER_TEXTURES 2135 and self.EXPORT_EXTRA_SHADER_TEXTURES): 2136 # sid meier's railroad and civ4: 2137 # set shader slots in extra data 2138 self.addShaderIntegerExtraDatas(trishape) 2139 trishape.addProperty(self.exportTexturingProperty( 2140 flags=0x0001, # standard 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 # add NiTriShape's alpha propery 2153 # refer to the alpha property in the trishape block 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 # add NiWireframeProperty 2166 trishape.addProperty(self.exportWireframeProperty(flags=1)) 2167 2168 if mesh_doublesided: 2169 # add NiStencilProperty 2170 trishape.addProperty(self.exportStencilProperty()) 2171 2172 if mesh_mat: 2173 # add NiTriShape's specular property 2174 # but NOT for sid meier's railroads and other extra shader 2175 # games (they use specularity even without this property) 2176 if (mesh_hasspec 2177 and (self.EXPORT_VERSION 2178 not in self.USED_EXTRA_SHADER_TEXTURES)): 2179 # refer to the specular property in the trishape block 2180 trishape.addProperty( 2181 self.exportSpecularProperty(flags=0x0001)) 2182 2183 # add NiTriShape's material property 2184 trimatprop = self.exportMaterialProperty( 2185 name=self.getFullName(mesh_mat.getName()), 2186 flags=0x0001, # ? standard 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 # refer to the material property in the trishape block 2196 trishape.addProperty(trimatprop) 2197 2198 2199 # material animation 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 # get the alpha keyframes from blender's ipo curve 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 # add and link alpha controller, data and interpolator blocks 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 # select extrapolation mode 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 # fill in timing values 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 # select interpolation mode and export the alpha curve data 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 # add NiTriShape's data 2259 # NIF flips the texture V-coordinate (OpenGL standard) 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 # flags 2267 tridata.consistencyFlags = NifFormat.ConsistencyType.CT_STATIC 2268 2269 # data 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] # opengl standard 2310 2311 # set triangles 2312 # stitch strips for civ4 2313 tridata.setTriangles(trilist, 2314 stitchstrips=self.EXPORT_STITCHSTRIPS) 2315 2316 # update tangent space (as binary extra data only for Oblivion) 2317 # for extra shader texture games, only export it if those 2318 # textures are actually exported (civ4 seems to be consistent with 2319 # not using tangent space on non shadered nifs) 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 # now export the vertex weights, if there are any 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 # the vertgroups that correspond to bonenames are bones 2336 # that influence the mesh 2337 boneinfluences = [] 2338 for bone in bonenames: 2339 if bone in vertgroups: 2340 boneinfluences.append(bone) 2341 if boneinfluences: # yes we have skinning! 2342 # create new skinning instance block and link it 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 # create skinning data and link it 2359 skindata = self.createBlock("NiSkinData", ob) 2360 skininst.data = skindata 2361 2362 skindata.hasVertexWeights = True 2363 # fix geometry rest pose: transform relative to 2364 # skeleton root 2365 skindata.setTransform( 2366 self.getObjectMatrix(ob, 'localspace').getInverse()) 2367 2368 # add vertex weights 2369 # first find weights and normalization factors 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 # this happens when the vertex group has been 2377 # added, but the weights have not been painted 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 # for each bone, first we get the bone block 2389 # then we get the vertex weights 2390 # and then we add it to the NiSkinData 2391 # note: allocate memory for faster performance 2392 vert_added = [False for i in xrange(len(vertlist))] 2393 for bone_index, bone in enumerate(boneinfluences): 2394 # find bone in exported blocks 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 # find vertex weights 2409 vert_weights = {} 2410 for v in vert_list[bone]: 2411 # v[0] is the original vertex index 2412 # v[1] is the weight 2413 2414 # vertmap[v[0]] is the set of vertices (indices) 2415 # to which v[0] was mapped 2416 # so we simply export the same weight as the 2417 # original vertex for each new vertex 2418 2419 # write the weights 2420 # extra check for multi material meshes 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 # add bone as influence, but only if there were 2426 # actually any vertices influenced by the bone 2427 if vert_weights: 2428 trishape.addBone(bone_block, vert_weights) 2429 2430 # each vertex must have been assigned to at least one 2431 # vertex group 2432 # or the model doesn't display correctly in the TESCS 2433 vert_weights = {} 2434 if False in vert_added: 2435 # select mesh object 2436 for bobj in self.scene.objects: 2437 bobj.sel = False 2438 self.scene.objects.active = ob 2439 ob.sel = 1 2440 # select bad vertices 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 # switch to edit mode and raise exception 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 # update bind position skinning data 2459 trishape.updateBindPosition() 2460 2461 # calculate center and radius for each skin bone data 2462 # block 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 # warn on bad config settings 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 # clean up 2489 del vert_weights 2490 del vert_added 2491 2492 2493 # shape key morphing 2494 key = mesh.key 2495 if key: 2496 if len(key.blocks) > 1: 2497 # yes, there is a key object attached 2498 # export as egm, or as morphdata? 2499 if key.blocks[1].name.startswith("EGM"): 2500 # egm export! 2501 self.exportEgm(key.blocks) 2502 elif key.ipo: 2503 # regular morphdata export 2504 # (there must be a shape ipo) 2505 keyipo = key.ipo 2506 # check that they are relative shape keys 2507 if not key.relative: 2508 # XXX if we do "key.relative = True" 2509 # XXX would this automatically fix the keys? 2510 raise ValueError( 2511 "Can only export relative shape keys.") 2512 2513 # create geometry morph controller 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 # create geometry morph data 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 # create interpolators (for newer nif versions) 2533 morphctrl.numInterpolators = len(key.blocks) 2534 morphctrl.interpolators.updateSize() 2535 2536 # XXX some unknowns, bethesda only 2537 # XXX just guessing here, data seems to be zero always 2538 morphctrl.numUnknownInts = len(key.blocks) 2539 morphctrl.unknownInts.updateSize() 2540 2541 for keyblocknum, keyblock in enumerate(key.blocks): 2542 # export morphed vertices 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 # vertmap check 2552 if not vert_indices: 2553 continue 2554 # copy vertex and assign morph vertex 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 # export ipo shape key curve 2566 curve = keyipo[keyblock.name] 2567 2568 # create interpolator for shape key 2569 # (needs to be there even if there is no curve) 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 # geometry only export has no float data 2577 # to do this conveniently, we just wipe the link... 2578 if self.EXPORT_ANIMATION == 1: 2579 interpol.data = None 2580 2581 # base key has no curve 2582 # but all other keys should have one 2583 if curve: 2584 # note: we set data on morph for older nifs 2585 # and on floatdata for newer nifs 2586 # of course only one of these will be actually 2587 # written to the file 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 #morph.keys[i].forwardTangent = 0.0 # ? 2606 #morph.keys[i].backwardTangent = 0.0 # ? 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 #floatdata.keys[i].forwardTangent = 0.0 # ? 2611 #floatdata.keys[i].backwardTangent = 0.0 # ? 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
2620 - def exportMaterialControllers(self, b_material, n_geom):
2621 """Export material animation data for given geometry.""" 2622 if self.EXPORT_ANIMATION == 1: 2623 # geometry only: don't write controllers 2624 return 2625 2626 self.exportMaterialAlphaController(b_material, n_geom) 2627 self.exportMaterialUVController(b_material, n_geom)
2628
2629 - def exportMaterialAlphaController(self, b_material, n_geom):
2630 """Export the material alpha controller data.""" 2631 # XXX todo: move alpha export here 2632 return
2633
2634 - def exportMaterialUVController(self, b_material, n_geom):
2635 """Export the material UV controller data.""" 2636 # get the material ipo 2637 b_ipo = b_material.ipo 2638 if not b_ipo: 2639 return 2640 # get the uv curves and translate them into nif data 2641 n_uvdata = NifFormat.NiUVData() 2642 n_times = [] # track all times (used later in start time and end time) 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 # XXX todo: set interpolation from blender interpolation 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 # add each point of the curve 2655 b_time, b_value = b_point.pt 2656 if b_channel in (Blender.Ipo.MA_OFSX, Blender.Ipo.MA_OFSY): 2657 # offsets are negated in blender 2658 b_value = -b_value 2659 n_key.time = (b_time - 1) * self.fspeed 2660 n_key.value = b_value 2661 # track time 2662 n_times.append(n_key.time) 2663 # if uv data is present (we check this by checking if times were added) 2664 # then add the controller so it is exported 2665 if n_times: 2666 n_uvctrl = NifFormat.NiUVController() 2667 # XXX todo: set flags from blender cycle value 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 # attach block to geometry 2674 n_geom.addController(n_uvctrl)
2675
2676 - def exportBones(self, arm, parent_block):
2677 """Export the bones of an armature.""" 2678 # the armature was already exported as a NiNode 2679 # now we must export the armature's bones 2680 assert( arm.getType() == 'Armature' ) 2681 2682 # find the root bones 2683 # dictionary of bones (name -> bone) 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() # dictionary of Bone Ipos (name -> ipo) 2694 else: 2695 bones_ipo = {} # no ipos 2696 2697 bones_node = {} # maps bone names to NiNode blocks 2698 2699 # here all the bones are added 2700 # first create all bones with their keyframes 2701 # and then fix the links in a second run 2702 2703 # ok, let's create the bone NiNode blocks 2704 for bone in bones.values(): 2705 # create a new block for this bone 2706 node = self.createBlock("NiNode", bone) 2707 # doing bone map now makes linkage very easy in second run 2708 bones_node[bone.name] = node 2709 2710 # add the node and the keyframe for this bone 2711 node.name = self.getFullName(bone.name) 2712 if self.EXPORT_VERSION in ('Oblivion', 'Fallout 3'): 2713 # default for Oblivion bones 2714 # note: bodies have 0x000E, clothing has 0x000F 2715 node.flags = 0x000E 2716 elif self.EXPORT_VERSION == 'Civilization IV': 2717 if bone.children: 2718 # default for Civ IV bones with children 2719 node.flags = 0x0006 2720 else: 2721 # default for Civ IV final bones 2722 node.flags = 0x0016 2723 else: 2724 node.flags = 0x0002 # default for Morrowind bones 2725 self.exportMatrix(bone, 'localspace', node) # rest pose 2726 2727 # bone rotations are stored in the IPO relative to the rest position 2728 # so we must take the rest position into account 2729 # (need original one, without extra transforms, so extra = False) 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 # does bone have priority value in NULL constraint? 2744 for constr in arm.getPose().bones[bone.name].constraints: 2745 # yes! store it for reference when creating the kf file 2746 if constr.name[:9].lower() == "priority:": 2747 self.bonePriorities[node] = int(constr.name[9:]) 2748 2749 # now fix the linkage between the blocks 2750 for bone in bones.values(): 2751 # link the bone's children to the bone 2752 if bone.children: 2753 self.logger.debug("Linking children of bone %s" % bone.name) 2754 for child in bone.children: 2755 # bone.children returns also grandchildren etc. 2756 # we only want immediate children, so do a parent check 2757 if child.parent.name == bone.name: 2758 bones_node[bone.name].addChild(bones_node[child.name]) 2759 # if it is a root bone, link it to the armature 2760 if not bone.parent: 2761 parent_block.addChild(bones_node[bone.name])
2762 2763 2764
2765 - def exportChildren(self, obj, parent_block):
2766 """Export all children of blender object ob as children of 2767 parent_block.""" 2768 # loop over all obj's children 2769 for ob_child in [ cld for cld in Blender.Object.Get() 2770 if cld.getParent() == obj ]: 2771 # is it a regular node? 2772 if ob_child.getType() in ['Mesh', 'Empty', 'Armature']: 2773 if (obj.getType() != 'Armature'): 2774 # not parented to an armature 2775 self.exportNode(ob_child, 'localspace', 2776 parent_block, ob_child.getName()) 2777 else: 2778 # this object is parented to an armature 2779 # we should check whether it is really parented to the 2780 # armature using vertex weights 2781 # or whether it is parented to some bone of the armature 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 # we should parent the object to the bone instead of 2788 # to the armature 2789 # so let's find that bone! 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 # ok, we should parent to block 2795 # instead of to parent_block 2796 # two problems to resolve: 2797 # - blender bone matrix is not the exported 2798 # bone matrix! 2799 # - blender objects parented to bone have 2800 # extra translation along the Y axis 2801 # with length of the bone ("tail") 2802 # this is handled in the getObjectSRT function 2803 self.exportNode(ob_child, 'localspace', 2804 bone_block, ob_child.getName()) 2805 break 2806 else: 2807 assert(False) # BUG!
2808 2809 2810
2811 - def exportMatrix(self, obj, space, block):
2812 """Set a block's transform matrix to an object's 2813 transformation matrix in rest pose.""" 2814 # decompose 2815 bscale, brot, btrans = self.getObjectSRT(obj, space) 2816 2817 # and fill in the values 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
2837 - def getObjectMatrix(self, obj, space):
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
2868 - def getObjectSRT(self, obj, space = 'localspace'):
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 # TODO remove the space argument, always do local space 2881 # handle the trivial case first 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 # now write out spaces 2890 if (not type(obj) is Blender.Armature.Bone): 2891 mat = Blender.Mathutils.Matrix(obj.getMatrix('localspace')) 2892 bone_parent_name = obj.getParentBoneName() 2893 # if there is a bone parent then the object is parented 2894 # then get the matrix relative to the bone parent head 2895 if bone_parent_name: 2896 # so v * O * T * B' = v * Z * B 2897 # where B' is the Blender bone matrix in armature 2898 # space, T is the bone tail translation, O is the object 2899 # matrix (relative to the head), and B is the nif bone matrix; 2900 # we wish to find Z 2901 2902 # obj.getMatrix('localspace') 2903 # gets the object local transform matrix, relative 2904 # to the armature!! (not relative to the bone) 2905 # so at this point, mat = O * T * B' 2906 # hence it must hold that mat = Z * B, 2907 # or equivalently Z = mat * B^{-1} 2908 2909 # now, B' = X * B, so B^{-1} = B'^{-1} * X 2910 # hence Z = mat * B'^{-1} * X 2911 2912 # first multiply with inverse of the Blender bone matrix 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 # now multiply with the bone correction matrix X 2920 try: 2921 extra = Blender.Mathutils.Matrix( 2922 self.getBoneExtraMatrixInv(bone_parent_name)) 2923 extra.invert() 2924 mat = mat * extra 2925 except KeyError: 2926 # no extra local transform 2927 pass 2928 else: 2929 # bones, get the rest matrix 2930 mat = self.getBoneRestMatrix(obj, 'BONESPACE') 2931 2932 try: 2933 return self.decomposeSRT(mat) 2934 except NifExportError: # non-uniform scaling 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
2942 - def decomposeSRT(self, mat):
2943 """Decompose Blender transform matrix as a scale, rotation matrix, and 2944 translation vector.""" 2945 # get scale components 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 # and fix their sign 2955 if (b_scale_rot.determinant() < 0): 2956 b_scale.negate() 2957 # only uniform scaling 2958 # allow rather large error to accomodate some nifs 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 # get rotation matrix 2965 b_rot = b_scale_rot * (1.0 / b_scale) 2966 # get translation 2967 b_trans = mat.translationPart() 2968 # done! 2969 return b_scale, b_rot, b_trans
2970 2971 2972
2973 - def getBoneRestMatrix(self, bone, space, extra = True, tail = False):
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 # Retrieves the offset from the original NIF matrix, if existing 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 # not sure why extra = True is required here 2999 # but if extra = extra then transforms are messed up, so keep 3000 # for now 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) # bug!
3013 3014 3015
3016 - def createBlock(self, blocktype, b_obj = None):
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
3031 - def registerBlock(self, block, b_obj = None):
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
3046 - def registerBlenderObject(self, block, b_obj):
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
3053 - def exportCollision(self, obj, parent_block):
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 # default 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: # adding collision failed 3075 continue 3076 else: # all nodes failed so add new one 3077 node = self.createBlock("NiNode", obj) 3078 node.setTransform(self.IDENTITY44) 3079 node.name = 'collisiondummy%i' % parent_block.numChildren 3080 node.flags = 0x000E # default 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
3087 - def exportCollisionHelper(self, obj, parent_block):
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 # is it packed 3097 coll_ispacked = (obj.rbShapeBoundType 3098 == Blender.Object.RBShapes['POLYHEDERON']) 3099 3100 # find physics properties 3101 material = self.EXPORT_OB_MATERIAL 3102 layer = self.EXPORT_OB_LAYER 3103 motionsys = self.EXPORT_OB_MOTIONSYSTEM 3104 # copy physics properties from Blender properties, if they exist 3105 for prop in obj.getAllProperties(): 3106 if prop.getName() == 'HavokMaterial': 3107 material = prop.getData() 3108 if isinstance(material, basestring): 3109 # given as a string, not as an integer 3110 material = getattr(NifFormat.HavokMaterial, material) 3111 elif prop.getName() == 'OblivionLayer': 3112 layer = getattr(NifFormat.OblivionLayer, prop.getData()) 3113 #elif prop.getName() == 'MotionSystem': 3114 # ob_mosys = getattr(NifFormat.MotionSystem, prop.getData()) 3115 3116 # if no collisions have been exported yet to this parent_block 3117 # then create new collision tree on parent_block 3118 # bhkCollisionObject -> bhkRigidBody 3119 if not parent_block.collisionObject: 3120 # note: collision settings are taken from lowerclasschair01.nif 3121 if self.EXPORT_OB_LAYER == NifFormat.OblivionLayer.OL_BIPED: 3122 # special collision object for creatures 3123 colobj = self.createBlock("bhkBlendCollisionObject", obj) 3124 colobj.flags = 9 3125 colobj.unknownFloat1 = 1.0 3126 colobj.unknownFloat2 = 1.0 3127 # also add a controller for it 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 # usual collision object 3137 colobj = self.createBlock("bhkCollisionObject", obj) 3138 if self.EXPORT_OB_LAYER == NifFormat.OblivionLayer.OL_ANIM_STATIC: 3139 # animated collision requires flags = 41 3140 colobj.flags = 41 3141 else: 3142 # in all other cases this seems to be enough 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 # will be fixed later 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
3195 - def exportCollisionPacked(self, obj, colbody, layer, material):
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 # the mopp origin, scale, and data are written later 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 # ignore degenerate faces 3246 triangles.append([face.v[i].index for i in (0, 1, 2)]) 3247 # note: face.no is a Python list, not a vector 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
3257 - def exportCollisionSingle(self, obj, colbody, layer, material):
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
3266 - def exportCollisionList(self, obj, colbody, layer, material):
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 # if no collisions have been exported yet to this parent_block 3273 # then create new collision tree on parent_block 3274 # bhkCollisionObject -> bhkRigidBody -> bhkListShape 3275 # (this works in all cases, can be simplified just before 3276 # the file is written) 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
3290 - def exportCollisionObject(self, obj, layer, material):
3291 """Export object obj as box, sphere, capsule, or convex hull. 3292 Note: polyheder is handled by exportCollisionPacked.""" 3293 3294 # find bounding box data 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 # note: collision settings are taken from lowerclasschair01.nif 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 # the translation part must point to the center of the data 3319 # so calculate the center in local coordinates 3320 center = Blender.Mathutils.Vector((minx + maxx) / 2.0, (miny + maxy) / 2.0, (minz + maxz) / 2.0) 3321 # and transform it to global coordinates 3322 center *= hktf 3323 hktf[3][0] = center[0] 3324 hktf[3][1] = center[1] 3325 hktf[3][2] = center[2] 3326 # we need to store the transpose of the matrix 3327 hktf.transpose() 3328 coltf.transform.setRows(*hktf) 3329 # fix matrix for havok coordinate system 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 # fix dimensions for havok coordinate system 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 # take average radius and 3357 # fix for havok coordinate system (6 * 7 = 42) 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 # take average radius 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 # set radius, with correct scale 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 # fix havok coordinate system for radii 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 # convex hull polytope; not in Python API 3397 # bound type has value 5 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 # /= not supported in Python API 3408 3409 # calculate vertices, normals, and distances 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 # remove duplicates through dictionary 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 # sort vertices and normals 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 # enables arrow detection 3446 colhull.unknown6Floats[5] = -0.0 # enables arrow detection 3447 # note: unknown 6 floats are usually all 0 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 # w component is 0 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
3470 - def exportConstraints(self, b_obj, root_block):
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 # bone object has its constraints stored in the posebone 3477 # so now we should get the posebone, but no constraints for 3478 # bones are exported anyway for now 3479 # so skip this object 3480 return 3481 3482 if not hasattr(b_obj, "constraints"): 3483 # skip text buffers etc 3484 return 3485 3486 for b_constr in b_obj.constraints: 3487 # rigid body joints 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 # check that the object is a rigid body 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 # no collision body for this object 3502 raise NifExportError("""\ 3503 Object %s has a rigid body constraint, 3504 but is not exported as collision object""") 3505 # yes there is a rigid body constraint 3506 # is it of a type that is supported? 3507 if b_constr[Blender.Constraint.Settings.CONSTR_RB_TYPE] == 1: 3508 # ball 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 # hinge 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 # parent constraint to hkbody 3533 hkbody.numConstraints += 1 3534 hkbody.constraints.updateSize() 3535 hkbody.constraints[-1] = hkconstraint 3536 3537 # export hkconstraint settings 3538 hkconstraint.numEntities = 2 3539 hkconstraint.entities.updateSize() 3540 hkconstraint.entities[0] = hkbody 3541 # is there a target? 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 # find target's bhkRigidBody 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 # not found 3554 raise NifExportError("""\ 3555 Rigid body target not exported in nif tree, 3556 check that %s is selected during export.""" % targetobj) 3557 # priority 3558 hkconstraint.priority = 1 3559 # extra malleable constraint settings 3560 if isinstance(hkconstraint, NifFormat.bhkMalleableConstraint): 3561 # unknowns 3562 hkconstraint.unknownInt2 = 2 3563 hkconstraint.unknownInt3 = 1 3564 # force required to keep bodies together 3565 # 0.5 seems a good standard value for creatures 3566 hkconstraint.tau = 0.5 3567 # default damping settings 3568 # (cannot access rbDamping in Blender Python API) 3569 hkconstraint.damping = 0.5 3570 3571 # calculate pivot point and constraint matrix 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 # transform pivot point and constraint matrix into bhkRigidBody 3583 # coordinates (also see import_nif.py, the 3584 # NifImport.importHavokConstraints method) 3585 3586 # the pivot point v' is in object coordinates 3587 # however nif expects it in hkbody coordinates, v 3588 # v * R * B = v' * O * T * B' 3589 # with R = rigid body transform (usually unit tf) 3590 # B = nif bone matrix 3591 # O = blender object transform 3592 # T = bone tail matrix (translation in Y direction) 3593 # B' = blender bone matrix 3594 # so we need to cancel out the object transformation by 3595 # v = v' * O * T * B' * B^{-1} * R^{-1} 3596 3597 # for the rotation matrix, we transform in the same way 3598 # but ignore all translation parts 3599 3600 # assume R is unit transform... 3601 3602 # apply object transform relative to the bone head 3603 # (this is O * T * B' * B^{-1} at once) 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 # export hkdescriptor pivot point 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 # export hkdescriptor axes and other parameters 3614 # (also see import_nif.py NifImport.importHavokConstraints) 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 # z axis is the twist vector 3620 hkdescriptor.twistA.x = axis_z[0] 3621 hkdescriptor.twistA.y = axis_z[1] 3622 hkdescriptor.twistA.z = axis_z[2] 3623 # x axis is the plane vector 3624 hkdescriptor.planeA.x = axis_x[0] 3625 hkdescriptor.planeA.y = axis_x[1] 3626 hkdescriptor.planeA.z = axis_x[2] 3627 # angle limits 3628 # take them twist and plane to be 45 deg (3.14 / 4 = 0.8) 3629 hkdescriptor.twistMinAngle = -0.8 3630 hkdescriptor.twistMaxAngle = +0.8 3631 hkdescriptor.planeMinAngle = -0.8 3632 hkdescriptor.planeMaxAngle = +0.8 3633 # same for maximum cone angle 3634 hkdescriptor.coneMaxAngle = +0.8 3635 elif isinstance(hkdescriptor, NifFormat.LimitedHingeDescriptor): 3636 # y axis is the zero angle vector on the plane of rotation 3637 hkdescriptor.perp2AxleInA1.x = axis_y[0] 3638 hkdescriptor.perp2AxleInA1.y = axis_y[1] 3639 hkdescriptor.perp2AxleInA1.z = axis_y[2] 3640 # x axis is the axis of rotation 3641 hkdescriptor.axleA.x = axis_x[0] 3642 hkdescriptor.axleA.y = axis_x[1] 3643 hkdescriptor.axleA.z = axis_x[2] 3644 # z is the remaining axis determining the positive 3645 # direction of rotation 3646 hkdescriptor.perp2AxleInA2.x = axis_z[0] 3647 hkdescriptor.perp2AxleInA2.y = axis_z[1] 3648 hkdescriptor.perp2AxleInA2.z = axis_z[2] 3649 # angle limits 3650 # typically, the constraint on one side is defined 3651 # by the z axis 3652 hkdescriptor.minAngle = 0.0 3653 # the maximum axis is typically about 90 degrees 3654 # 3.14 / 2 = 1.5 3655 hkdescriptor.maxAngle = 1.5 3656 3657 else: 3658 raise ValueError("unknown descriptor %s" 3659 % hkdescriptor.__class__.__name__) 3660 3661 # friction: again, just picking a reasonable value 3662 if isinstance(hkconstraint, 3663 NifFormat.bhkMalleableConstraint): 3664 # malleable typically have 0 3665 # (perhaps because they have a damping parameter) 3666 hkdescriptor.maxFriction = 0.0 3667 else: 3668 # non-malleable typically have 10 3669 hkdescriptor.maxFriction = 10.0 3670 3671 # do AB 3672 hkconstraint.updateAB(root_block)
3673 3674
3675 - def exportAlphaProperty(self, flags=0x00ED, threshold=0):
3676 """Return existing alpha property with given flags, or create new one 3677 if an alpha property with required flags is not found.""" 3678 # search for duplicate 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 # no alpha property with given flag found, so create new one 3684 alphaprop = self.createBlock("NiAlphaProperty") 3685 alphaprop.flags = flags 3686 alphaprop.threshold = threshold 3687 return alphaprop
3688
3689 - def exportSpecularProperty(self, flags = 0x0001):
3690 """Return existing specular property with given flags, or create new one 3691 if a specular property with required flags is not found.""" 3692 # search for duplicate 3693 for block in self.blocks: 3694 if isinstance(block, NifFormat.NiSpecularProperty) \ 3695 and block.flags == flags: 3696 return block 3697 # no specular property with given flag found, so create new one 3698 specprop = self.createBlock("NiSpecularProperty") 3699 specprop.flags = flags 3700 return specprop
3701
3702 - def exportWireframeProperty(self, flags = 0x0001):
3703 """Return existing wire property with given flags, or create new one 3704 if an wire property with required flags is not found.""" 3705 # search for duplicate 3706 for block in self.blocks: 3707 if isinstance(block, NifFormat.NiWireframeProperty) \ 3708 and block.flags == flags: 3709 return block 3710 # no alpha property with given flag found, so create new one 3711 wireprop = self.createBlock("NiWireframeProperty") 3712 wireprop.flags = flags 3713 return wireprop
3714
3715 - def exportStencilProperty(self):
3716 """Return existing stencil property with given flags, or create new one 3717 if an identical stencil property.""" 3718 # search for duplicate 3719 for block in self.blocks: 3720 if isinstance(block, NifFormat.NiStencilProperty): 3721 # all these blocks have the same setting, no further check 3722 # is needed 3723 return block 3724 # no stencil property found, so create new one 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 # create block (but don't register it yet in self.blocks) 3740 matprop = NifFormat.NiMaterialProperty() 3741 3742 # list which determines whether the material name is relevant or not 3743 # only for particular names this holds, such as EnvMap2 3744 # by default, the material name does not affect rendering 3745 specialnames = ("EnvMap2", "EnvMap", "skin", "Hair", 3746 "dynalpha", "HideSecret", "Lava") 3747 3748 # hack to preserve EnvMap2, skinm, ... named blocks (even if they got 3749 # renamed to EnvMap2.xxx or skin.xxx on import) 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 # clear noname materials 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 # search for duplicate 3783 # (ignore the name string as sometimes import needs to create different 3784 # materials even when NiMaterialProperty is the same) 3785 for block in self.blocks: 3786 if not isinstance(block, NifFormat.NiMaterialProperty): 3787 continue 3788 # when optimization is enabled, ignore material name 3789 if self.EXPORT_OPTIMIZE_MATERIALS: 3790 ignore_strings = not(block.name in specialnames) 3791 else: 3792 ignore_strings = False 3793 # check hash 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 # no material property with given settings found, so use and register 3803 # the new one 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: # mtex.uvlayer not in uvlayers list 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 # assume 0 is active layer 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 # sid meier's railroads: 3835 # some textures end up in the shader texture list 3836 # there are 5 slots available, so set them up 3837 texprop.numShaderTextures = 5 3838 texprop.shaderTextures.updateSize() 3839 for mapindex, shadertexdesc in enumerate(texprop.shaderTextures): 3840 # set default values 3841 shadertexdesc.isUsed = False 3842 shadertexdesc.mapIndex = mapindex 3843 3844 # some texture slots required by the engine 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 # the other slots are exported below 3856 3857 elif self.EXPORT_VERSION == "Civilization IV": 3858 # some textures end up in the shader texture list 3859 # there are 4 slots available, so set them up 3860 texprop.numShaderTextures = 4 3861 texprop.shaderTextures.updateSize() 3862 for mapindex, shadertexdesc in enumerate(texprop.shaderTextures): 3863 # set default values 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 # check for texture flip definition 3873 try: 3874 fliptxt = Blender.Text.Get(basemtex.tex.getName()) 3875 except NameError: 3876 pass 3877 else: 3878 # texture slot 0 = base 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 #texprop.hasRefTexture = True 3934 #self.exportTexDesc(texdesc = texprop.refTexture, 3935 # uvlayers = uvlayers, 3936 # mtex = refmtex) 3937 else: 3938 shadertexdesc = texprop.shaderTextures[3] 3939 shadertexdesc.isUsed = True 3940 shadertexdesc.textureData.source = \ 3941 self.exportSourceTexture(texture=refmtex.tex) 3942 3943 # search for duplicate 3944 for block in self.blocks: 3945 if isinstance(block, NifFormat.NiTexturingProperty) \ 3946 and block.getHash() == texprop.getHash(): 3947 return block 3948 3949 # no texturing property with given settings found, so use and register 3950 # the new one 3951 return self.registerBlock(texprop)
3952
3953 - def exportBSShaderProperty( 3954 self, basemtex=None, bumpmtex=None, glowmtex=None):
3955 """Export a Bethesda shader property block.""" 3956 3957 # create new block 3958 bsshader = NifFormat.BSShaderPPLightingProperty() 3959 # set shader options 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 # set textures 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 # search for duplicates 3978 # DISABLED: the Fallout 3 engine cannot handle them 3979 #for block in self.blocks: 3980 # if (isinstance(block, NifFormat.BSShaderPPLightingProperty) 3981 # and block.getHash() == bsshader.getHash()): 3982 # return block 3983 3984 # no duplicate found, so use and register new one 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
4007 - def exportBSBound(self, obj, block_parent):
4008 """Export an Oblivion bounding box.""" 4009 bbox = self.createBlock("BSBound") 4010 # ... the following incurs double scaling because it will be added in 4011 # both the extra data list and in the old extra data sequence!!! 4012 #block_parent.addExtraData(bbox) 4013 # quick hack (better solution would be to make applyScale non-recursive) 4014 block_parent.numExtraDataList += 1 4015 block_parent.extraDataList.updateSize() 4016 block_parent.extraDataList[-1] = bbox 4017 4018 bbox.name = "BBX" 4019 # calculate bounding box extents 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 # set the center and dimensions 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
4035 - def addShaderIntegerExtraDatas(self, trishape):
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
4041 - def exportEgm(self, keyblocks):
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 # note: keyblocks[0] is base key 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
4057 -def config_callback(**config):
4058 """Called when config script is done. Starts and times import.""" 4059 starttime = Blender.sys.time() 4060 # run exporter 4061 exporter = NifExport(**config) 4062 # finish export 4063 exporter.logger.info('Finished in %.2f seconds' 4064 % (Blender.sys.time() - starttime)) 4065 Blender.Window.WaitCursor(0)
4066
4067 -def fileselect_callback(filename):
4068 """Called once file is selected. Starts config GUI.""" 4069 global _CONFIG 4070 _CONFIG.run(NifConfig.TARGET_EXPORT, filename, config_callback)
4071 4072 if __name__ == '__main__': 4073 # use global config variable so gui elements don't go out of skope 4074 _CONFIG = NifConfig() 4075 # open file selector window, and then call fileselect_callback 4076 Blender.Window.FileSelector( 4077 fileselect_callback, "Export NIF/KF", _CONFIG.config["EXPORT_FILE"]) 4078