def import_object_animation(self, n_block, b_obj): """ Loads an animation attached to a nif block (non-skeletal). Becomes the object level animation of the blender object. """ NifLog.debug('Importing animation for object %s'.format(b_obj.name)) kfc = nif_utils.find_controller(n_block, NifFormat.NiKeyframeController) if kfc: self.nif_import.animationhelper.create_action(b_obj, b_obj.name+"-Anim" ) self.import_keyframe_controller(kfc, b_obj)
def set_alpha(self, b_mat, ShaderProperty, n_alpha_prop): NifLog.debug("Alpha prop detected") b_mat.use_transparency = True if hasattr(ShaderProperty, 'alpha'): b_mat.alpha = (1 - ShaderProperty.alpha) else: b_mat.alpha = 0 b_mat.transparency_method = 'Z_TRANSPARENCY' # enable z-buffered transparency b_mat.offset_z = n_alpha_prop.threshold # Transparency threshold b_mat.niftools_alpha.alphaflag = n_alpha_prop.flags return b_mat
def import_bone_animation(self, n_block, b_armature_obj, bone_name): """ Loads an animation attached to a nif block (skeletal). Becomes the pose bone level animation of the blender object. """ NifLog.debug('Importing animation for bone %s'.format(bone_name)) kfc = nif_utils.find_controller(n_block, NifFormat.NiKeyframeController) if kfc: bone_bm = nif_utils.import_matrix(n_block) # base pose niBone_bind_scale, niBone_bind_rot, niBone_bind_trans = nif_utils.decompose_srt(bone_bm) niBone_bind_rot_inv = niBone_bind_rot.to_4x4().inverted() self.import_keyframe_controller(kfc, b_armature_obj, bone_name, niBone_bind_scale, niBone_bind_rot_inv, niBone_bind_trans)
def populate_bone_tree(self, skelroot): """Add all of skelroot's bones to its dict_armatures list. """ for bone in skelroot.tree(): if bone is skelroot: continue if not isinstance(bone, NifFormat.NiNode): continue if isinstance(bone, NifFormat.NiLODNode): # LOD nodes are never bones continue if self.nif_import.is_grouping_node(bone): continue if bone not in self.dict_armatures[skelroot]: self.dict_armatures[skelroot].append(bone) NifLog.debug( "'{0}' marked as extra bone of armature '{1}'".format( bone.name, skelroot.name))
def complete_bone_tree(self, bone, skelroot): """Make sure that the complete hierarchy from skelroot down to bone is marked in dict_armatures. """ # we must already have marked this one as a bone assert skelroot in self.dict_armatures # debug assert bone in self.dict_armatures[skelroot] # debug # get the node parent, this should be marked as an armature or as a bone boneparent = bone._parent if boneparent != skelroot: # parent is not the skeleton root if boneparent not in self.dict_armatures[skelroot]: # neither is it marked as a bone: so mark the parent as a bone self.dict_armatures[skelroot].append(boneparent) # store the coordinates for realignement autodetection NifLog.debug("'{0}' is a bone of armature '{1}'".format( boneparent.name, skelroot.name)) # now the parent is marked as a bone # recursion: complete the bone tree, # this time starting from the parent bone self.complete_bone_tree(boneparent, skelroot)
def import_keyframe_controller(self, kfc, b_obj, bone_name=None, niBone_bind_scale=None, niBone_bind_rot_inv=None, niBone_bind_trans=None): b_action = b_obj.animation_data.action if bone_name: b_obj = b_obj.pose.bones[bone_name] # old style: data directly on controller kfd = kfc.data # new style: data via interpolator kfi = kfc.interpolator translations = [] scales = [] rotations = [] eulers = [] #TODO: test interpolators # B-spline curve import if isinstance(kfi, NifFormat.NiBSplineInterpolator): times = list(kfi.get_times()) translations = zip( times, list(kfi.get_translations()) ) scales = zip( times, list(kfi.get_scales()) ) rotations = zip( times, list(kfi.get_rotations()) ) #TODO: get these from interpolator? interp_rot = "LINEAR" interp_loc = "LINEAR" interp_scale = "LINEAR" return # next is a quick hack to make the new transform # interpolator work as if it is an old style keyframe data # block parented directly on the controller if isinstance(kfi, NifFormat.NiTransformInterpolator): kfd = kfi.data # for now, in this case, ignore interpolator kfi = None if isinstance(kfd, NifFormat.NiKeyframeData): interp_rot = self.nif_import.animationhelper.get_b_interp_from_n_interp(kfd.rotation_type) interp_loc = self.nif_import.animationhelper.get_b_interp_from_n_interp(kfd.translations.interpolation) interp_scale = self.nif_import.animationhelper.get_b_interp_from_n_interp(kfd.scales.interpolation) if kfd.rotation_type == 4: b_obj.rotation_mode = "XYZ" # uses xyz rotation if kfd.xyz_rotations[0].keys: #euler keys need not be sampled at the same time in KFs #but we need complete key sets to do the space conversion #so perform linear interpolation to import all keys properly #get all the keys' times times_x = [key.time for key in kfd.xyz_rotations[0].keys] times_y = [key.time for key in kfd.xyz_rotations[1].keys] times_z = [key.time for key in kfd.xyz_rotations[2].keys] #the unique time stamps we have to sample all curves at times_all = sorted( set(times_x + times_y + times_z) ) #the actual resampling x_r = interpolate(times_all, times_x, [key.value for key in kfd.xyz_rotations[0].keys]) y_r = interpolate(times_all, times_y, [key.value for key in kfd.xyz_rotations[1].keys]) z_r = interpolate(times_all, times_z, [key.value for key in kfd.xyz_rotations[2].keys]) eulers = zip(times_all, zip(x_r, y_r, z_r) ) else: b_obj.rotation_mode = "QUATERNION" rotations = [(key.time, key.value) for key in kfd.quaternion_keys] if kfd.scales.keys: scales = [(key.time, key.value) for key in kfd.scales.keys] if kfd.translations.keys: translations = [(key.time, key.value) for key in kfd.translations.keys] if eulers: NifLog.debug('Rotation keys...(euler)') fcurves = self.nif_import.animationhelper.create_fcurves(b_action, "rotation_euler", range(3), kfc.flags, bone_name) for t, val in eulers: key = mathutils.Euler( val ) if bone_name: key = armature.import_keymat(niBone_bind_rot_inv, key.to_matrix().to_4x4() ).to_euler() self.nif_import.animationhelper.add_key(fcurves, t, key, interp_rot) elif rotations: NifLog.debug('Rotation keys...(quaternions)') fcurves = self.nif_import.animationhelper.create_fcurves(b_action, "rotation_quaternion", range(4), kfc.flags, bone_name) for t, val in rotations: key = mathutils.Quaternion([val.w, val.x, val.y, val.z]) if bone_name: key = armature.import_keymat(niBone_bind_rot_inv, key.to_matrix().to_4x4() ).to_quaternion() self.nif_import.animationhelper.add_key(fcurves, t, key, interp_rot) if translations: NifLog.debug('Translation keys...') fcurves = self.nif_import.animationhelper.create_fcurves(b_action, "location", range(3), kfc.flags, bone_name) for t, val in translations: key = mathutils.Vector([val.x, val.y, val.z]) if bone_name: key = armature.import_keymat(niBone_bind_rot_inv, mathutils.Matrix.Translation(key - niBone_bind_trans)).to_translation() self.nif_import.animationhelper.add_key(fcurves, t, key, interp_loc) if scales: NifLog.debug('Scale keys...') fcurves = self.nif_import.animationhelper.create_fcurves(b_action, "scale", range(3), kfc.flags, bone_name) for t, val in scales: key = (val, val, val) self.nif_import.animationhelper.add_key(fcurves, t, key, interp_scale)
def export_bones(self, arm, parent_block): """Export the bones of an armature.""" # the armature was already exported as a NiNode # now we must export the armature's bones assert (arm.type == 'ARMATURE') # find the root bones # list of all bones bones = arm.data.bones.values() # maps bone names to NiNode blocks bones_node = {} # here all the bones are added # first create all bones with their keyframes # and then fix the links in a second run # ok, let's create the bone NiNode blocks for bone in bones: # create a new block for this bone node = self.nif_export.objecthelper.create_ninode(bone) # doing bone map now makes linkage very easy in second run bones_node[bone.name] = node # add the node and the keyframe for this bone node.name = self.nif_export.objecthelper.get_full_name(bone.name) if (bone.niftools_bone.boneflags != 0): node.flags = bone.niftools_bone.boneflags else: if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'): # default for Oblivion bones # note: bodies have 0x000E, clothing has 0x000F node.flags = 0x000E elif NifOp.props.game in ('CIVILIZATION_IV', 'EMPIRE_EARTH_II'): if bone.children: # default for Civ IV/EE II bones with children node.flags = 0x0006 else: # default for Civ IV/EE II final bones node.flags = 0x0016 elif NifOp.props.game in ('DIVINITY_2', ): if bone.children: # default for Div 2 bones with children node.flags = 0x0186 elif bone.name.lower()[-9:] == 'footsteps': node.flags = 0x0116 else: # default for Div 2 final bones node.flags = 0x0196 else: node.flags = 0x0002 # default for Morrowind bones # rest pose self.nif_export.objecthelper.set_object_matrix(bone, node) # per-node animation self.nif_export.animationhelper.export_keyframes(node, arm, bone) # does bone have priority value in NULL constraint? for constr in arm.pose.bones[bone.name].constraints: # yes! store it for reference when creating the kf file if constr.name[:9].lower() == "priority:": self.nif_export.dict_bone_priorities[ armature.get_bone_name_for_nif(bone.name)] = int( constr.name[9:]) # now fix the linkage between the blocks for bone in bones: # link the bone's children to the bone NifLog.debug("Linking children of bone {0}".format(bone.name)) for child in bone.children: bones_node[bone.name].add_child(bones_node[child.name]) # if it is a root bone, link it to the armature if not bone.parent: parent_block.add_child(bones_node[bone.name])
def import_source(self, source): b_image = None fn = None # the texture uses an external image file if isinstance(source, NifFormat.NiSourceTexture): fn = source.file_name.decode() elif isinstance(source, str): fn = source else: raise TypeError("source must be NiSourceTexture or str") fn = fn.replace('\\', os.sep) fn = fn.replace('/', os.sep) # go searching for it importpath = os.path.dirname(NifOp.props.filepath) searchPathList = [importpath] if bpy.context.user_preferences.filepaths.texture_directory: searchPathList.append( bpy.context.user_preferences.filepaths.texture_directory) # TODO: 3 - Implement full texture path finding. nif_dir = os.path.join(os.getcwd(), 'nif') searchPathList.append(nif_dir) # if it looks like a Morrowind style path, use common sense to # guess texture path meshes_index = importpath.lower().find("meshes") if meshes_index != -1: searchPathList.append(importpath[:meshes_index] + 'textures') # if it looks like a Civilization IV style path, use common sense # to guess texture path art_index = importpath.lower().find("art") if art_index != -1: searchPathList.append(importpath[:art_index] + 'shared') # go through all texture search paths for texdir in searchPathList: texdir = texdir.replace('\\', os.sep) texdir = texdir.replace('/', os.sep) # go through all possible file names, try alternate extensions # too; for linux, also try lower case versions of filenames texfns = reduce( operator.add, [[fn[:-4] + ext, fn[:-4].lower() + ext] for ext in ('.DDS', '.dds', '.PNG', '.png', '.TGA', '.tga', '.BMP', '.bmp', '.JPG', '.jpg')]) texfns = [fn, fn.lower()] + list(set(texfns)) for texfn in texfns: # now a little trick, to satisfy many Morrowind mods if (texfn[:9].lower() == 'textures' + os.sep) \ and (texdir[-9:].lower() == os.sep + 'textures'): # strip one of the two 'textures' from the path tex = os.path.join(texdir[:-9], texfn) else: tex = os.path.join(texdir, texfn) # "ignore case" on linux tex = bpy.path.resolve_ncase(tex) NifLog.debug("Searching {0}".format(tex)) if os.path.exists(tex): # tries to load the file b_image = bpy.data.images.load(tex) # Blender will return an image object even if the # file format is not supported, # so to check if the image is actually loaded an error # is forced via "b_image.size" try: b_image.size except: # RuntimeError: couldn't load image data in Blender b_image = None # not supported, delete image object else: # file format is supported NifLog.debug("Found '{0}' at {1}".format(fn, tex)) break if b_image: return [tex, b_image] else: tex = os.path.join(searchPathList[0], fn) return [tex, b_image]
def mark_armatures_bones(self, niBlock): """Mark armatures and bones by peeking into NiSkinInstance blocks.""" # case where we import skeleton only, # or importing an Oblivion or Fallout 3 skeleton: # do all NiNode's as bones if NifOp.props.skeleton == "SKELETON_ONLY" or ( self.nif_import.data.version in (0x14000005, 0x14020007) and (os.path.basename(NifOp.props.filepath).lower() in ('skeleton.nif', 'skeletonbeast.nif'))): if not isinstance(niBlock, NifFormat.NiNode): raise nif_utils.NifError( "cannot import skeleton: root is not a NiNode") # for morrowind, take the Bip01 node to be the skeleton root if self.nif_import.data.version == 0x04000002: skelroot = niBlock.find(block_name='Bip01', block_type=NifFormat.NiNode) if not skelroot: skelroot = niBlock else: skelroot = niBlock if skelroot not in self.dict_armatures: self.dict_armatures[skelroot] = [] NifLog.info("Selecting node '%s' as skeleton root".format( skelroot.name)) # add bones self.populate_bone_tree(skelroot) return # done! # attaching to selected armature -> first identify armature and bones elif NifOp.props.skeleton == "GEOMETRY_ONLY" and not self.dict_armatures: b_armature_obj = self.nif_import.selected_objects[0] skelroot = niBlock.find(block_name=b_armature_obj.name) if not skelroot: raise nif_utils.NifError("nif has no armature '%s'" % b_armature_obj.name) NifLog.debug("Identified '{0}' as armature".format(skelroot.name)) self.dict_armatures[skelroot] = [] for bone_name in b_armature_obj.data.bones.keys(): # blender bone naming -> nif bone naming nif_bone_name = armature.get_bone_name_for_nif(bone_name) # find a block with bone name bone_block = skelroot.find(block_name=nif_bone_name) # add it to the name list if there is a bone with that name if bone_block: NifLog.info( "Identified nif block '{0}' with bone '{1}' in selected armature" .format(nif_bone_name, bone_name)) self.nif_import.dict_names[bone_block] = bone_name self.dict_armatures[skelroot].append(bone_block) self.complete_bone_tree(bone_block, skelroot) # search for all NiTriShape or NiTriStrips blocks... if isinstance(niBlock, NifFormat.NiTriBasedGeom): # yes, we found one, get its skin instance if niBlock.is_skin(): NifLog.debug("Skin found on block '{0}'".format(niBlock.name)) # it has a skin instance, so get the skeleton root # which is an armature only if it's not a skinning influence # so mark the node to be imported as an armature skininst = niBlock.skin_instance skelroot = skininst.skeleton_root if NifOp.props.skeleton == "EVERYTHING": if skelroot not in self.dict_armatures: self.dict_armatures[skelroot] = [] NifLog.debug("'{0}' is an armature".format( skelroot.name)) elif NifOp.props.skeleton == "GEOMETRY_ONLY": if skelroot not in self.dict_armatures: raise nif_utils.NifError( "nif structure incompatible with '%s' as armature:" " node '%s' has '%s' as armature" % (b_armature_obj.name, niBlock.name, skelroot.name)) for boneBlock in skininst.bones: # boneBlock can be None; see pyffi issue #3114079 if not boneBlock: continue if boneBlock not in self.dict_armatures[skelroot]: self.dict_armatures[skelroot].append(boneBlock) NifLog.debug( "'{0}' is a bone of armature '{1}'".format( boneBlock.name, skelroot.name)) # now we "attach" the bone to the armature: # we make sure all NiNodes from this bone all the way # down to the armature NiNode are marked as bones self.complete_bone_tree(boneBlock, skelroot) # mark all nodes as bones self.populate_bone_tree(skelroot) # continue down the tree for child in niBlock.get_refs(): if not isinstance(child, NifFormat.NiAVObject): continue # skip blocks that don't have transforms self.mark_armatures_bones(child)