def import_kf_standalone(self, kf_root, b_armature_obj, bind_data): """ Import a kf animation. Needs a suitable armature in blender scene. """ NifLog.info("Importing KF tree") # check that this is an Oblivion style kf file if not isinstance(kf_root, NifFormat.NiControllerSequence): raise nif_utils.NifError("non-Oblivion .kf import not supported") # import text keys self.import_text_keys(kf_root) self.create_action(b_armature_obj, kf_root.name.decode()) # go over all controlled blocks (NiKeyframeController) for controlledblock in kf_root.controlled_blocks: # nb: this yielded just an empty bytestring # nodename = controlledblock.get_node_name() kfc = controlledblock.controller bone_name = armature.get_bone_name_for_blender( controlledblock.target_name) if bone_name in bind_data: niBone_bind_scale, niBone_bind_rot_inv, niBone_bind_trans = bind_data[ bone_name] self.armature_animation.import_keyframe_controller( kfc, b_armature_obj, bone_name, niBone_bind_scale, niBone_bind_rot_inv, niBone_bind_trans)
def import_material_color_controller(self, b_material, b_channel, n_geom, n_target_color): # find material color controller with matching target color n_matprop = nif_utils.find_property(n_geom, NifFormat.NiMaterialProperty) if not n_matprop: return for ctrl in n_matprop.get_controllers(): if isinstance(ctrl, NifFormat.NiMaterialColorController): if ctrl.get_target_color() == n_target_color: n_matcolor_ctrl = ctrl break else: return NifLog.info( "Importing material color controller for target color {0} into blender channel {0}" .format(n_target_color, b_channel)) # import data as curves b_mat_action = self.nif_import.animationhelper.create_action( b_material, "MaterialAction") fcurves = create_fcurves(b_mat_action, b_channel, range(3)) interp = self.nif_import.animationhelper.get_b_interp_from_n_interp( n_matcolor_ctrl.data.data.interpolation) self.nif_import.animationhelper.set_extrapolation( n_matcolor_ctrl.flags, fcurves) for key in n_matcolor_ctrl.data.data.keys: self.nif_import.animationhelper.add_key(fcurves, key.time, key.value.as_list(), interp)
def import_material_uv_controller(self, b_material, n_geom): """Import UV controller data.""" # search for the block n_ctrl = nif_utils.find_controller(n_geom, NifFormat.NiUVController) if not (n_ctrl and n_ctrl.data): return NifLog.info("Importing UV controller") b_mat_action = self.nif_import.animationhelper.create_action( b_material, "MaterialAction") dtypes = ("offset", 0), ("offset", 1), ("scale", 0), ("scale", 1) for n_uvgroup, (data_path, array_ind) in zip(n_ctrl.data.uv_groups, dtypes): if n_uvgroup.keys: interp = self.nif_import.animationhelper.get_b_interp_from_n_interp( n_uvgroup.interpolation) #in blender, UV offset is stored per texture slot #so we have to repeat the import for each used tex slot for i, texture_slot in enumerate(b_material.texture_slots): if texture_slot: fcurves = create_fcurves( b_mat_action, "texture_slots[" + str(i) + "]." + data_path, (array_ind, )) for key in n_uvgroup.keys: if "offset" in data_path: self.nif_import.animationhelper.add_key( fcurves, key.time, (-key.value, ), interp) else: self.nif_import.animationhelper.add_key( fcurves, key.time, (key.value, ), interp) self.nif_import.animationhelper.set_extrapolation( n_ctrl.flags, fcurves)
def import_shader_by_type(self, b_mat, shader_tex_desc, extra_shader_index): if extra_shader_index == 0: # EnvironmentMapIndex if shader_tex_desc.texture_data.source.file_name.lower( ).startswith("rrt_engine_env_map"): # sid meier's railroads: env map generated by engine # we can skip this NifLog.info( "Skipping environment map texture. Env Map is generated by Engine" ) envTexDesc = shader_tex_desc.texture_data self.has_envtex = True self.env_map = self.import_image_texture(b_mat, envTexDesc) elif extra_shader_index == 1: # NormalMapIndex bumpTexDesc = shader_tex_desc.texture_data self.has_bumptex = True self.bump_map = self.import_image_texture(b_mat, bumpTexDesc) elif extra_shader_index == 2: # SpecularIntensityIndex glossTexDesc = shader_tex_desc.texture_data self.has_glosstex = True self.gloss_map = self.import_image_texture(b_mat, glossTexDesc) elif extra_shader_index == 3: # EnvironmentIntensityIndex (this is reflection) refTexDesc = shader_tex_desc.texture_data self.has_reftex = True self.reflection_map = self.reflection_map = self.import_image_texture( b_mat, refTexDesc) elif extra_shader_index == 4: # LightCubeMapIndex if shader_tex_desc.texture_data.source.file_name.lower( ).startswith("rrt_cube_light_map"): # sid meier's railroads: light map generated by engine # we can skip this NifLog.info("Ignoring Env Map as generated by Engine") NifLog.warn("Skipping light cube texture.") elif extra_shader_index == 5: # ShadowTextureIndex NifLog.warn("Skipping shadow texture.") else: NifLog.warn("Unknown texture type found in extra_shader_index")
def __init__(self, operator): """Common initialization functions for executing the import/export operators: """ NifOp.init(operator) # print scripts info from . import bl_info niftools_ver = (".".join(str(i) for i in bl_info["version"])) NifLog.info( "Executing - Niftools : Blender Nif Plugin v{0} (running on Blender {1}, PyFFI {2})" .format(niftools_ver, bpy.app.version_string, pyffi.__version__)) # find and store this list now of selected objects as creating new objects adds them to the selection list self.selected_objects = bpy.context.selected_objects[:]
def set_frames_per_second(self, roots): """Scan all blocks and set a reasonable number for FPS to this class and the scene.""" # find all key times key_times = [] for root in roots: for kfd in root.tree(block_type=NifFormat.NiKeyframeData): key_times.extend(key.time for key in kfd.translations.keys) key_times.extend(key.time for key in kfd.scales.keys) key_times.extend(key.time for key in kfd.quaternion_keys) key_times.extend(key.time for key in kfd.xyz_rotations[0].keys) key_times.extend(key.time for key in kfd.xyz_rotations[1].keys) key_times.extend(key.time for key in kfd.xyz_rotations[2].keys) for kfi in root.tree(block_type=NifFormat.NiBSplineInterpolator): if not kfi.basis_data: # skip bsplines without basis data (eg bowidle.kf in # Oblivion) continue key_times.extend( point * (kfi.stop_time - kfi.start_time) / (kfi.basis_data.num_control_points - 2) for point in range(kfi.basis_data.num_control_points - 2)) for uvdata in root.tree(block_type=NifFormat.NiUVData): for uvgroup in uvdata.uv_groups: key_times.extend(key.time for key in uvgroup.keys) fps = self.fps # not animated, return a reasonable default if not key_times: return # calculate FPS key_times = sorted(set(key_times)) lowest_diff = sum( abs(int(time * fps + 0.5) - (time * fps)) for time in key_times) # for test_fps in range(1,120): #disabled, used for testing for test_fps in [20, 24, 25, 35]: diff = sum( abs(int(time * test_fps + 0.5) - (time * test_fps)) for time in key_times) if diff < lowest_diff: lowest_diff = diff fps = test_fps NifLog.info("Animation estimated at %i frames per second." % fps) self.fps = fps bpy.context.scene.render.fps = fps bpy.context.scene.frame_set(0)
def import_object_vis_controller(self, n_node, b_obj): """Import vis controller for blender object.""" n_vis_ctrl = nif_utils.find_controller(n_node, NifFormat.NiVisController) if not (n_vis_ctrl and n_vis_ctrl.data): return NifLog.info("Importing vis controller") b_obj_action = self.nif_import.animationhelper.create_action( b_obj, b_obj.name + "-Anim") fcurves = create_fcurves(b_obj_action, "hide", (0, )) for key in n_vis_ctrl.data.keys: self.nif_import.animationhelper.add_key(fcurves, key.time, (key.value, ), "CONSTANT") #get extrapolation from flags and set it to fcurves self.nif_import.animationhelper.set_extrapolation( n_vis_ctrl.flags, fcurves)
def import_material_alpha_controller(self, b_material, n_geom): # find alpha controller n_matprop = nif_utils.find_property(n_geom, NifFormat.NiMaterialProperty) if not n_matprop: return n_alphactrl = nif_utils.find_controller(n_matprop, NifFormat.NiAlphaController) if not (n_alphactrl and n_alphactrl.data): return NifLog.info("Importing alpha controller") b_mat_action = self.nif_import.animationhelper.create_action( b_material, "MaterialAction") fcurves = create_fcurves(b_mat_action, "alpha", (0, )) interp = self.nif_import.animationhelper.get_b_interp_from_n_interp( n_alphactrl.data.data.interpolation) self.nif_import.animationhelper.set_extrapolation( n_alphactrl.flags, fcurves) for key in n_alphactrl.data.data.keys: self.nif_import.animationhelper.add_key(fcurves, key.time, (key.value, ), interp)
def import_embedded_texture_source(self, source): fn = None # find a file name (but avoid overwriting) n = 0 while True: fn = "image%03i.dds" % n tex = os.path.join(os.path.dirname(NifOp.props.filepath), fn) if not os.path.exists(tex): break n += 1 if self.nif_import.IMPORT_EXPORTEMBEDDEDTEXTURES: # save embedded texture as dds file stream = open(tex, "wb") try: NifLog.info("Saving embedded texture as {0}".format(tex)) source.pixel_data.save_as_dds(stream) except ValueError: # value error means that the pixel format is not supported b_image = None else: # saving dds succeeded so load the file b_image = bpy.ops.image.open(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 finally: stream.close() else: b_image = None return [fn, b_image]
def load_kf(file_path): """Loads a Kf file from the given path""" NifLog.info("Loading {0}".format(file_path)) kf_file = NifFormat.Data() # open keyframe file for binary reading with open(file_path, "rb") as kf_stream: # check if nif file is valid kf_file.inspect_version_only(kf_stream) if kf_file.version >= 0: # it is valid, so read the file NifLog.info("KF file version: {0}".format(kf_file.version, "x")) NifLog.info("Reading keyframe file") kf_file.read(kf_stream) elif kf_file.version == -1: raise NifError("Unsupported KF version.") else: raise NifError("Not a KF file.") return kf_file
def load_egm(file_path): """Loads an egm file from the given path""" NifLog.info("Loading {0}".format(file_path)) egm_file = EgmFormat.Data() # open keyframe file for binary reading with open(file_path, "rb") as egm_stream: # check if nif file is valid egm_file.inspect_version_only(egm_stream) if egm_file.version >= 0: # it is valid, so read the file NifLog.info("EGM file version: {0}".format( egm_file.version, "x")) NifLog.info("Reading FaceGen egm file") egm_file.read(egm_stream) elif egm_file.version == -1: raise NifError("Unsupported EGM version.") else: raise NifError("Not a EGM file.") return egm_file
def load_nif(file_path): """Loads a nif from the given file path""" NifLog.info("Importing {0}".format(file_path)) nif_data = NifFormat.Data() # open file for binary reading with open(file_path, "rb") as nif_stream: # check if nif file is valid nif_data.inspect_version_only(nif_stream) if nif_data.version >= 0: # it is valid, so read the file NifLog.info("NIF file version: {0}".format( nif_data.version, "x")) NifLog.info("Reading file") nif_data.read(nif_stream) elif nif_data.version == -1: raise NifError("Unsupported NIF version.") else: raise NifError("Not a NIF file.") return nif_data
def import_constraint(self, hkbody): """Imports a bone havok constraint as Blender object constraint.""" assert (isinstance(hkbody, NifFormat.bhkRigidBody)) # check for constraints if not hkbody.constraints: return # find objects if len(self.nif_import.dict_havok_objects[hkbody]) != 1: NifLog.warn( "Rigid body with no or multiple shapes, constraints skipped") return b_hkobj = self.nif_import.dict_havok_objects[hkbody][0] NifLog.info("Importing constraints for %s" % b_hkobj.name) # now import all constraints for hkconstraint in hkbody.constraints: # check constraint entities if not hkconstraint.num_entities == 2: NifLog.warn("Constraint with more than 2 entities, skipped") continue if not hkconstraint.entities[0] is hkbody: NifLog.warn("First constraint entity not self, skipped") continue if not hkconstraint.entities[ 1] in self.nif_import.dict_havok_objects: NifLog.warn("Second constraint entity not imported, skipped") continue # get constraint descriptor if isinstance(hkconstraint, NifFormat.bhkRagdollConstraint): hkdescriptor = hkconstraint.ragdoll b_hkobj.rigid_body.enabled = True elif isinstance(hkconstraint, NifFormat.bhkLimitedHingeConstraint): hkdescriptor = hkconstraint.limited_hinge b_hkobj.rigid_body.enabled = True elif isinstance(hkconstraint, NifFormat.bhkHingeConstraint): hkdescriptor = hkconstraint.hinge b_hkobj.rigid_body.enabled = True elif isinstance(hkconstraint, NifFormat.bhkMalleableConstraint): if hkconstraint.type == 7: hkdescriptor = hkconstraint.ragdoll b_hkobj.rigid_body.enabled = False elif hkconstraint.type == 2: hkdescriptor = hkconstraint.limited_hinge b_hkobj.rigid_body.enabled = False else: NifLog.warn("Unknown malleable type ({0}), skipped".format( str(hkconstraint.type))) # extra malleable constraint settings ### damping parameters not yet in Blender Python API ### tau (force between bodies) not supported by Blender else: NifLog.warn("Unknown constraint type ({0}), skipped".format( hkconstraint.__class__.__name__)) continue # add the constraint as a rigid body joint b_constr = b_hkobj.constraints.new('RIGID_BODY_JOINT') b_constr.name = b_hkobj.name b_constr.show_pivot = True # note: rigidbodyjoint parameters (from Constraint.c) # CONSTR_RB_AXX 0.0 # CONSTR_RB_AXY 0.0 # CONSTR_RB_AXZ 0.0 # CONSTR_RB_EXTRAFZ 0.0 # CONSTR_RB_MAXLIMIT0 0.0 # CONSTR_RB_MAXLIMIT1 0.0 # CONSTR_RB_MAXLIMIT2 0.0 # CONSTR_RB_MAXLIMIT3 0.0 # CONSTR_RB_MAXLIMIT4 0.0 # CONSTR_RB_MAXLIMIT5 0.0 # CONSTR_RB_MINLIMIT0 0.0 # CONSTR_RB_MINLIMIT1 0.0 # CONSTR_RB_MINLIMIT2 0.0 # CONSTR_RB_MINLIMIT3 0.0 # CONSTR_RB_MINLIMIT4 0.0 # CONSTR_RB_MINLIMIT5 0.0 # CONSTR_RB_PIVX 0.0 # CONSTR_RB_PIVY 0.0 # CONSTR_RB_PIVZ 0.0 # CONSTR_RB_TYPE 12 # LIMIT 63 # PARSIZEY 63 # TARGET [Object "capsule.002"] # limit 3, 4, 5 correspond to angular limits along x, y and z # and are measured in degrees # pivx/y/z is the pivot point # set constraint target b_constr.target = \ self.nif_import.dict_havok_objects[hkconstraint.entities[1]][0] # set rigid body type (generic) b_constr.pivot_type = 'GENERIC_6_DOF' # limiting parameters (limit everything) b_constr.use_angular_limit_x = True b_constr.use_angular_limit_y = True b_constr.use_angular_limit_z = True # get pivot point pivot = mathutils.Vector( (hkdescriptor.pivot_a.x * self.HAVOK_SCALE, hkdescriptor.pivot_a.y * self.HAVOK_SCALE, hkdescriptor.pivot_a.z * self.HAVOK_SCALE)) # get z- and x-axes of the constraint # (also see export_nif.py NifImport.export_constraints) if isinstance(hkdescriptor, NifFormat.RagdollDescriptor): b_constr.pivot_type = 'CONE_TWIST' # for ragdoll, take z to be the twist axis (central axis of the # cone, that is) axis_z = mathutils.Vector( (hkdescriptor.twist_a.x, hkdescriptor.twist_a.y, hkdescriptor.twist_a.z)) # for ragdoll, let x be the plane vector axis_x = mathutils.Vector( (hkdescriptor.plane_a.x, hkdescriptor.plane_a.y, hkdescriptor.plane_a.z)) # set the angle limits # (see http://niftools.sourceforge.net/wiki/Oblivion/Bhk_Objects/Ragdoll_Constraint # for a nice picture explaining this) b_constr.limit_angle_min_x = \ hkdescriptor.plane_min_angle b_constr.limit_angle_max_x = \ hkdescriptor.plane_max_angle b_constr.limit_angle_min_y = \ -hkdescriptor.cone_max_angle b_constr.limit_angle_max_y = \ hkdescriptor.cone_max_angle b_constr.limit_angle_min_z = \ hkdescriptor.twist_min_angle b_constr.limit_angle_max_z = \ hkdescriptor.twist_max_angle b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction elif isinstance(hkdescriptor, NifFormat.LimitedHingeDescriptor): # for hinge, y is the vector on the plane of rotation defining # the zero angle axis_y = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_1.x, hkdescriptor.perp_2_axle_in_a_1.y, hkdescriptor.perp_2_axle_in_a_1.z)) # for hinge, take x to be the the axis of rotation # (this corresponds with Blender's convention for hinges) axis_x = mathutils.Vector( (hkdescriptor.axle_a.x, hkdescriptor.axle_a.y, hkdescriptor.axle_a.z)) # for hinge, z is the vector on the plane of rotation defining # the positive direction of rotation axis_z = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_2.x, hkdescriptor.perp_2_axle_in_a_2.y, hkdescriptor.perp_2_axle_in_a_2.z)) # they should form a orthogonal basis if (mathutils.Vector.cross(axis_x, axis_y) - axis_z).length > 0.01: # either not orthogonal, or negative orientation if (mathutils.Vector.cross(-axis_x, axis_y) - axis_z).length > 0.01: NifLog.warn( "Axes are not orthogonal in {0}; Arbitrary orientation has been chosen" .format(hkdescriptor.__class__.__name__)) axis_z = mathutils.Vector.cross(axis_x, axis_y) else: # fix orientation NifLog.warn( "X axis flipped in {0} to fix orientation".format( hkdescriptor.__class__.__name__)) axis_x = -axis_x # getting properties with no blender constraint # equivalent and setting as obj properties b_constr.limit_angle_max_x = hkdescriptor.max_angle b_constr.limit_angle_min_x = hkdescriptor.min_angle b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction if hasattr(hkconstraint, "tau"): b_hkobj.niftools_constraint.tau = hkconstraint.tau b_hkobj.niftools_constraint.damping = hkconstraint.damping elif isinstance(hkdescriptor, NifFormat.HingeDescriptor): # for hinge, y is the vector on the plane of rotation defining # the zero angle axis_y = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_1.x, hkdescriptor.perp_2_axle_in_a_1.y, hkdescriptor.perp_2_axle_in_a_1.z)) # for hinge, z is the vector on the plane of rotation defining # the positive direction of rotation axis_z = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_2.x, hkdescriptor.perp_2_axle_in_a_2.y, hkdescriptor.perp_2_axle_in_a_2.z)) # take x to be the the axis of rotation # (this corresponds with Blender's convention for hinges) axis_x = mathutils.Vector.cross(axis_y, axis_z) b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction else: raise ValueError("unknown descriptor %s" % hkdescriptor.__class__.__name__) # transform pivot point and constraint matrix into object # coordinates # (also see export_nif.py NifImport.export_constraints) # the pivot point v is in hkbody coordinates # however blender expects it in object coordinates, v' # v * R * B = v' * O * T * B' # with R = rigid body transform (usually unit tf) # B = nif bone matrix # O = blender object transform # T = bone tail matrix (translation in Y direction) # B' = blender bone matrix # so we need to cancel out the object transformation by # v' = v * R * B * B'^{-1} * T^{-1} * O^{-1} # the local rotation L at the pivot point must be such that # (axis_z + v) * R * B = ([0 0 1] * L + v') * O * T * B' # so (taking the rotation parts of all matrices!!!) # [0 0 1] * L = axis_z * R * B * B'^{-1} * T^{-1} * O^{-1} # and similarly # [1 0 0] * L = axis_x * R * B * B'^{-1} * T^{-1} * O^{-1} # hence these give us the first and last row of L # which is exactly enough to provide the euler angles # multiply with rigid body transform if isinstance(hkbody, NifFormat.bhkRigidBodyT): # set rotation transform = mathutils.Quaternion( (hkbody.rotation.w, hkbody.rotation.x, hkbody.rotation.y, hkbody.rotation.z)).to_matrix() transform.resize_4x4() # set translation transform[0][3] = hkbody.translation.x * self.HAVOK_SCALE transform[1][3] = hkbody.translation.y * self.HAVOK_SCALE transform[2][3] = hkbody.translation.z * self.HAVOK_SCALE # apply transform pivot = pivot * transform transform = transform.to_3x3() axis_z = axis_z * transform axis_x = axis_x * transform # TODO: update this to use the new bone system # # next, cancel out bone matrix correction # # note that B' = X * B with X = self.nif_import.dict_bones_extra_matrix[B] # # so multiply with the inverse of X # for niBone in self.nif_import.dict_bones_extra_matrix: # if niBone.collision_object \ # and niBone.collision_object.body is hkbody: # transform = mathutils.Matrix( # self.nif_import.dict_bones_extra_matrix[niBone]) # transform.invert() # pivot = pivot * transform # transform = transform.to_3x3() # axis_z = axis_z * transform # axis_x = axis_x * transform # break # cancel out bone tail translation if b_hkobj.parent_bone: pivot[1] -= b_hkobj.parent.data.bones[ b_hkobj.parent_bone].length # cancel out object transform transform = mathutils.Matrix(b_hkobj.matrix_local) transform.invert() pivot = pivot * transform transform = transform.to_3x3() axis_z = axis_z * transform axis_x = axis_x * transform # set pivot point b_constr.pivot_x = pivot[0] b_constr.pivot_y = pivot[1] b_constr.pivot_z = pivot[2] # set euler angles constr_matrix = mathutils.Matrix( (axis_x, mathutils.Vector.cross(axis_z, axis_x), axis_z)) constr_euler = constr_matrix.to_euler() b_constr.axis_x = constr_euler.x b_constr.axis_y = constr_euler.y b_constr.axis_z = constr_euler.z # DEBUG assert ((axis_x - mathutils.Vector( (1, 0, 0)) * constr_matrix).length < 0.0001) assert ((axis_z - mathutils.Vector( (0, 0, 1)) * constr_matrix).length < 0.0001) # the generic rigid body type is very buggy... so for simulation # purposes let's transform it into ball and hinge if isinstance(hkdescriptor, NifFormat.RagdollDescriptor): # cone_twist b_constr.pivot_type = 'CONE_TWIST' elif isinstance( hkdescriptor, (NifFormat.LimitedHingeDescriptor, NifFormat.HingeDescriptor)): # (limited) hinge b_constr.pivot_type = 'HINGE' else: raise ValueError("unknown descriptor %s" % hkdescriptor.__class__.__name__)
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.nif_import.dict_armatures: self.nif_import.dict_armatures[skelroot] = [] NifLog.info("Selecting node '%s' as skeleton root".format( skelroot.name)) # add bones for bone in skelroot.tree(): if bone is skelroot: continue if not isinstance(bone, NifFormat.NiNode): continue if self.nif_import.is_grouping_node(bone): continue if bone not in self.nif_import.dict_armatures[skelroot]: self.nif_import.dict_armatures[skelroot].append(bone) return # done! # attaching to selected armature -> first identify armature and bones elif NifOp.props.skeleton == "GEOMETRY_ONLY" and not self.nif_import.dict_armatures: skelroot = niBlock.find( block_name=self.nif_import.selected_objects[0].name) if not skelroot: raise nif_utils.NifError( "nif has no armature '%s'" % self.nif_import.selected_objects[0].name) NifLog.debug("Identified '{0}' as armature".format(skelroot.name)) self.nif_import.dict_armatures[skelroot] = [] for bone_name in self.nif_import.selected_objects[ 0].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.nif_import.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.nif_import.dict_armatures: self.nif_import.dict_armatures[skelroot] = [] NifLog.debug("'{0}' is an armature".format( skelroot.name)) elif NifOp.props.skeleton == "GEOMETRY_ONLY": if skelroot not in self.nif_import.dict_armatures: raise nif_utils.NifError( "nif structure incompatible with '%s' as armature:" " node '%s' has '%s' as armature" % (self.nif_import.selected_objects[0].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.nif_import.dict_armatures[ skelroot]: self.nif_import.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 if asked if self.nif_import.IMPORT_EXTRANODES: # add bones 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.nif_import.dict_armatures[ skelroot]: self.nif_import.dict_armatures[skelroot].append( bone) NifLog.debug( "'{0}' marked as extra bone of armature '{1}'". format(bone.name, skelroot.name)) # we make sure all NiNodes from this bone # all the way down to the armature NiNode # are marked as bones self.complete_bone_tree(bone, 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)
def execute(self): """Main export function.""" if bpy.context.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT', toggle=False) NifLog.info("Exporting {0}".format(NifOp.props.filepath)) # TODO: ''' if NifOp.props.animation == 'ALL_NIF_XNIF_XKF' and NifOp.props.game == 'MORROWIND': # if exporting in nif+xnif+kf mode, then first export # the nif with geometry + animation, which is done by: NifOp.props.animation = 'ALL_NIF' ''' # extract directory, base name, extension directory = os.path.dirname(NifOp.props.filepath) filebase, fileext = os.path.splitext( os.path.basename(NifOp.props.filepath)) self.dict_armatures = {} self.dict_bone_priorities = {} self.dict_havok_objects = {} self.dict_names = {} self.dict_blocks = {} self.dict_block_names = [] self.dict_materials = {} self.dict_textures = {} self.dict_mesh_uvlayers = [] # if an egm is exported, this will contain the data self.egm_data = None try: # catch export errors for b_obj in bpy.data.objects: # armatures should not be in rest position if b_obj.type == 'ARMATURE': # ensure we get the mesh vertices in animation mode, # and not in rest position! b_obj.data.pose_position = 'POSE' if b_obj.type == 'MESH': # TODO - Need to implement correct armature checking # b_armature_modifier = None b_obj_name = b_obj.name if b_obj.parent: for b_mod in bpy.data.objects[b_obj_name].modifiers: if b_mod.type == 'ARMATURE': # b_armature_modifier = b_mod.name if b_mod.use_bone_envelopes: raise nif_utils.NifError( "'%s': Cannot export envelope skinning. If you have vertex groups, turn off envelopes.\n" "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." % b_obj.name) # if not b_armature_modifier: # raise nif_utils.NifError("'%s': is parented but does not have" # " the armature modifier set. This will" # " cause animations to fail." # % b_obj.name) # check for non-uniform transforms # (lattices are not exported so ignore them as they often tend # to have non-uniform scaling) if b_obj.type != 'LATTICE': scale = b_obj.matrix_local.to_scale() if abs(scale.x - scale.y) > NifOp.props.epsilon or abs( scale.y - scale.z) > NifOp.props.epsilon: raise nif_utils.NifError( "Non-uniform scaling not supported.\n " "Workaround: apply size and rotation (CTRL-A) on '%s'." % b_obj.name) root_name = filebase # get the root object from selected object # only export empties, meshes, and armatures if not bpy.context.selected_objects: raise nif_utils.NifError( "Please select the object(s) to export, and run this script again." ) root_objects = set() export_types = ('EMPTY', 'MESH', 'ARMATURE') exportable_objects = [ b_obj for b_obj in bpy.context.selected_objects if b_obj.type in export_types ] for root_object in exportable_objects: while root_object.parent: root_object = root_object.parent if NifOp.props.game in ('CIVILIZATION_IV', 'OBLIVION', 'FALLOUT_3', 'ZOO_TYCOON_2'): if (root_object.type == 'ARMATURE') or (root_object.name.lower() == "bip01"): root_name = 'Scene Root' # TODO remove as already filtered if root_object.type not in export_types: raise nif_utils.NifError( "Root object (%s) must be an 'EMPTY', 'MESH', or 'ARMATURE' object." % root_object.name) root_objects.add(root_object) # smooth seams of objects if NifOp.props.smooth_object_seams: self.objecthelper.mesh_helper.smooth_mesh_seams( bpy.context.scene.objects) # TODO: use Blender actions for animation groups # check for animation groups definition in a text buffer 'Anim' try: animtxt = None # Changed for testing needs fix bpy.data.texts["Anim"] except NameError: animtxt = None # rebuild the full name dictionary from the 'FullNames' text buffer self.objecthelper.rebuild_full_names() # export nif: # ----------- NifLog.info("Exporting") # find nif version to write # TODO Move fully to scene level self.version = NifOp.op.version[NifOp.props.game] self.user_version, self.user_version_2 = scene_export.get_version_info( NifOp.props) #the axes used for bone correction depend on the nif version armature.set_bone_correction_from_version(self.version) # create a nif object # export the root node (the name is fixed later to avoid confusing the # exporter with duplicate names) root_block = self.objecthelper.export_node(None, None, '') # TODO Move to object system and redo # export objects NifLog.info("Exporting objects") for root_object in root_objects: if NifOp.props.game in 'SKYRIM': if root_object.niftools_bs_invmarker: for extra_item in root_block.extra_data_list: if isinstance(extra_item, NifFormat.BSInvMarker): raise nif_utils.NifError( "Multiple Items have Inventory marker data only one item may contain this data" ) else: n_extra_list = NifFormat.BSInvMarker() n_extra_list.name = root_object.niftools_bs_invmarker[ 0].name.encode() n_extra_list.rotation_x = root_object.niftools_bs_invmarker[ 0].bs_inv_x n_extra_list.rotation_y = root_object.niftools_bs_invmarker[ 0].bs_inv_y n_extra_list.rotation_z = root_object.niftools_bs_invmarker[ 0].bs_inv_z n_extra_list.zoom = root_object.niftools_bs_invmarker[ 0].bs_inv_zoom root_block.add_extra_data(n_extra_list) # export the root objects as a NiNodes; their children are # exported as well self.objecthelper.export_node(root_object, root_block, root_object.name) # post-processing: # ---------------- # if we exported animations, but no animation groups are defined, # define a default animation group NifLog.info("Checking animation groups") if not animtxt: has_controllers = False for block in self.dict_blocks: # has it a controller field? if isinstance(block, NifFormat.NiObjectNET): if block.controller: has_controllers = True break if has_controllers: NifLog.info("Defining default animation group.") # write the animation group text buffer animtxt = bpy.data.texts.new("Anim") animtxt.write( "%i/Idle: Start/Idle: Loop Start\n%i/Idle: Loop Stop/Idle: Stop" % (bpy.context.scene.frame_start, bpy.context.scene.frame_end)) # animations without keyframe animations crash the TESCS # if we are in that situation, add a trivial keyframe animation NifLog.info("Checking controllers") if animtxt and NifOp.props.game == 'MORROWIND': has_keyframecontrollers = False for block in self.dict_blocks: if isinstance(block, NifFormat.NiKeyframeController): has_keyframecontrollers = True break if ((not has_keyframecontrollers) and (not NifOp.props.bs_animation_node)): NifLog.info("Defining dummy keyframe controller") # add a trivial keyframe controller on the scene root self.animationhelper.export_keyframes(root_block) if NifOp.props.bs_animation_node and NifOp.props.game == 'MORROWIND': for block in self.dict_blocks: if isinstance(block, NifFormat.NiNode): # if any of the shape children has a controller # or if the ninode has a controller # convert its type if block.controller or any( child.controller for child in block.children if isinstance(child, NifFormat.NiGeometry)): new_block = NifFormat.NiBSAnimationNode().deepcopy( block) # have to change flags to 42 to make it work new_block.flags = 42 root_block.replace_global_node(block, new_block) if root_block is block: root_block = new_block # oblivion skeleton export: check that all bones have a # transform controller and transform interpolator if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM') and filebase.lower() in ( 'skeleton', 'skeletonbeast'): # here comes everything that is Oblivion skeleton export specific NifLog.info( "Adding controllers and interpolators for skeleton") for block in list(self.dict_blocks.keys()): if isinstance(block, NifFormat.NiNode ) and block.name.decode() == "Bip01": for bone in block.tree(block_type=NifFormat.NiNode): ctrl = self.objecthelper.create_block( "NiTransformController") interp = self.objecthelper.create_block( "NiTransformInterpolator") ctrl.interpolator = interp bone.add_controller(ctrl) ctrl.flags = 12 ctrl.frequency = 1.0 ctrl.phase = 0.0 ctrl.start_time = self.FLOAT_MAX ctrl.stop_time = self.FLOAT_MIN interp.translation.x = bone.translation.x interp.translation.y = bone.translation.y interp.translation.z = bone.translation.z scale, quat = bone.rotation.get_scale_quat() interp.rotation.x = quat.x interp.rotation.y = quat.y interp.rotation.z = quat.z interp.rotation.w = quat.w interp.scale = bone.scale else: # here comes everything that should be exported EXCEPT # for Oblivion skeleton exports # export animation groups (not for skeleton.nif export!) if animtxt: # TODO: removed temorarily to process bseffectshader export anim_textextra = None # self.animationhelper.export_text_keys(root_block) else: anim_textextra = None # oblivion and Fallout 3 furniture markers if NifOp.props.game in ( 'OBLIVION', 'FALLOUT_3', 'SKYRIM') and filebase[:15].lower() == 'furnituremarker': # exporting a furniture marker for Oblivion/FO3 try: furniturenumber = int(filebase[15:]) except ValueError: raise nif_utils.NifError( "Furniture marker has invalid number (%s).\n" "Name your file 'furnituremarkerxx.nif' where xx is a number between 00 and 19." % filebase[15:]) # name scene root name the file base name root_name = filebase # create furniture marker block furnmark = self.objecthelper.create_block("BSFurnitureMarker") furnmark.name = "FRN" furnmark.num_positions = 1 furnmark.positions.update_size() furnmark.positions[0].position_ref_1 = furniturenumber furnmark.positions[0].position_ref_2 = furniturenumber # create extra string data sgoKeep sgokeep = self.objecthelper.create_block("NiStringExtraData") sgokeep.name = "UPB" # user property buffer sgokeep.string_data = "sgoKeep=1 ExportSel = Yes" # Unyielding = 0, sgoKeep=1ExportSel = Yes # add extra blocks root_block.add_extra_data(furnmark) root_block.add_extra_data(sgokeep) # FIXME: NifLog.info("Checking collision") # activate oblivion/Fallout 3 collision and physics if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'): hascollision = False for b_obj in bpy.data.objects: if b_obj.game.use_collision_bounds: hascollision = True break if hascollision: # enable collision bsx = self.objecthelper.create_block("BSXFlags") bsx.name = 'BSX' bsx.integer_data = b_obj.niftools.bsxflags root_block.add_extra_data(bsx) # many Oblivion nifs have a UPB, but export is disabled as # they do not seem to affect anything in the game if b_obj.niftools.upb: upb = self.objecthelper.create_block( "NiStringExtraData") upb.name = 'UPB' if b_obj.niftools.upb == '': upb.string_data = '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' else: upb.string_data = b_obj.niftools.upb.encode() root_block.add_extra_data(upb) # update rigid body center of gravity and mass if self.EXPORT_OB_COLLISION_DO_NOT_USE_BLENDER_PROPERTIES: # we are not using blender properties to set the mass # so calculate mass automatically first calculate distribution of mass total_mass = 0 for block in self.dict_blocks: if isinstance(block, NifFormat.bhkRigidBody): block.update_mass_center_inertia( solid=self.EXPORT_OB_SOLID) total_mass += block.mass if total_mass == 0: # to avoid zero division error later (if mass is zero then this does not matter anyway) total_mass = 1 # now update the mass ensuring that total mass is self.EXPORT_OB_MASS for block in self.dict_blocks: if isinstance(block, NifFormat.bhkRigidBody): mass = self.EXPORT_OB_MASS * block.mass / total_mass # lower bound on mass if mass < 0.0001: mass = 0.05 block.update_mass_center_inertia( mass=mass, solid=self.EXPORT_OB_SOLID) else: # using blender properties, so block.mass *should* have # been set properly for block in self.dict_blocks: if isinstance(block, NifFormat.bhkRigidBody): # lower bound on mass if block.mass < 0.0001: block.mass = 0.05 block.update_mass_center_inertia( mass=block.mass, solid=self.EXPORT_OB_SOLID) # bhkConvexVerticesShape of children of bhkListShapes need an extra bhkConvexTransformShape (see issue #3308638, reported by Koniption) # note: self.dict_blocks changes during iteration, so need list copy for block in list(self.dict_blocks): if isinstance(block, NifFormat.bhkListShape): for i, sub_shape in enumerate(block.sub_shapes): if isinstance(sub_shape, NifFormat.bhkConvexVerticesShape): coltf = self.objecthelper.create_block( "bhkConvexTransformShape") coltf.material = sub_shape.material coltf.unknown_float_1 = 0.1 coltf.unknown_8_bytes[0] = 96 coltf.unknown_8_bytes[1] = 120 coltf.unknown_8_bytes[2] = 53 coltf.unknown_8_bytes[3] = 19 coltf.unknown_8_bytes[4] = 24 coltf.unknown_8_bytes[5] = 9 coltf.unknown_8_bytes[6] = 253 coltf.unknown_8_bytes[7] = 4 coltf.transform.set_identity() coltf.shape = sub_shape block.sub_shapes[i] = coltf # export constraints for b_obj in self.objecthelper.get_exported_objects(): if isinstance(b_obj, bpy.types.Object) and b_obj.constraints: self.constrainthelper.export_constraints(b_obj, root_block) # export weapon location if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'): if self.EXPORT_OB_PRN != "NONE": # add string extra data prn = self.objecthelper.create_block("NiStringExtraData") prn.name = 'Prn' prn.string_data = { "BACK": "BackWeapon", "SIDE": "SideWeapon", "QUIVER": "Quiver", "SHIELD": "Bip01 L ForearmTwist", "HELM": "Bip01 Head", "RING": "Bip01 R Finger1" }[self.EXPORT_OB_PRN] root_block.add_extra_data(prn) # add vertex color and zbuffer properties for civ4 and railroads if NifOp.props.game in ('CIVILIZATION_IV', 'SID_MEIER_S_RAILROADS'): self.propertyhelper.object_property.export_vertex_color_property( root_block) self.propertyhelper.object_property.export_z_buffer_property( root_block) elif NifOp.props.game in ('EMPIRE_EARTH_II', ): self.propertyhelper.object_property.export_vertex_color_property( root_block) self.propertyhelper.object_property.export_z_buffer_property( root_block, flags=15, function=1) # FIXME: """ if self.EXPORT_FLATTENSKIN: # (warning: trouble if armatures parent other armatures or # if bones parent geometries, or if object is animated) # flatten skins skelroots = set() affectedbones = [] for block in self.dict_blocks: if isinstance(block, NifFormat.NiGeometry) and block.is_skin(): NifLog.info("Flattening skin on geometry {0}".format(block.name)) affectedbones.extend(block.flatten_skin()) skelroots.add(block.skin_instance.skeleton_root) # remove NiNodes that do not affect skin for skelroot in skelroots: NifLog.info("Removing unused NiNodes in '{0}'".format(skelroot.name)) skelrootchildren = [child for child in skelroot.children if ((not isinstance(child, NifFormat.NiNode)) or (child in affectedbones))] skelroot.num_children = len(skelrootchildren) skelroot.children.update_size() for i, child in enumerate(skelrootchildren): skelroot.children[i] = child """ # apply scale if abs(NifOp.props.scale_correction_export) > NifOp.props.epsilon: NifLog.info("Applying scale correction {0}".format( str(NifOp.props.scale_correction_export))) data = NifFormat.Data() data.roots = [root_block] toaster = pyffi.spells.nif.NifToaster() toaster.scale = NifOp.props.scale_correction_export pyffi.spells.nif.fix.SpellScale(data=data, toaster=toaster).recurse() # also scale egm if self.egm_data: self.egm_data.apply_scale( NifOp.props.scale_correction_export) # generate mopps (must be done after applying scale!) if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'): for block in self.dict_blocks: if isinstance(block, NifFormat.bhkMoppBvTreeShape): NifLog.info("Generating mopp...") block.update_mopp() # print "=== DEBUG: MOPP TREE ===" # block.parse_mopp(verbose = True) # print "=== END OF MOPP TREE ===" # warn about mopps on non-static objects if any(sub_shape.layer != 1 for sub_shape in block.shape.sub_shapes): NifLog.warn( "Mopps for non-static objects may not function correctly in-game. You may wish to use simple primitives for collision." ) # delete original scene root if a scene root object was already defined if root_block.num_children == 1 and ( root_block.children[0].name in ['Scene Root', 'Bip01'] or root_block.children[0].name[-3:] == 'nif'): if root_block.children[0].name[-3:] == 'nif': root_block.children[0].name = filebase NifLog.info("Making '{0}' the root block".format( root_block.children[0].name)) # remove root_block from self.dict_blocks self.dict_blocks.pop(root_block) # set new root block old_root_block = root_block root_block = old_root_block.children[0] # copy extra data and properties for extra in old_root_block.get_extra_datas(): # delete links in extras to avoid parentship problems extra.next_extra_data = None # now add it root_block.add_extra_data(extra) for b in old_root_block.get_controllers(): root_block.add_controller(b) for b in old_root_block.properties: root_block.add_property(b) for b in old_root_block.effects: root_block.add_effect(b) else: root_block.name = root_name self.root_ninode = None for root_obj in root_objects: if root_obj.niftools.rootnode == 'BSFadeNode': self.root_ninode = 'BSFadeNode' elif self.root_ninode is None: self.root_ninode = 'NiNode' # making root block a fade node if NifOp.props.game in ('FALLOUT_3', 'SKYRIM' ) and self.root_ninode == 'BSFadeNode': NifLog.info("Making root block a BSFadeNode") fade_root_block = NifFormat.BSFadeNode().deepcopy(root_block) fade_root_block.replace_global_node(root_block, fade_root_block) root_block = fade_root_block export_animation = NifOp.props.animation if export_animation == 'ALL_NIF': NifLog.info("Exporting geometry and animation") elif export_animation == 'GEOM_NIF': # for morrowind: everything except keyframe controllers NifLog.info("Exporting geometry only") elif export_animation == 'ANIM_KF': # for morrowind: only keyframe controllers NifLog.info("Exporting animation only (as .kf file)") # export nif file: # ---------------- NifLog.info("Writing NIF version 0x%08X" % self.version) if export_animation != 'ANIM_KF': if NifOp.props.game == 'EMPIRE_EARTH_II': ext = ".nifcache" else: ext = ".nif" NifLog.info("Writing {0} file".format(ext)) # make sure we have the right file extension if fileext.lower() != ext: NifLog.warn( "Changing extension from {0} to {1} on output file". format(fileext, ext)) niffile = os.path.join(directory, filebase + ext) data = NifFormat.Data(version=self.version, user_version=self.user_version, user_version_2=self.user_version_2) data.roots = [root_block] if NifOp.props.game == 'NEOSTEAM': data.modification = "neosteam" elif NifOp.props.game == 'ATLANTICA': data.modification = "ndoors" elif NifOp.props.game == 'HOWLING_SWORD': data.modification = "jmihs1" with open(niffile, "wb") as stream: data.write(stream) # create and export keyframe file and xnif file: # ---------------------------------------------- # convert root_block tree into a keyframe tree if export_animation == 'ANIM_KF' or export_animation == 'ALL_NIF_XNIF_XKF': NifLog.info("Creating keyframe tree") # find all nodes and relevant controllers node_kfctrls = {} for node in root_block.tree(): if not isinstance(node, NifFormat.NiAVObject): continue # get list of all controllers for this node ctrls = node.get_controllers() for ctrl in ctrls: if NifOp.props.game == 'MORROWIND': # morrowind: only keyframe controllers if not isinstance(ctrl, NifFormat.NiKeyframeController): continue if node not in node_kfctrls: node_kfctrls[node] = [] node_kfctrls[node].append(ctrl) # morrowind if NifOp.props.game in ('MORROWIND', 'FREEDOM_FORCE'): # create kf root header kf_root = self.objecthelper.create_block( "NiSequenceStreamHelper") kf_root.add_extra_data(anim_textextra) # reparent controller tree for node, ctrls in node_kfctrls.items(): for ctrl in ctrls: # create node reference by name nodename_extra = self.objecthelper.create_block( "NiStringExtraData") nodename_extra.bytes_remaining = len(node.name) + 4 nodename_extra.string_data = node.name # break the controller chain ctrl.next_controller = None # add node reference and controller kf_root.add_extra_data(nodename_extra) kf_root.add_controller(ctrl) # wipe controller target ctrl.target = None # oblivion elif NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'CIVILIZATION_IV', 'ZOO_TYCOON_2', 'FREEDOM_FORCE_VS_THE_3RD_REICH'): # TODO [animation] allow for object kf only # create kf root header kf_root = self.objecthelper.create_block( "NiControllerSequence") # if self.EXPORT_ANIMSEQUENCENAME: # kf_root.name = self.EXPORT_ANIMSEQUENCENAME # else: kf_root.name = filebase kf_root.unknown_int_1 = 1 kf_root.weight = 1.0 kf_root.text_keys = anim_textextra kf_root.cycle_type = NifFormat.CycleType.CYCLE_CLAMP kf_root.frequency = 1.0 kf_root.start_time = bpy.context.scene.frame_start * bpy.context.scene.render.fps kf_root.stop_time = (bpy.context.scene.frame_end - bpy.context.scene.frame_start ) * bpy.context.scene.render.fps # quick hack to set correct target name if "Bip01" in b_armature.data.bones: targetname = "Bip01" elif "Bip02" in b_armature.data.bones: targetname = "Bip02" else: targetname = root_block.name kf_root.target_name = targetname kf_root.string_palette = NifFormat.NiStringPalette() b_armature = armature.get_armature() # per-node animation if b_armature: for b_bone in b_armature.data.bones: self.animationhelper.export_keyframes( kf_root, b_armature, b_bone) # per-object animation else: for b_obj in bpy.data.objects: self.animationhelper.export_keyframes( kf_root, b_obj) # for node, ctrls in zip(iter(node_kfctrls.keys()), iter(node_kfctrls.values())): # # export a block for every interpolator in every controller # for ctrl in ctrls: # # XXX add get_interpolators to pyffi interface # if isinstance(ctrl, NifFormat.NiSingleInterpController): # interpolators = [ctrl.interpolator] # elif isinstance( ctrl, (NifFormat.NiGeomMorpherController, NifFormat.NiMorphWeightsController)): # interpolators = ctrl.interpolators # if isinstance(ctrl, NifFormat.NiGeomMorpherController): # variable_2s = [morph.frame_name for morph in ctrl.data.morphs] # else: # variable_2s = [None for interpolator in interpolators] # for interpolator, variable_2 in zip(interpolators, variable_2s): # # create ControlledLink for each interpolator # controlledblock = kf_root.add_controlled_block() # if self.version < 0x0A020000: # # older versions need the actual controller blocks # controlledblock.target_name = node.name # controlledblock.controller = ctrl # # erase reference to target node # ctrl.target = None # else: # # newer versions need the interpolator blocks # controlledblock.interpolator = interpolator # # get bone animation priority (previously fetched from the constraints during export_bones) # if not node.name in self.dict_bone_priorities or self.EXPORT_ANIM_DO_NOT_USE_BLENDER_PROPERTIES: # if self.EXPORT_ANIMPRIORITY != 0: # priority = self.EXPORT_ANIMPRIORITY # else: # priority = 26 # NifLog.warn("No priority set for bone {0}, falling back on default value ({1})".format(node.name, str(priority))) # else: # priority = self.dict_bone_priorities[node.name] # controlledblock.priority = priority # # set palette, and node and controller type names, and variables # controlledblock.string_palette = kf_root.string_palette # controlledblock.set_node_name(node.name) # controlledblock.set_controller_type(ctrl.__class__.__name__) # if variable_2: # controlledblock.set_variable_2(variable_2) else: raise nif_utils.NifError( "Keyframe export for '%s' is not supported.\nOnly Morrowind, Oblivion, Fallout 3, Civilization IV," " Zoo Tycoon 2, Freedom Force, and Freedom Force vs. the 3rd Reich keyframes are supported." % NifOp.props.game) # write kf (and xnif if asked) prefix = "" if ( export_animation != 'ALL_NIF_XNIF_XKF') else "x" ext = ".kf" NifLog.info("Writing {0} file".format(prefix + ext)) kffile = os.path.join(directory, prefix + filebase + ext) data = NifFormat.Data(version=self.version, user_version=self.user_version, user_version_2=self.user_version_2) data.roots = [kf_root] data.neosteam = (NifOp.props.game == 'NEOSTEAM') stream = open(kffile, "wb") try: data.write(stream) finally: stream.close() if export_animation == 'ALL_NIF_XNIF_XKF': NifLog.info("Detaching keyframe controllers from nif") # detach the keyframe controllers from the nif (for xnif) for node in root_block.tree(): if not isinstance(node, NifFormat.NiNode): continue # remove references to keyframe controllers from node # (for xnif) while isinstance(node.controller, NifFormat.NiKeyframeController): node.controller = node.controller.next_controller ctrl = node.controller while ctrl: if isinstance(ctrl.next_controller, NifFormat.NiKeyframeController): ctrl.next_controller = ctrl.next_controller.next_controller else: ctrl = ctrl.next_controller NifLog.info("Detaching animation text keys from nif") # detach animation text keys if root_block.extra_data is not anim_textextra: raise RuntimeError( "Oops, you found a bug! Animation extra data" " wasn't where expected...") root_block.extra_data = None prefix = "x" # we are in morrowind 'nifxnifkf mode' ext = ".nif" NifLog.info("Writing {0} file".format(prefix + ext)) xniffile = os.path.join(directory, prefix + filebase + ext) data = NifFormat.Data(version=self.version, user_version=self.user_version, user_version_2=self.user_version_2) data.roots = [root_block] data.neosteam = (NifOp.props.game == 'NEOSTEAM') stream = open(xniffile, "wb") try: data.write(stream) finally: stream.close() # export egm file: # ----------------- if self.egm_data: ext = ".egm" NifLog.info("Writing {0} file".format(ext)) egmfile = os.path.join(directory, filebase + ext) stream = open(egmfile, "wb") try: self.egm_data.write(stream) finally: stream.close() finally: # clear progress bar NifLog.info("Finished") # save exported file (this is used by the test suite) self.root_blocks = [root_block] return {'FINISHED'}
def import_mesh_controllers(self, n_node, b_obj): """Import mesh controller for blender object.""" morphCtrl = nif_utils.find_controller( n_node, NifFormat.NiGeomMorpherController) if morphCtrl: b_mesh = b_obj.data morphData = morphCtrl.data if morphData.num_morphs: fps = bpy.context.scene.render.fps # insert base key at frame 1, using relative keys b_mesh.insertKey(1, 'relative') # get name for base key keyname = morphData.morphs[0].frame_name if not keyname: keyname = 'Base' # set name for base key b_mesh.key.blocks[0].name = keyname # get base vectors and import all morphs baseverts = morphData.morphs[0].vectors b_ipo = Blender.Ipo.New('Key', 'KeyIpo') b_mesh.key.ipo = b_ipo for idxMorph in range(1, morphData.num_morphs): # get name for key keyname = morphData.morphs[idxMorph].frame_name if not keyname: keyname = 'Key %i' % idxMorph NifLog.info("Inserting key '{0}'".format(keyname)) # get vectors morphverts = morphData.morphs[idxMorph].vectors # for each vertex calculate the key position from base # pos + delta offset assert (len(baseverts) == len(morphverts) == len(v_map)) for bv, mv, b_v_index in zip(baseverts, morphverts, v_map): base = mathutils.Vector(bv.x, bv.y, bv.z) delta = mathutils.Vector(mv.x, mv.y, mv.z) v = base + delta if applytransform: v *= transform b_mesh.vertices[b_v_index].co[0] = v.x b_mesh.vertices[b_v_index].co[1] = v.y b_mesh.vertices[b_v_index].co[2] = v.z # update the mesh and insert key b_mesh.insertKey(idxMorph, 'relative') # set name for key b_mesh.key.blocks[idxMorph].name = keyname # set up the ipo key curve try: b_curve = b_ipo.addCurve(keyname) except ValueError: # this happens when two keys have the same name # an instance of this is in fallout 3 # meshes/characters/_male/skeleton.nif HeadAnims:0 NifLog.warn( "Skipped duplicate of key '{0}'".format(keyname)) # no idea how to set up the bezier triples -> switching # to linear instead b_curve.interpolation = Blender.IpoCurve.InterpTypes.LINEAR # select extrapolation b_curve.extend = self.get_extend_from_flags( morphCtrl.flags) # set up the curve's control points # first find the keys # older versions store keys in the morphData morphkeys = morphData.morphs[idxMorph].keys # newer versions store keys in the controller if (not morphkeys) and morphCtrl.interpolators: morphkeys = morphCtrl.interpolators[ idxMorph].data.data.keys for key in morphkeys: x = key.value frame = 1 + int(key.time * fps + 0.5) b_curve.addBezier((frame, x)) # finally: return to base position for bv, b_v_index in zip(baseverts, v_map): base = mathutils.Vector(bv.x, bv.y, bv.z) if applytransform: base *= transform b_mesh.vertices[b_v_index].co[0] = base.x b_mesh.vertices[b_v_index].co[1] = base.y b_mesh.vertices[b_v_index].co[2] = base.z
def import_object_animation(self, niBlock, b_obj): """ Load animation attached to (Scene Root) object. Becomes the object level animation of the object. """ # TODO: remove code duplication with import_keyframe_controller kfc = nif_utils.find_controller(niBlock, NifFormat.NiKeyframeController) if not kfc: # no animation data: do nothing return if kfc.interpolator: if isinstance(kfc.interpolator, NifFormat.NiBSplineInterpolator): kfd = None # not supported yet so avoids fatal error - should be kfc.interpolator.spline_data when spline data is figured out. else: kfd = kfc.interpolator.data else: kfd = kfc.data if not kfd: # no animation data: do nothing return # denote progress NifLog.info("Animation") NifLog.info("Importing animation data for {0}".format(b_obj.name)) assert (isinstance(kfd, NifFormat.NiKeyframeData)) #get the interpolation modes interp_rot = get_interp_mode(kfd) interp_loc = get_interp_mode(kfd.translations) interp_scale = get_interp_mode(kfd.scales) b_obj_action = self.create_action(b_obj, b_obj.name + "-Anim") if kfd.scales.keys: NifLog.debug('Scale keys...') fcurves = create_fcurves(b_obj_action, "scale", range(3)) for key in kfd.scales.keys: v = (key.value, key.value, key.value) self.add_key(fcurves, key.time, v, interp_scale) # detect the type of rotation keys if kfd.rotation_type == 4: NifLog.debug('Rotation keys...(eulers)') b_obj.rotation_mode = "XYZ" #Eulers are a bit different here, we can import them regardless of their timing #because they need no correction math in object space fcurves = create_fcurves(b_obj_action, "rotation_euler", range(3)) keys = (kfd.xyz_rotations[0].keys, kfd.xyz_rotations[1].keys, kfd.xyz_rotations[2].keys) for fcu, keys_dim in zip(fcurves, keys): for key in keys_dim: self.add_key((fcu, ), key.time, (key.value, ), interp_rot) # uses quaternions elif kfd.quaternion_keys: NifLog.debug('Rotation keys...(quaternions)') b_obj.rotation_mode = "QUATERNION" fcurves = create_fcurves(b_obj_action, "rotation_quaternion", range(4)) for key in kfd.quaternion_keys: v = (key.value.w, key.value.x, key.value.y, key.value.z) self.add_key(fcurves, key.time, v, interp_rot) if kfd.translations.keys: NifLog.debug('Translation keys...') fcurves = create_fcurves(b_obj_action, "location", range(3)) for key in kfd.translations.keys: v = (key.value.x, key.value.y, key.value.z) self.add_key(fcurves, key.time, v, interp_rot)
def export_text_keys( self, block_parent, ): """Parse the animation groups buffer and write an extra string data block, and attach it to an existing block (typically, the root of the nif tree).""" if NifOp.props.animation == 'GEOM_NIF': # animation group extra data is not present in geometry only files return if "Anim" not in bpy.data.texts: return animtxt = bpy.data.texts["Anim"] NifLog.info("Exporting animation groups") # -> get animation groups information # parse the anim text descriptor # the format is: # frame/string1[/string2[.../stringN]] # example: # 001/Idle: Start/Idle: Stop/Idle2: Start/Idle2: Loop Start # 051/Idle2: Stop/Idle3: Start # 101/Idle3: Loop Start/Idle3: Stop slist = animtxt.asLines() flist = [] dlist = [] for s in slist: # ignore empty lines if not s: continue # parse line t = s.split('/') if (len(t) < 2): raise nif_utils.NifError("Syntax error in Anim buffer ('%s')" % s) f = int(t[0]) if ((f < bpy.context.scene.frame_start) or (f > bpy.context.scene.frame_end)): NifLog.warn( "Frame in animation buffer out of range ({0} not between [{1}, {2}])" .format(str(f), str(bpy.context.scene.frame_start), str(bpy.context.scene.frame_end))) d = t[1].strip() for i in range(2, len(t)): d = d + '\r\n' + t[i].strip() #print 'frame %d'%f + ' -> \'%s\''%d # debug flist.append(f) dlist.append(d) # -> now comes the real export # add a NiTextKeyExtraData block, and refer to this block in the # parent node (we choose the root block) textextra = self.nif_export.objecthelper.create_block( "NiTextKeyExtraData", animtxt) block_parent.add_extra_data(textextra) # create a text key for each frame descriptor textextra.num_text_keys = len(flist) textextra.text_keys.update_size() for i, key in enumerate(textextra.text_keys): key.time = flist[i] / self.fps key.value = dlist[i] return textextra
def wrap(*args): time1 = time.time() ret = f(*args) time2 = time.time() NifLog.info('{:s} function took {:.3f} ms'.format(f.__name__, (time2 - time1) * 1000.0)) return ret