Package pyffi :: Package spells :: Package nif :: Module modify
[hide private]
[frames] | no frames]

Source Code for Module pyffi.spells.nif.modify

   1  """ 
   2  :mod:`pyffi.spells.nif.modify` ---  spells to make modifications 
   3  ================================================================= 
   4  Module which contains all spells that modify a nif. 
   5   
   6  .. autoclass:: SpellTexturePath 
   7     :show-inheritance: 
   8     :members: 
   9   
  10  .. autoclass:: SpellSubstituteTexturePath 
  11     :show-inheritance: 
  12     :members: 
  13   
  14  .. autoclass:: SpellLowResTexturePath 
  15     :show-inheritance: 
  16     :members: 
  17   
  18  .. autoclass:: SpellCollisionType 
  19     :show-inheritance: 
  20     :members: 
  21   
  22  .. autoclass:: SpellCollisionMaterial 
  23     :show-inheritance: 
  24     :members: 
  25   
  26  .. autoclass:: SpellScaleAnimationTime 
  27     :show-inheritance: 
  28     :members: 
  29   
  30  .. autoclass:: SpellReverseAnimation 
  31     :show-inheritance: 
  32     :members: 
  33   
  34  .. autoclass:: SpellSubstituteStringPalette 
  35     :show-inheritance: 
  36     :members: 
  37   
  38  .. autoclass:: SpellChangeBonePriorities 
  39     :show-inheritance: 
  40     :members: 
  41   
  42  .. autoclass:: SpellSetInterpolatorTransRotScale 
  43     :show-inheritance: 
  44     :members: 
  45   
  46  .. autoclass:: SpellDelInterpolatorTransformData 
  47     :show-inheritance: 
  48     :members: 
  49   
  50  .. autoclass:: SpellDelBranches 
  51     :show-inheritance: 
  52     :members: 
  53   
  54  .. autoclass:: _SpellDelBranchClasses 
  55     :show-inheritance: 
  56     :members: 
  57   
  58  .. autoclass:: SpellDelSkinShapes 
  59     :show-inheritance: 
  60     :members: 
  61   
  62  .. autoclass:: SpellDisableParallax 
  63     :show-inheritance: 
  64     :members: 
  65   
  66  .. autoclass:: SpellAddStencilProperty 
  67     :show-inheritance: 
  68     :members: 
  69   
  70  .. autoclass:: SpellDelVertexColor 
  71     :show-inheritance: 
  72     :members: 
  73   
  74  .. autoclass:: SpellMakeSkinlessNif 
  75     :show-inheritance: 
  76     :members: 
  77   
  78  .. autoclass:: SpellCleanFarNif 
  79     :show-inheritance: 
  80     :members: 
  81   
  82  .. autoclass:: SpellMakeFarNif 
  83     :show-inheritance: 
  84     :members: 
  85   
  86  """ 
  87   
  88  # -------------------------------------------------------------------------- 
  89  # ***** BEGIN LICENSE BLOCK ***** 
  90  # 
  91  # Copyright (c) 2007-2011, NIF File Format Library and Tools. 
  92  # All rights reserved. 
  93  # 
  94  # Redistribution and use in source and binary forms, with or without 
  95  # modification, are permitted provided that the following conditions 
  96  # are met: 
  97  # 
  98  #    * Redistributions of source code must retain the above copyright 
  99  #      notice, this list of conditions and the following disclaimer. 
 100  # 
 101  #    * Redistributions in binary form must reproduce the above 
 102  #      copyright notice, this list of conditions and the following 
 103  #      disclaimer in the documentation and/or other materials provided 
 104  #      with the distribution. 
 105  # 
 106  #    * Neither the name of the NIF File Format Library and Tools 
 107  #      project nor the names of its contributors may be used to endorse 
 108  #      or promote products derived from this software without specific 
 109  #      prior written permission. 
 110  # 
 111  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 112  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 113  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 114  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 115  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 116  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 117  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 118  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 119  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 120  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 121  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 122  # POSSIBILITY OF SUCH DAMAGE. 
 123  # 
 124  # ***** END LICENSE BLOCK ***** 
 125  # -------------------------------------------------------------------------- 
 126   
 127  from pyffi.formats.nif import NifFormat 
 128  from pyffi.object_models.common import _as_bytes 
 129  from pyffi.spells.nif import NifSpell 
 130  import pyffi.spells.nif 
 131  import pyffi.spells.nif.check # recycle checking spells for update spells 
 132  import pyffi.spells.nif.fix 
 133   
 134  from itertools import izip 
 135  import os 
 136  import re # for modify_substitutestringpalette and modify_substitutetexturepath 
137 138 -class SpellTexturePath( 139 pyffi.spells.nif.fix.SpellParseTexturePath):
140 """Changes the texture path while keeping the texture names.""" 141 142 SPELLNAME = "modify_texturepath" 143 READONLY = False 144 145 @classmethod
146 - def toastentry(cls, toaster):
147 if not toaster.options["arg"]: 148 toaster.logger.warn( 149 "must specify path as argument " 150 "(e.g. -a textures\\pm\\dungeons\\bloodyayleid\\interior) " 151 "to apply spell") 152 return False 153 else: 154 toaster.texture_path = str(toaster.options["arg"]) 155 # standardize the path 156 toaster.texture_path = toaster.texture_path.replace("/", os.sep) 157 toaster.texture_path = toaster.texture_path.replace("\\", os.sep) 158 return True
159
160 - def substitute(self, old_path):
161 # note: replace backslashes by os.sep in filename, and 162 # when joined, revert them back, for linux 163 new_path = os.path.join( 164 self.toaster.texture_path, 165 os.path.basename(old_path.replace("\\", os.sep)) 166 ).replace(os.sep, "\\") 167 if new_path != old_path: 168 self.changed = True 169 self.toaster.msg("%s -> %s" % (old_path, new_path)) 170 return new_path
171
172 -class SpellSubstituteTexturePath( 173 pyffi.spells.nif.fix.SpellFixTexturePath):
174 """Runs a regex replacement on texture paths.""" 175 176 SPELLNAME = "modify_substitutetexturepath" 177 178 @classmethod
179 - def toastentry(cls, toaster):
180 arg = toaster.options["arg"] 181 if not arg: 182 # missing arg 183 toaster.logger.warn( 184 "must specify regular expression and substitution as argument " 185 "(e.g. -a /architecture/city) to apply spell") 186 return False 187 dummy, toaster.regex, toaster.sub = arg.split(arg[0]) 188 toaster.sub = _as_bytes(toaster.sub) 189 toaster.regex = re.compile(_as_bytes(toaster.regex)) 190 return True
191
192 - def substitute(self, old_path):
193 """Returns modified texture path, and reports if path was modified. 194 """ 195 if not old_path: 196 # leave empty path be 197 return old_path 198 new_path = self.toaster.regex.sub(self.toaster.sub, old_path) 199 if old_path != new_path: 200 self.changed = True 201 self.toaster.msg("%s -> %s" % (old_path, new_path)) 202 return new_path
203
204 -class SpellLowResTexturePath(SpellSubstituteTexturePath):
205 """Changes the texture path by replacing 'textures\\*' with 206 'textures\\lowres\\*' - used mainly for making _far.nifs 207 """ 208 209 SPELLNAME = "modify_texturepathlowres" 210 211 @classmethod
212 - def toastentry(cls, toaster):
213 toaster.sub = _as_bytes("textures\\\\lowres\\\\") 214 toaster.regex = re.compile(_as_bytes("^textures\\\\"), re.IGNORECASE) 215 return True
216
217 - def substitute(self, old_path):
218 if (_as_bytes('\\lowres\\') not in old_path.lower()): 219 return SpellSubstituteTexturePath.substitute(self, old_path) 220 else: 221 return old_path
222
223 -class SpellCollisionType(NifSpell):
224 """Sets the object collision to be a different type""" 225 226 SPELLNAME = "modify_collisiontype" 227 READONLY = False 228
229 - class CollisionTypeStatic:
230 layer = 1 231 motion_system = 7 232 unknown_byte1 = 1 233 unknown_byte2 = 1 234 quality_type = 1 235 wind = 0 236 solid = True 237 mass = 0
238
239 - class CollisionTypeAnimStatic(CollisionTypeStatic):
240 layer = 2 241 motion_system = 6 242 unknown_byte1 = 2 243 unknown_byte2 = 2 244 quality_type = 2
245
246 - class CollisionTypeTerrain(CollisionTypeStatic):
247 layer = 14 248 motion_system = 7
249
250 - class CollisionTypeClutter(CollisionTypeAnimStatic):
251 layer = 4 252 motion_system = 4 253 quality_type = 3 254 mass = 10
255
256 - class CollisionTypeWeapon(CollisionTypeClutter):
257 layer = 5 258 mass = 25
259
260 - class CollisionTypeNonCollidable(CollisionTypeStatic):
261 layer = 15 262 motion_system = 7
263 264 COLLISION_TYPE_DICT = { 265 "static": CollisionTypeStatic, 266 "anim_static": CollisionTypeAnimStatic, 267 "clutter": CollisionTypeClutter, 268 "weapon": CollisionTypeWeapon, 269 "terrain": CollisionTypeTerrain, 270 "non_collidable": CollisionTypeNonCollidable 271 } 272 273 @classmethod
274 - def toastentry(cls, toaster):
275 try: 276 toaster.col_type = cls.COLLISION_TYPE_DICT[toaster.options["arg"]] 277 except KeyError: 278 # incorrect arg 279 toaster.logger.warn( 280 "must specify collision type to change to as argument " 281 "(e.g. -a static (accepted names: %s) " 282 "to apply spell" 283 % ", ".join(cls.COLLISION_TYPE_DICT.iterkeys())) 284 return False 285 else: 286 return True
287
288 - def datainspect(self):
290
291 - def branchinspect(self, branch):
292 # only inspect the NiAVObject branch 293 return isinstance(branch, (NifFormat.NiAVObject, 294 NifFormat.bhkCollisionObject, 295 NifFormat.bhkRigidBody, 296 NifFormat.bhkMoppBvTreeShape, 297 NifFormat.bhkPackedNiTriStripsShape))
298
299 - def branchentry(self, branch):
300 if isinstance(branch, NifFormat.bhkRigidBody): 301 self.changed = True 302 branch.layer = self.toaster.col_type.layer 303 branch.layer_copy = self.toaster.col_type.layer 304 branch.mass = self.toaster.col_type.mass 305 branch.motion_system = self.toaster.col_type.motion_system 306 branch.unknown_byte_1 = self.toaster.col_type.unknown_byte1 307 branch.unknown_byte_2 = self.toaster.col_type.unknown_byte2 308 branch.quality_type = self.toaster.col_type.quality_type 309 branch.wind = self.toaster.col_type.wind 310 branch.solid = self.toaster.col_type.solid 311 self.toaster.msg("collision set to %s" 312 % self.toaster.options["arg"]) 313 # bhkPackedNiTriStripsShape could be further down, so keep looking 314 return True 315 elif isinstance(branch, NifFormat.bhkPackedNiTriStripsShape): 316 self.changed = True 317 for subshape in branch.get_sub_shapes(): 318 subshape.layer = self.toaster.col_type.layer 319 self.toaster.msg("collision set to %s" 320 % self.toaster.options["arg"]) 321 # all extra blocks here done; no need to recurse further 322 return False 323 else: 324 # recurse further 325 return True
326
327 -class SpellScaleAnimationTime(NifSpell):
328 """Scales the animation time.""" 329 330 SPELLNAME = "modify_scaleanimationtime" 331 READONLY = False 332 333 @classmethod
334 - def toastentry(cls, toaster):
335 if not toaster.options["arg"]: 336 toaster.logger.warn( 337 "must specify scaling number as argument " 338 "(e.g. -a 0.6) to apply spell") 339 return False 340 else: 341 toaster.animation_scale = float(toaster.options["arg"]) 342 return True
343
344 - def datainspect(self):
345 # returns more than needed but easiest way to ensure it catches all 346 # types of animations 347 return True
348
349 - def branchinspect(self, branch):
350 # inspect the NiAVObject branch, and NiControllerSequence 351 # branch (for kf files) 352 return isinstance(branch, (NifFormat.NiAVObject, 353 NifFormat.NiTimeController, 354 NifFormat.NiInterpolator, 355 NifFormat.NiControllerManager, 356 NifFormat.NiControllerSequence, 357 NifFormat.NiKeyframeData, 358 NifFormat.NiTextKeyExtraData, 359 NifFormat.NiFloatData))
360
361 - def branchentry(self, branch):
362 363 def scale_key_times(keys): 364 """Helper function to scale key times.""" 365 for key in keys: 366 key.time *= self.toaster.animation_scale
367 368 if isinstance(branch, NifFormat.NiKeyframeData): 369 self.changed = True 370 if branch.rotation_type == 4: 371 scale_key_times(branch.xyz_rotations[0].keys) 372 scale_key_times(branch.xyz_rotations[1].keys) 373 scale_key_times(branch.xyz_rotations[2].keys) 374 else: 375 scale_key_times(branch.quaternion_keys) 376 scale_key_times(branch.translations.keys) 377 scale_key_times(branch.scales.keys) 378 # no children of NiKeyframeData so no need to recurse further 379 return False 380 elif isinstance(branch, NifFormat.NiControllerSequence): 381 self.changed = True 382 branch.stop_time *= self.toaster.animation_scale 383 # recurse further into children of NiControllerSequence 384 return True 385 elif isinstance(branch, NifFormat.NiTextKeyExtraData): 386 self.changed = True 387 scale_key_times(branch.text_keys) 388 # no children of NiTextKeyExtraData so no need to recurse further 389 return False 390 elif isinstance(branch, NifFormat.NiTimeController): 391 self.changed = True 392 branch.stop_time *= self.toaster.animation_scale 393 # recurse further into children of NiTimeController 394 return True 395 elif isinstance(branch, NifFormat.NiFloatData): 396 self.changed = True 397 scale_key_times(branch.data.keys) 398 # no children of NiFloatData so no need to recurse further 399 return False 400 else: 401 # recurse further 402 return True
403
404 -class SpellReverseAnimation(NifSpell):
405 """Reverses the animation by reversing datas in relation to the time.""" 406 407 SPELLNAME = "modify_reverseanimation" 408 READONLY = False 409
410 - def datainspect(self):
411 # returns more than needed but easiest way to ensure it catches all 412 # types of animations 413 return True
414
415 - def branchinspect(self, branch):
416 # inspect the NiAVObject branch, and NiControllerSequence 417 # branch (for kf files) 418 return isinstance(branch, (NifFormat.NiAVObject, 419 NifFormat.NiTimeController, 420 NifFormat.NiInterpolator, 421 NifFormat.NiControllerManager, 422 NifFormat.NiControllerSequence, 423 NifFormat.NiKeyframeData, 424 NifFormat.NiTextKeyExtraData, 425 NifFormat.NiFloatData))
426
427 - def branchentry(self, branch):
428 429 def reverse_keys(keys): 430 """Helper function to reverse keys.""" 431 # copy the values 432 key_values = [key.value for key in keys] 433 # reverse them 434 for key, new_value in izip(keys, reversed(key_values)): 435 key.value = new_value
436 437 if isinstance(branch, NifFormat.NiKeyframeData): 438 self.changed = True 439 # (this also covers NiTransformData) 440 if branch.rotation_type == 4: 441 reverse_keys(branch.xyz_rotations[0].keys) 442 reverse_keys(branch.xyz_rotations[1].keys) 443 reverse_keys(branch.xyz_rotations[2].keys) 444 else: 445 reverse_keys(branch.quaternion_keys) 446 reverse_keys(branch.translations.keys) 447 reverse_keys(branch.scales.keys) 448 # no children of NiTransformData so no need to recurse further 449 return False 450 elif isinstance(branch, NifFormat.NiTextKeyExtraData): 451 self.changed = True 452 reverse_keys(branch.text_keys) 453 # no children of NiTextKeyExtraData so no need to recurse further 454 return False 455 elif isinstance(branch, NifFormat.NiFloatData): 456 self.changed = True 457 reverse_keys(branch.data.keys) 458 # no children of NiFloatData so no need to recurse further 459 return False 460 else: 461 # recurse further 462 return True
463
464 -class SpellCollisionMaterial(NifSpell):
465 """Sets the object's collision material to be a different type""" 466 467 SPELLNAME = "modify_collisionmaterial" 468 READONLY = False 469
471 material = 0
472
474 material = 1
475
477 material = 5
478 479 COLLISION_MATERIAL_DICT = { 480 "stone": CollisionMaterialStone, 481 "cloth": CollisionMaterialCloth, 482 "metal": CollisionMaterialMetal 483 } 484 485 @classmethod
486 - def toastentry(cls, toaster):
487 try: 488 toaster.col_material = cls.COLLISION_MATERIAL_DICT[toaster.options["arg"]] 489 except KeyError: 490 # incorrect arg 491 toaster.logger.warn( 492 "must specify collision material to change to as argument " 493 "(e.g. -a stone (accepted names: %s) " 494 "to apply spell" 495 % ", ".join(cls.COLLISION_MATERIAL_DICT.iterkeys())) 496 return False 497 else: 498 return True
499
500 - def datainspect(self):
502
503 - def branchinspect(self, branch):
504 # only inspect the NiAVObject branch 505 return isinstance(branch, (NifFormat.NiAVObject, 506 NifFormat.bhkCollisionObject, 507 NifFormat.bhkRigidBody, 508 NifFormat.bhkShape))
509
510 - def branchentry(self, branch):
511 if isinstance(branch, NifFormat.bhkShape): 512 self.changed = True 513 branch.material = self.toaster.col_material.material 514 self.toaster.msg("collision material set to %s" % self.toaster.options["arg"]) 515 # bhkPackedNiTriStripsShape could be further down, so keep looking 516 return True 517 elif isinstance(branch, NifFormat.bhkPackedNiTriStripsShape): 518 self.changed = True 519 for subshape in branch.get_sub_shapes(): 520 subshape.material = self.toaster.col_type.material 521 self.toaster.msg("collision material set to %s" % self.toaster.options["arg"]) 522 # all extra blocks here done; no need to recurse further 523 return False 524 else: 525 # recurse further 526 return True
527
528 -class SpellDelBranches(NifSpell):
529 """Delete blocks that match the exclude list.""" 530 531 SPELLNAME = "modify_delbranches" 532 READONLY = False 533
534 - def is_branch_to_be_deleted(self, branch):
535 """Returns ``True`` for those branches that must be deleted. 536 The default implementation returns ``True`` for branches that 537 are not admissible as specified by include/exclude options of 538 the toaster. Override in subclasses that must delete specific 539 branches. 540 """ 541 # check if it is excluded or not 542 return not self.toaster.is_admissible_branch_class(branch.__class__)
543
544 - def _branchinspect(self, branch):
545 """This spell inspects every branch, also the non-admissible ones, 546 therefore we must override this method. 547 """ 548 return True
549
550 - def branchentry(self, branch):
551 """Strip branch if it is flagged for deletion. 552 """ 553 # check if it is to be deleted or not 554 if self.is_branch_to_be_deleted(branch): 555 # it is, wipe it out 556 self.toaster.msg("stripping this branch") 557 self.data.replace_global_node(branch, None) 558 self.changed = True 559 # do not recurse further 560 return False 561 else: 562 # this one was not excluded, keep recursing 563 return True
564
565 -class _SpellDelBranchClasses(SpellDelBranches):
566 """Delete blocks that match a given list. Only useful as base class 567 for other spells. 568 """ 569 570 BRANCH_CLASSES_TO_BE_DELETED = () 571 """List of branch classes that have to be deleted.""" 572
573 - def datainspect(self):
574 return any( 575 self.inspectblocktype(branch_class) 576 for branch_class in self.BRANCH_CLASSES_TO_BE_DELETED)
577
578 - def is_branch_to_be_deleted(self, branch):
579 return isinstance(branch, self.BRANCH_CLASSES_TO_BE_DELETED)
580
581 -class SpellDelVertexColor(SpellDelBranches):
582 """Delete vertex color properties and vertex color data.""" 583 584 SPELLNAME = "modify_delvertexcolor" 585
586 - def is_branch_to_be_deleted(self, branch):
587 return isinstance(branch, NifFormat.NiVertexColorProperty)
588
589 - def datainspect(self):
591
592 - def branchinspect(self, branch):
593 # only inspect the NiAVObject branch 594 return isinstance(branch, (NifFormat.NiAVObject, 595 NifFormat.NiTriBasedGeomData, 596 NifFormat.NiVertexColorProperty))
597
598 - def branchentry(self, branch):
599 # delete vertex color property 600 SpellDelBranches.branchentry(self, branch) 601 # reset vertex color flags 602 if isinstance(branch, NifFormat.NiTriBasedGeomData): 603 if branch.has_vertex_colors: 604 self.toaster.msg("removing vertex colors") 605 branch.has_vertex_colors = False 606 self.changed = True 607 # no children; no need to recurse further 608 return False 609 # recurse further 610 return True
611
612 # identical to niftoaster.py modify_delbranches -x NiVertexColorProperty 613 # delete? 614 -class SpellDelVertexColorProperty(_SpellDelBranchClasses):
615 """Delete vertex color property if it is present.""" 616 617 SPELLNAME = "modify_delvertexcolorprop" 618 BRANCH_CLASSES_TO_BE_DELETED = (NifFormat.NiVertexColorProperty,)
619
620 # identical to niftoaster.py modify_delbranches -x NiAlphaProperty 621 # delete? 622 -class SpellDelAlphaProperty(_SpellDelBranchClasses):
623 """Delete alpha property if it is present.""" 624 625 SPELLNAME = "modify_delalphaprop" 626 BRANCH_CLASSES_TO_BE_DELETED = (NifFormat.NiAlphaProperty,)
627
628 # identical to niftoaster.py modify_delbranches -x NiSpecularProperty 629 # delete? 630 -class SpellDelSpecularProperty(_SpellDelBranchClasses):
631 """Delete specular property if it is present.""" 632 633 SPELLNAME = "modify_delspecularprop" 634 BRANCH_CLASSES_TO_BE_DELETED = (NifFormat.NiSpecularProperty,)
635
636 # identical to niftoaster.py modify_delbranches -x BSXFlags 637 # delete? 638 -class SpellDelBSXFlags(_SpellDelBranchClasses):
639 """Delete BSXFlags if any are present.""" 640 641 SPELLNAME = "modify_delbsxflags" 642 BRANCH_CLASSES_TO_BE_DELETED = (NifFormat.BSXFlags,)
643
644 # identical to niftoaster.py modify_delbranches -x NiStringExtraData 645 # delete? 646 -class SpellDelStringExtraDatas(_SpellDelBranchClasses):
647 """Delete NiSringExtraDatas if they are present.""" 648 649 SPELLNAME = "modify_delstringextradatas" 650 BRANCH_CLASSES_TO_BE_DELETED = (NifFormat.NiStringExtraData,)
651
652 -class SpellDelSkinShapes(SpellDelBranches):
653 """Delete any geometries with a material name of 'skin'""" 654 655 SPELLNAME = "modify_delskinshapes" 656
657 - def is_branch_to_be_deleted(self, branch):
658 if isinstance(branch, NifFormat.NiTriBasedGeom): 659 for prop in branch.get_properties(): 660 if isinstance(prop, NifFormat.NiMaterialProperty): 661 if prop.name.lower() == "skin": 662 # skin material, tag for deletion 663 return True 664 # do not delete anything else 665 return False
666
667 - def branchinspect(self, branch):
668 # only inspect the NiAVObject branch 669 return isinstance(branch, NifFormat.NiAVObject)
670
671 # identical to niftoaster.py modify_delbranches -x NiCollisionObject 672 # delete? 673 -class SpellDelCollisionData(_SpellDelBranchClasses):
674 """Deletes any Collision data present.""" 675 676 SPELLNAME = "modify_delcollision" 677 BRANCH_CLASSES_TO_BE_DELETED = (NifFormat.NiCollisionObject,)
678
679 # identical to niftoaster.py modify_delbranches -x NiTimeController 680 # delete? 681 -class SpellDelAnimation(_SpellDelBranchClasses):
682 """Deletes any animation data present.""" 683 684 SPELLNAME = "modify_delanimation" 685 BRANCH_CLASSES_TO_BE_DELETED = (NifFormat.NiTimeController,)
686
687 -class SpellDisableParallax(NifSpell):
688 """Disable parallax shader (for Oblivion, but may work on other nifs too). 689 """ 690 691 SPELLNAME = "modify_disableparallax" 692 READONLY = False 693
694 - def datainspect(self):
695 # XXX should we check that the nif is Oblivion version? 696 # only run the spell if there are textures 697 return self.inspectblocktype(NifFormat.NiTexturingProperty)
698
699 - def branchinspect(self, branch):
700 return isinstance(branch, (NifFormat.NiAVObject, 701 NifFormat.NiTexturingProperty))
702
703 - def branchentry(self, branch):
704 if isinstance(branch, NifFormat.NiTexturingProperty): 705 # is parallax enabled? 706 if branch.apply_mode == 4: 707 # yes! 708 self.toaster.msg("disabling parallax shader") 709 branch.apply_mode = 2 710 self.changed = True 711 # stop recursing 712 return False 713 else: 714 # keep recursing 715 return True
716
717 -class SpellAddStencilProperty(NifSpell):
718 """Adds a NiStencilProperty to each geometry if it is not present.""" 719 720 SPELLNAME = "modify_addstencilprop" 721 READONLY = False 722
723 - def datainspect(self):
725
726 - def branchinspect(self, branch):
727 # only inspect the NiAVObject branch 728 return isinstance(branch, NifFormat.NiAVObject)
729
730 - def branchentry(self, branch):
731 if isinstance(branch, NifFormat.NiTriBasedGeom): 732 # does this block have an stencil property? 733 for prop in branch.get_properties(): 734 if isinstance(prop, NifFormat.NiStencilProperty): 735 return False 736 # no stencil property found 737 self.toaster.msg("adding NiStencilProperty") 738 branch.add_property(NifFormat.NiStencilProperty()) 739 self.changed = True 740 # no geometry children, no need to recurse further 741 return False 742 # recurse further 743 return True
744
745 # note: this should go into the optimize module 746 # but we have to put it here to avoid circular dependencies 747 -class SpellCleanFarNif( 748 pyffi.spells.SpellGroupParallel( 749 SpellDelVertexColorProperty, 750 SpellDelAlphaProperty, 751 SpellDelSpecularProperty, 752 SpellDelBSXFlags, 753 SpellDelStringExtraDatas, 754 pyffi.spells.nif.fix.SpellDelTangentSpace, 755 SpellDelCollisionData, 756 SpellDelAnimation, 757 SpellDisableParallax)):
758 """Spell to clean _far type nifs (for even more optimizations, 759 combine this with the optimize spell). 760 """ 761 762 SPELLNAME = "modify_cleanfarnif" 763 764 # only apply spell on _far files
765 - def datainspect(self):
766 return self.stream.name.endswith('_far.nif')
767
768 # TODO: implement via modify_delbranches? 769 # this is like SpellCleanFarNif but with changing the texture path 770 # and optimizing the geometry 771 -class SpellMakeFarNif( 772 pyffi.spells.SpellGroupParallel( 773 SpellDelVertexColorProperty, 774 SpellDelAlphaProperty, 775 SpellDelSpecularProperty, 776 SpellDelBSXFlags, 777 SpellDelStringExtraDatas, 778 pyffi.spells.nif.fix.SpellDelTangentSpace, 779 SpellDelCollisionData, 780 SpellDelAnimation, 781 SpellDisableParallax, 782 SpellLowResTexturePath)):
783 #TODO: implement vert decreaser. 784 """Spell to make _far type nifs (for even more optimizations, 785 combine this with the optimize spell). 786 """ 787 SPELLNAME = "modify_makefarnif"
788
789 -class SpellMakeSkinlessNif( 790 pyffi.spells.SpellGroupSeries( 791 pyffi.spells.SpellGroupParallel( 792 SpellDelSkinShapes, 793 SpellAddStencilProperty) 794 )):
795 """Spell to make fleshless CMR (Custom Model Races) 796 clothing/armour type nifs. 797 """ 798 SPELLNAME = "modify_makeskinlessnif"
799
800 -class SpellSubstituteStringPalette( 801 pyffi.spells.nif.fix.SpellCleanStringPalette):
802 """Substitute strings in a string palette.""" 803 804 SPELLNAME = "modify_substitutestringpalette" 805 806 @classmethod
807 - def toastentry(cls, toaster):
808 arg = toaster.options["arg"] 809 if not arg: 810 # missing arg 811 toaster.logger.warn( 812 "must specify regular expression and substitution as argument " 813 "(e.g. -a /Bip01/Bip02) to apply spell") 814 return False 815 dummy, toaster.regex, toaster.sub = arg.split(arg[0]) 816 toaster.sub = _as_bytes(toaster.sub) 817 toaster.regex = re.compile(_as_bytes(toaster.regex)) 818 return True
819
820 - def substitute(self, old_string):
821 """Returns modified string, and reports if string was modified. 822 """ 823 if not old_string: 824 # leave empty strings be 825 return old_string 826 new_string = self.toaster.regex.sub(self.toaster.sub, old_string) 827 if old_string != new_string: 828 self.changed = True 829 self.toaster.msg("%s -> %s" % (old_string, new_string)) 830 return new_string
831
832 -class SpellChangeBonePriorities(NifSpell):
833 """Changes controlled block priorities based on controlled block name.""" 834 835 SPELLNAME = "modify_bonepriorities" 836 READONLY = False 837 838 @classmethod
839 - def toastentry(cls, toaster):
840 if not toaster.options["arg"]: 841 toaster.logger.warn( 842 "must specify bone(s) and priority(ies) as argument " 843 "(e.g. -a 'bip01:50|bip01 spine:10') to apply spell " 844 "make sure all bone names in lowercase") 845 return False 846 else: 847 toaster.bone_priorities = dict( 848 (name.lower(), int(priority)) 849 for (name, priority) in ( 850 namepriority.split(":") 851 for namepriority in toaster.options["arg"].split("|"))) 852 return True
853
854 - def datainspect(self):
855 # returns only if nif/kf contains NiSequence 856 return self.inspectblocktype(NifFormat.NiSequence)
857
858 - def branchinspect(self, branch):
859 # inspect the NiAVObject and NiSequence branches 860 return isinstance(branch, (NifFormat.NiAVObject, 861 NifFormat.NiControllerManager, 862 NifFormat.NiSequence))
863
864 - def branchentry(self, branch):
865 if isinstance(branch, NifFormat.NiSequence): 866 for controlled_block in branch.controlled_blocks: 867 try: 868 controlled_block.priority = self.toaster.bone_priorities[ 869 controlled_block.get_node_name().lower()] 870 except KeyError: 871 # node name not in bone priority list 872 continue 873 self.changed = True 874 self.toaster.msg("%s priority changed to %d" % 875 (controlled_block.get_node_name(), 876 controlled_block.priority)) 877 return True
878
879 -class SpellChangeAllBonePriorities(SpellChangeBonePriorities):
880 """Changes all controlled block priorities to supplied argument.""" 881 882 SPELLNAME = "modify_allbonepriorities" 883 884 @classmethod
885 - def toastentry(cls, toaster):
886 if not toaster.options["arg"]: 887 toaster.logger.warn( 888 "must specify priority as argument (e.g. -a 20)") 889 return False 890 else: 891 toaster.bone_priority = int(toaster.options["arg"]) 892 return True
893
894 - def branchentry(self, branch):
895 if isinstance(branch, NifFormat.NiSequence): 896 for controlled_block in branch.controlled_blocks: 897 if controlled_block.priority == self.toaster.bone_priority: 898 self.toaster.msg("%s priority is already %d" % 899 (controlled_block.get_node_name(), 900 controlled_block.priority)) 901 else: 902 controlled_block.priority = self.toaster.bone_priority 903 self.changed = True 904 self.toaster.msg("%s priority changed to %d" % 905 (controlled_block.get_node_name(), 906 controlled_block.priority)) 907 return True
908
909 -class SpellSetInterpolatorTransRotScale(NifSpell):
910 """Changes specified bone(s) translations/rotations in their 911 NiTransformInterpolator. 912 """ 913 914 SPELLNAME = "modify_interpolatortransrotscale" 915 READONLY = False 916 917 @classmethod
918 - def toastentry(cls, toaster):
919 if not toaster.options["arg"]: 920 toaster.logger.warn( 921 "must specify bone(s), translation and rotation for each" 922 " bone as argument (e.g." 923 " -a 'bip01:1,2,3;0,0,0,1;1|bip01 spine2:0,0,0;1,0,0,0.5;1')" 924 " to apply spell; make sure all bone names are lowercase," 925 " first three numbers being translation," 926 " next three being rotation," 927 " last being scale;" 928 " enter X to leave existing value for that value") 929 return False 930 else: 931 def _float(x): 932 if x == "X": 933 return None 934 else: 935 return float(x)
936 937 toaster.interp_transforms = dict( 938 (name.lower(), ([_float(x) for x in trans.split(",")], 939 [_float(x) for x in rot.split(",")], 940 _float(scale))) 941 for (name, (trans, rot, scale)) in ( 942 (name, transrotscale.split(";")) 943 for (name, transrotscale) in ( 944 name_transrotscale.split(":") 945 for name_transrotscale 946 in toaster.options["arg"].split("|")))) 947 return True
948
949 - def datainspect(self):
950 # returns only if nif/kf contains NiSequence 951 return self.inspectblocktype(NifFormat.NiSequence)
952
953 - def branchinspect(self, branch):
954 # inspect the NiAVObject and NiSequence branches 955 return isinstance(branch, (NifFormat.NiAVObject, 956 NifFormat.NiSequence))
957
958 - def branchentry(self, branch):
959 if isinstance(branch, NifFormat.NiSequence): 960 for controlled_block in branch.controlled_blocks: 961 try: 962 (transx, transy, transz), (quatx, quaty, quatz, quatw), scale = self.toaster.interp_transforms[controlled_block.get_node_name().lower()] 963 except KeyError: 964 # node name not in change list 965 continue 966 interp = controlled_block.interpolator 967 if transx is not None: 968 interp.translation.x = transx 969 if transy is not None: 970 interp.translation.y = transy 971 if transz is not None: 972 interp.translation.z = transz 973 if quatx is not None: 974 interp.rotation.x = quatx 975 if quaty is not None: 976 interp.rotation.y = quaty 977 if quatz is not None: 978 interp.rotation.z = quatz 979 if quatw is not None: 980 interp.rotation.w = quatw 981 if scale is not None: 982 interp.scale = scale 983 self.changed = True 984 self.toaster.msg( 985 "%s rotated/translated/scaled as per argument" 986 % (controlled_block.get_node_name())) 987 return True
988
989 -class SpellDelInterpolatorTransformData(NifSpell):
990 """Deletes the specified bone(s) NiTransformData(s).""" 991 992 SPELLNAME = "modify_delinterpolatortransformdata" 993 READONLY = False 994 995 @classmethod
996 - def toastentry(cls, toaster):
997 if not toaster.options["arg"]: 998 toaster.logger.warn( 999 "must specify bone name(s) as argument " 1000 "(e.g. -a 'bip01|bip01 pelvis') to apply spell " 1001 "make sure all bone name(s) in lowercase") 1002 return False 1003 else: 1004 toaster.change_blocks = toaster.options["arg"].split('|') 1005 return True
1006
1007 - def datainspect(self):
1008 # returns only if nif/kf contains NiSequence 1009 return self.inspectblocktype(NifFormat.NiSequence)
1010
1011 - def branchinspect(self, branch):
1012 # inspect the NiAVObject and NiSequence branches 1013 return isinstance(branch, (NifFormat.NiAVObject, 1014 NifFormat.NiSequence))
1015
1016 - def branchentry(self, branch):
1017 if isinstance(branch, NifFormat.NiSequence): 1018 for controlled_block in branch.controlled_blocks: 1019 if controlled_block.get_node_name().lower() in self.toaster.change_blocks: 1020 self.data.replace_global_node(controlled_block.interpolator.data, None) 1021 self.toaster.msg("NiTransformData removed from interpolator for %s" % (controlled_block.get_node_name())) 1022 self.changed = True 1023 return True
1024
1025 -class SpellCollisionToMopp(NifSpell):
1026 """Transforms non-mopp triangle collisions to the more efficient mopps.""" 1027 1028 SPELLNAME = "modify_collisiontomopp" 1029 READONLY = False 1030
1031 - def datainspect(self):
1033
1034 - def branchinspect(self, branch):
1035 # only inspect the NiAVObject branch 1036 return isinstance(branch, (NifFormat.NiAVObject, 1037 NifFormat.bhkCollisionObject, 1038 NifFormat.bhkRigidBody))
1039
1040 - def branchentry(self, branch):
1041 if isinstance(branch, NifFormat.bhkRigidBody): 1042 if isinstance(branch.shape, (NifFormat.bhkNiTriStripsShape, 1043 NifFormat.bhkPackedNiTriStripsShape)): 1044 colmopp = NifFormat.bhkMoppBvTreeShape() 1045 colmopp.material = branch.shape.material 1046 colmopp.unknown_8_bytes[0] = 160 1047 colmopp.unknown_8_bytes[1] = 13 1048 colmopp.unknown_8_bytes[2] = 75 1049 colmopp.unknown_8_bytes[3] = 1 1050 colmopp.unknown_8_bytes[4] = 192 1051 colmopp.unknown_8_bytes[5] = 207 1052 colmopp.unknown_8_bytes[6] = 144 1053 colmopp.unknown_8_bytes[7] = 11 1054 colmopp.unknown_float = 1.0 1055 if isinstance(branch.shape, NifFormat.bhkNiTriStripsShape): 1056 branch.shape = branch.shape.get_interchangeable_packed_shape() 1057 colmopp.shape = branch.shape 1058 branch.shape = colmopp 1059 self.changed = True 1060 branch.shape.update_mopp() 1061 self.toaster.msg("collision set to MOPP") 1062 # Don't need to recurse further 1063 return False 1064 else: 1065 # recurse further 1066 return True
1067
1068 -class SpellMirrorAnimation(NifSpell):
1069 """Mirrors the animation by switching bones and mirroring their x values. 1070 Only useable on creature/character animations (well any animations 1071 as long as they have bones in the form of bip01/2 L ...). 1072 """ 1073 1074 SPELLNAME = "modify_mirroranimation" 1075 READONLY = False 1076
1077 - def datainspect(self):
1078 # returns more than needed but easiest way to ensure it catches all 1079 # types of animations 1080 return True
1081
1082 - def dataentry(self):
1083 # make list of used bones 1084 self.old_bone_data = {} 1085 for branch in self.data.get_global_iterator(): 1086 if isinstance(branch, NifFormat.NiControllerSequence): 1087 for block in branch.controlled_blocks: 1088 name = block.get_node_name().lower() 1089 if ' r ' in name or ' l ' in name: 1090 self.old_bone_data[name] = [block.interpolator, block.controller, block.priority, block.string_palette, block.node_name_offset, block.controller_type_offset] 1091 if self.old_bone_data: 1092 return True
1093
1094 - def branchinspect(self, branch):
1095 # inspect the NiAVObject branch, and NiControllerSequence 1096 # branch (for kf files) 1097 return isinstance(branch, (NifFormat.NiAVObject, 1098 NifFormat.NiTimeController, 1099 NifFormat.NiInterpolator, 1100 NifFormat.NiControllerManager, 1101 NifFormat.NiControllerSequence))
1102
1103 - def branchentry(self, branch):
1104 old_bone_data = self.old_bone_data 1105 1106 if isinstance(branch, NifFormat.NiControllerSequence): 1107 for block in branch.controlled_blocks: 1108 node_name = block.get_node_name().lower() 1109 if ' l ' in node_name: node_name = node_name.replace(' l ', ' r ') 1110 elif ' r ' in node_name: node_name = node_name.replace(' r ', ' l ') 1111 if node_name in old_bone_data: 1112 self.changed = True 1113 block.interpolator, block.controller, block.priority, block.string_palette, block.node_name_offset, block.controller_type_offset = old_bone_data[node_name] 1114 # and then reverse x movements (since otherwise the movement of f.e. an arm towards the center of the body will be still in the same direction but away from the body 1115 if not block.interpolator: continue 1116 ip = block.interpolator 1117 ip.translation.x = -ip.translation.x 1118 ip.rotation.x = -ip.rotation.x 1119 if ip.data: 1120 data = ip.data 1121 if data.translations.num_keys: 1122 for key in data.translations.keys: 1123 key.value.x = -key.value.x 1124 if data.rotation_type == 4: 1125 if data.xyz_rotations[1].num_keys != 0: 1126 for key in data.xyz_rotations[1].keys: 1127 key.value = -key.value 1128 elif data.num_rotation_keys != 0: 1129 for key in data.quaternion_keys: 1130 key.value.x = -key.value.x 1131 else: 1132 # recurse further 1133 return True
1134