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)
Пример #9
0
    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]
Пример #10
0
    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    
Пример #11
0
    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
Пример #12
0
    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
Пример #13
0
    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__)
Пример #14
0
    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)
Пример #15
0
    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