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 export_flip_controller(self, fliptxt, texture, target, target_tex):
        ## TODO:port code to use native Blender texture flipping system
        #
        # export a NiFlipController
        #
        # fliptxt is a blender text object containing the flip definitions
        # texture is the texture object in blender ( texture is used to checked for pack and mipmap flags )
        # target is the NiTexturingProperty
        # target_tex is the texture to flip ( 0 = base texture, 4 = glow texture )
        #
        # returns exported NiFlipController
        #
        tlist = fliptxt.asLines()

        # create a NiFlipController
        flip = self.nif_export.objecthelper.create_block(
            "NiFlipController", fliptxt)
        target.add_controller(flip)

        # fill in NiFlipController's values
        flip.flags = 8  # active
        flip.frequency = 1.0
        flip.start_time = (bpy.context.scene.frame_start -
                           1) * bpy.context.scene.render.fps
        flip.stop_time = (
            bpy.context.scene.frame_end -
            bpy.context.scene.frame_start) * bpy.context.scene.render.fps
        flip.texture_slot = target_tex
        count = 0
        for t in tlist:
            if len(t) == 0: continue  # skip empty lines
            # create a NiSourceTexture for each flip
            tex = self.nif_export.texturehelper.texture_writer.export_source_texture(
                texture, t)
            flip.num_sources += 1
            flip.sources.update_size()
            flip.sources[flip.num_sources - 1] = tex
            count += 1
        if count < 2:
            raise nif_utils.NifError("Error in Texture Flip buffer '%s':"
                                     " must define at least two textures" %
                                     fliptxt.name)
        flip.delta = (flip.stop_time - flip.start_time) / count
Ejemplo n.º 3
0
    def export_collision(self, b_obj, parent_block):
        """Main function for adding collision object b_obj to a node."""
        if NifOp.props.game == 'MORROWIND':
            if b_obj.game.collision_bounds_type != 'TRIANGLE_MESH':
                raise nif_utils.NifError(
                    "Morrowind only supports Triangle Mesh collisions.")
            node = self.objecthelper.create_block("RootCollisionNode", b_obj)
            parent_block.add_child(node)
            node.flags = 0x0003  # default
            self.objecthelper.set_object_matrix(b_obj, node)
            for child in b_obj.children:
                self.objecthelper.export_node(child, node, None)

        elif NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'):

            nodes = [parent_block]
            nodes.extend([
                block for block in parent_block.children
                if block.name[:14] == 'collisiondummy'
            ])
            for node in nodes:
                try:
                    self.bhkshapehelper.export_collision_helper(b_obj, node)
                    break
                except ValueError:  # adding collision failed
                    continue
            else:  # all nodes failed so add new one
                node = self.objecthelper.create_ninode(b_obj)
                node.set_transform(self.IDENTITY44)
                node.name = 'collisiondummy%i' % parent_block.num_children
                if b_obj.niftools.objectflags != 0:
                    node_flag_hex = hex(b_obj.niftools.objectflags)
                else:
                    node_flag_hex = 0x000E  # default
                node.flags = node_flag_hex
                parent_block.add_child(node)
                self.bhkshapehelper.export_collision_helper(b_obj, node)

        else:
            NifLog.warn(
                "Only Morrowind, Oblivion, and Fallout 3 collisions are supported, skipped collision object '{0}'"
                .format(b_obj.name))
Ejemplo n.º 4
0
    def execute(self):
        """Main import function."""

        dirname = os.path.dirname(NifOp.props.filepath)
        kf_files = [os.path.join(dirname, file.name) for file in NifOp.props.files if file.name.lower().endswith(".kf")]
        b_armature = armature.get_armature()
        if not b_armature:
            raise nif_utils.NifError("No armature was found in scene, can not import KF animation!")
        
        #get nif space bind pose of armature here for all anims
        bind_data = armature.get_bind_data(b_armature)
        for kf_file in kf_files:
            kfdata = KFFile.load_kf(kf_file)
            #the axes used for bone correction depend on the nif version
            armature.set_bone_correction_from_version(kfdata.version)
            # use pyffi toaster to scale the tree
            toaster = pyffi.spells.nif.NifToaster()
            toaster.scale = NifOp.props.scale_correction_import
            pyffi.spells.nif.fix.SpellScale(data=kfdata, toaster=toaster).recurse()
            # calculate and set frames per second
            self.animationhelper.set_frames_per_second( kfdata.roots )
            for kf_root in kfdata.roots:
                self.animationhelper.import_kf_standalone( kf_root, b_armature, bind_data )
        return {'FINISHED'}
    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 export_keyframes(self, parent_block, b_obj=None, bone=None):
        """
        If called on b_obj=None and bone=None it should save an empty controller.
        If called on an b_obj = type(armature), it expects a bone too.
        If called on an object, with bone=None, it exports object level animation.
        """

        # sometimes we need to export an empty keyframe...
        scale_curve = []
        quat_curve = []
        euler_curve = []
        trans_curve = []

        exp_fcurves = []

        #just for more detailed error reporting later on
        bonestr = ""

        #we have either skeletal or object animation
        if b_obj and b_obj.animation_data and b_obj.animation_data.action:
            action = b_obj.animation_data.action

            #skeletal animation - with bone correction & coordinate corrections
            if bone and bone.name in action.groups:
                # get bind matrix for bone or object
                bind_matrix = self.nif_export.objecthelper.get_object_bind(
                    bone)
                exp_fcurves = action.groups[bone.name].channels
                #just for more detailed error reporting later on
                bonestr = " in bone " + bone.name
            #object level animation - no coordinate corrections
            elif not bone:
                # raise error on any objects parented to bones
                if b_obj.parent and b_obj.parent_type == "BONE":
                    raise nif_utils.NifError(
                        b_obj.name +
                        " is parented to a bone AND has animations. The nif format does not support this!"
                    )

                # we have either a root object (Scene Root), in which case we take the coordinates without modification
                # or a generic object parented to an empty = node
                # objects may have an offset from their parent that is not apparent in the user input (ie. UI values and keyframes)
                # we want to export matrix_local, and the keyframes are in matrix_basis, so do:
                # matrix_local = matrix_parent_inverse * matrix_basis
                bind_matrix = b_obj.matrix_parent_inverse
                exp_fcurves = [
                    fcu for fcu in action.fcurves
                    if fcu.data_path in ("rotation_quaternion",
                                         "rotation_euler", "location", "scale")
                ]
            # decompose the bind matrix
            if exp_fcurves:
                bind_scale, bind_rot, bind_trans = nif_utils.decompose_srt(
                    bind_matrix)
                bind_rot = bind_rot.to_4x4()
            start_frame, stop_frame = action.frame_range

        # we are supposed to export an empty controller
        else:
            # only set frame range
            start_frame = bpy.context.scene.frame_start
            stop_frame = bpy.context.scene.frame_end

        if NifOp.props.animation == 'GEOM_NIF' and self.nif_export.version < 0x0A020000:
            # keyframe controllers are not present in geometry only files
            # for more recent versions, the controller and interpolators are
            # present, only the data is not present (see further on)
            return

        # add a keyframecontroller block, and refer to this block in the
        # parent's time controller
        if self.nif_export.version < 0x0A020000:
            kfc = self.nif_export.objecthelper.create_block(
                "NiKeyframeController", exp_fcurves)
        else:
            kfc = self.nif_export.objecthelper.create_block(
                "NiTransformController", exp_fcurves)
            kfi = self.nif_export.objecthelper.create_block(
                "NiTransformInterpolator", exp_fcurves)
            # link interpolator from the controller
            kfc.interpolator = kfi
            # set interpolator default data
            kfi.scale, kfi.rotation, kfi.translation = \
                parent_block.get_transform().get_scale_quat_translation()

        #if parent is a node, attach controller to that node
        if isinstance(parent_block, NifFormat.NiNode):
            parent_block.add_controller(kfc)
        #else ControllerSequence, so create a link
        elif isinstance(parent_block, NifFormat.NiControllerSequence):
            controlledblock = parent_block.add_controlled_block()
            if self.nif_export.version < 0x0A020000:
                # older versions need the actual controller blocks
                controlledblock.target_name = armature.get_bone_name_for_nif(
                    bone.name)
                controlledblock.controller = kfc
                # erase reference to target node
                kfc.target = None
            else:
                # newer versions need the interpolator blocks
                controlledblock.interpolator = kfi
        else:
            raise nif_utils.NifError("Unsupported KeyframeController parent!")

        # fill in the non-trivial values
        self.set_flags_and_timing(kfc, exp_fcurves, start_frame, stop_frame)

        if NifOp.props.animation == 'GEOM_NIF':
            # keyframe data is not present in geometry files
            return

        # get the desired fcurves for each data type from exp_fcurves
        quaternions = [
            fcu for fcu in exp_fcurves if fcu.data_path.endswith("quaternion")
        ]
        translations = [
            fcu for fcu in exp_fcurves if fcu.data_path.endswith("location")
        ]
        eulers = [
            fcu for fcu in exp_fcurves if fcu.data_path.endswith("euler")
        ]
        scales = [
            fcu for fcu in exp_fcurves if fcu.data_path.endswith("scale")
        ]

        # go over all fcurves collected above and transform and store all keys
        if scales:
            #just use the first scale curve and assume even scale over all curves
            for frame, scale in self.iter_frame_key(scales, mathutils.Vector):
                scale_curve.append((frame, scale[0]))

        if quaternions:
            if len(quaternions) != 4:
                raise nif_utils.NifError("Incomplete ROT key set" + bonestr +
                                         " for action " + action.name)
            else:
                for frame, quat in self.iter_frame_key(quaternions,
                                                       mathutils.Quaternion):
                    quat = armature.export_keymat(bind_rot,
                                                  quat.to_matrix().to_4x4(),
                                                  bone).to_quaternion()
                    quat_curve.append((frame, quat))

        if eulers:
            if len(eulers) != 3:
                raise nif_utils.NifError("Incomplete Euler key set" + bonestr +
                                         " for action " + action.name)
            else:
                for frame, euler in self.iter_frame_key(
                        eulers, mathutils.Euler):
                    euler = armature.export_keymat(bind_rot,
                                                   euler.to_matrix().to_4x4(),
                                                   bone).to_euler(
                                                       "XYZ", euler)
                    euler_curve.append((frame, euler))

        if translations:
            if len(translations) != 3:
                raise nif_utils.NifError("Incomplete LOC key set" + bonestr +
                                         " for action " + action.name)
            else:
                for frame, trans in self.iter_frame_key(
                        translations, mathutils.Vector):
                    trans = armature.export_keymat(
                        bind_rot, mathutils.Matrix.Translation(trans),
                        bone).to_translation() + bind_trans
                    trans_curve.append((frame, trans))

        # finally we can export the data calculated above
        if (max(len(quat_curve), len(euler_curve), len(trans_curve),
                len(scale_curve)) <= 1
                and self.nif_export.version >= 0x0A020000):
            # only add data if number of keys is > 1
            # (see importer comments with import_kf_root: a single frame
            # keyframe denotes an interpolator without further data)
            # insufficient keys, so set the data and we're done!
            if trans_curve:
                trans = trans_curve[0][1]
                kfi.translation.x = trans[0]
                kfi.translation.y = trans[1]
                kfi.translation.z = trans[2]
            if quat_curve:
                quat = quat_curve[0][1]
                kfi.rotation.x = quat.x
                kfi.rotation.y = quat.y
                kfi.rotation.z = quat.z
                kfi.rotation.w = quat.w
            elif euler_curve:
                quat = euler_curve[0][1].to_quaternion()
                kfi.rotation.x = quat.x
                kfi.rotation.y = quat.y
                kfi.rotation.z = quat.z
                kfi.rotation.w = quat.w
            # ignore scale for now...
            kfi.scale = 1.0
            # done!
            return

        # add the keyframe data
        if self.nif_export.version < 0x0A020000:
            kfd = self.nif_export.objecthelper.create_block(
                "NiKeyframeData", exp_fcurves)
            kfc.data = kfd
        else:
            # number of frames is > 1, so add transform data
            kfd = self.nif_export.objecthelper.create_block(
                "NiTransformData", exp_fcurves)
            kfi.data = kfd

        ### TODO [animation] support other interpolation modes, get interpolation from blender?
        ###                  probably requires additional data like tangents and stuff

        # save all nif keys
        if euler_curve:
            kfd.rotation_type = NifFormat.KeyType.XYZ_ROTATION_KEY
            kfd.num_rotation_keys = 1  # *NOT* len(frames) this crashes the engine!
            for i, coord in enumerate(kfd.xyz_rotations):
                coord.num_keys = len(euler_curve)
                coord.interpolation = NifFormat.KeyType.LINEAR_KEY
                coord.keys.update_size()
                for key, (frame, euler) in zip(coord.keys, euler_curve):
                    key.time = frame / self.fps
                    key.value = euler[i]
        elif quat_curve:
            kfd.rotation_type = NifFormat.KeyType.LINEAR_KEY
            kfd.num_rotation_keys = len(quat_curve)
            kfd.quaternion_keys.update_size()
            for key, (frame, quat) in zip(kfd.quaternion_keys, quat_curve):
                key.time = frame / self.fps
                key.value.w = quat.w
                key.value.x = quat.x
                key.value.y = quat.y
                key.value.z = quat.z

        kfd.translations.interpolation = NifFormat.KeyType.LINEAR_KEY
        kfd.translations.num_keys = len(trans_curve)
        kfd.translations.keys.update_size()
        for key, (frame, trans) in zip(kfd.translations.keys, trans_curve):
            key.time = frame / self.fps
            key.value.x, key.value.y, key.value.z = trans

        kfd.scales.interpolation = NifFormat.KeyType.LINEAR_KEY
        kfd.scales.num_keys = len(scale_curve)
        kfd.scales.keys.update_size()
        for key, (frame, scale) in zip(kfd.scales.keys, scale_curve):
            key.time = frame / self.fps
            key.value = scale
Ejemplo n.º 7
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)
Ejemplo n.º 8
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'}
Ejemplo n.º 9
0
    def export_texture_filename(self, texture):
        """Returns file name from texture.

        @param texture: The texture object in blender.
        @return: The file name of the image used in the texture.
        """
        if texture.type == 'ENVIRONMENT_MAP':
            # this works for morrowind only
            if NifOp.props.game != 'MORROWIND':
                raise nif_utils.NifError(
                    "cannot export environment maps for nif version '%s'" %
                    NifOp.props.game)
            return "enviro 01.TGA"

        elif texture.type == 'IMAGE':
            # get filename from image

            # XXX still needed? can texture.image be None in current blender?
            # check that image is loaded
            if texture.image is None:
                raise nif_utils.NifError(
                    "image type texture has no file loaded ('%s')" %
                    texture.name)

            filename = texture.image.filepath

            # warn if packed flag is enabled
            if texture.image.packed_file:
                NifLog.warn(
                    "Packed image in texture '{0}' ignored, exporting as '{1}' instead."
                    .format(texture.name, filename))

            # try and find a DDS alternative, force it if required
            ddsfilename = "%s%s" % (filename[:-4], '.dds')
            if os.path.exists(ddsfilename) or NifOp.props.force_dds:
                filename = ddsfilename

            # sanitize file path
            if not NifOp.props.game in ('MORROWIND', 'OBLIVION', 'FALLOUT_3',
                                        'SKYRIM'):
                # strip texture file path
                filename = os.path.basename(filename)

            else:
                # strip the data files prefix from the texture's file name
                filename = filename.lower()
                idx = filename.find("textures")
                if (idx >= 0):
                    filename = filename[idx:]
                else:
                    NifLog.warn(
                        "{0} does not reside in a 'Textures' folder; texture path will be stripped  and textures may not display in-game"
                        .format(filename))
                    filename = os.path.basename(filename)
            # for linux export: fix path seperators
            return filename.replace('/', '\\')
        else:
            # texture must be of type IMAGE or ENVMAP
            raise nif_utils.NifError(
                "Error: Texture '%s' must be of type IMAGE or ENVMAP" %
                texture.name)
Ejemplo n.º 10
0
    def export_bs_shader_property(self, b_obj=None, b_mat=None):
        """Export a Bethesda shader property block."""
        self.determine_texture_types(b_obj, b_mat)

        # create new block
        if b_obj.niftools_shader.bs_shadertype == 'BSShaderPPLightingProperty':
            bsshader = NifFormat.BSShaderPPLightingProperty()
            # set shader options
            # TODO: FIXME:
            b_s_type = NifFormat.BSShaderType._enumkeys.index(b_obj.niftools_shader.bsspplp_shaderobjtype)
            bsshader.shader_type = NifFormat.BSShaderType._enumvalues[b_s_type]

            # Shader Flags
            if hasattr(bsshader, 'shader_flags'):
                self.export_shader_flags(b_obj, bsshader)

        if b_obj.niftools_shader.bs_shadertype == 'BSLightingShaderProperty':
            bsshader = NifFormat.BSLightingShaderProperty()
            b_s_type = NifFormat.BSLightingShaderPropertyShaderType._enumkeys.index(b_obj.niftools_shader.bslsp_shaderobjtype)
            bsshader.shader_type = NifFormat.BSLightingShaderPropertyShaderType._enumvalues[b_s_type]

            # UV Offset
            if hasattr(bsshader, 'uv_offset'):
                self.export_uv_offset(bsshader)

            # UV Scale
            if hasattr(bsshader, 'uv_scale'):
                self.export_uv_scale(bsshader)

            # Texture Clamping mode
            if not self.base_mtex.texture.image.use_clamp_x:
                wrap_s = 2
            else:
                wrap_s = 0
            if not self.base_mtex.texture.image.use_clamp_y:
                wrap_t = 1
            else:
                wrap_t = 0
            bsshader.texture_clamp_mode = (wrap_s + wrap_t)

            # Diffuse color
            bsshader.skin_tint_color.r = b_mat.diffuse_color.r
            bsshader.skin_tint_color.g = b_mat.diffuse_color.g
            bsshader.skin_tint_color.b = b_mat.diffuse_color.b
            # b_mat.diffuse_intensity = 1.0

            bsshader.lighting_effect_1 = b_mat.niftools.lightingeffect1
            bsshader.lighting_effect_2 = b_mat.niftools.lightingeffect2

            # Emissive
            bsshader.emissive_color.r = b_mat.niftools.emissive_color.r
            bsshader.emissive_color.g = b_mat.niftools.emissive_color.g
            bsshader.emissive_color.b = b_mat.niftools.emissive_color.b
            bsshader.emissive_multiple = b_mat.emit

            # gloss
            bsshader.glossiness = b_mat.specular_hardness

            # Specular color
            bsshader.specular_color.r = b_mat.specular_color.r
            bsshader.specular_color.g = b_mat.specular_color.g
            bsshader.specular_color.b = b_mat.specular_color.b
            bsshader.specular_strength = b_mat.specular_intensity

            # Alpha
            if b_mat.use_transparency:
                bsshader.alpha = (1 - b_mat.alpha)

            # Shader Flags
            if hasattr(bsshader, 'shader_flags_1'):
                self.export_shader_flags(b_obj, bsshader)

        if b_obj.niftools_shader.bs_shadertype == 'BSEffectShaderProperty':
            bsshader = NifFormat.BSEffectShaderProperty()

            # Alpha
            if b_mat.use_transparency:
                bsshader.alpha = (1 - b_mat.alpha)

            # clamp Mode
            bsshader.texture_clamp_mode = 65283

            # Emissive
            bsshader.emissive_color.r = b_mat.niftools.emissive_color.r
            bsshader.emissive_color.g = b_mat.niftools.emissive_color.g
            bsshader.emissive_color.b = b_mat.niftools.emissive_color.b
            bsshader.emissive_color.a = b_mat.niftools.emissive_alpha
            bsshader.emissive_multiple = b_mat.emit

            # Shader Flags
            if hasattr(bsshader, 'shader_flags_1'):
                self.export_shader_flags(b_obj, bsshader)

        if b_obj.niftools_shader.bs_shadertype == 'None':
            raise nif_utils.NifError("Export version expected shader. "
                                     "No shader applied to mesh '%s', these cannot be exported to NIF."
                                     " Set shader before exporting." % b_obj)
        # set textures
        texset = NifFormat.BSShaderTextureSet()
        bsshader.texture_set = texset
        if self.base_mtex:
            texset.textures[0] = self.texture_writer.export_texture_filename(self.base_mtex.texture)
        if self.normal_mtex:
            texset.textures[1] = self.texture_writer.export_texture_filename(self.normal_mtex.texture)
        if self.glow_mtex:
            texset.textures[2] = self.texture_writer.export_texture_filename(self.glow_mtex.texture)
        if self.detail_mtex:
            texset.textures[3] = self.texture_writer.export_texture_filename(self.detail_mtex.texture)

        if b_obj.niftools_shader.bs_shadertype == 'BSLightingShaderProperty':
            texset.num_textures = 9
            texset.textures.update_size()
            if self.detail_mtex:
                texset.textures[6] = self.texture_writer.export_texture_filename(self.detail_mtex.texture)
            if self.gloss_mtex:
                texset.textures[7] = self.texture_writer.export_texture_filename(self.gloss_mtex.texture)

        if b_obj.niftools_shader.bs_shadertype == 'BSEffectShaderProperty':
            bsshader.source_texture = self.texture_writer.export_texture_filename(self.base_mtex.texture)
            bsshader.greyscale_texture = self.texture_writer.export_texture_filename(self.glow_mtex.texture)

        return bsshader
Ejemplo n.º 11
0
    def determine_texture_types(self, b_obj, b_mat):

        used_slots = self.get_used_textslots(b_mat)
        self.base_mtex = None
        self.bump_mtex = None
        self.dark_mtex = None
        self.detail_mtex = None
        self.gloss_mtex = None
        self.glow_mtex = None
        self.normal_mtex = None
        self.ref_mtex = None

        for b_mat_texslot in used_slots:
            # check REFL-mapped textures
            # (used for "NiTextureEffect" materials)
            if b_mat_texslot.texture_coords == 'REFLECTION':
                if not b_mat_texslot.use_map_color_diffuse:
                    # it should map to colour
                    raise nif_utils.NifError("Non-COL-mapped reflection texture in mesh '%s', material '%s', these cannot be exported to NIF.\n"
                                             "Either delete all non-COL-mapped reflection textures, or in the Shading Panel, under Material Buttons, set texture 'Map To' to 'COL'." % (b_obj.name, b_mat.name))
                if b_mat_texslot.blend_type != 'ADD':
                    # it should have "ADD" blending mode
                    NifLog.warn("Reflection texture should have blending mode 'Add' on texture in mesh '{0}', material '{1}').".format(b_obj.name, b_mat.name))
                # an envmap image should have an empty... don't care
                self.ref_mtex = b_mat_texslot

            # check UV-mapped textures
            elif b_mat_texslot.texture_coords == 'UV':

                # update set of uv layers that must be exported
                if not b_mat_texslot.uv_layer in self.nif_export.dict_mesh_uvlayers:
                    self.nif_export.dict_mesh_uvlayers.append(b_mat_texslot.uv_layer)

                # glow tex
                if b_mat_texslot.use_map_emit:
                    # multi-check
                    if self.glow_mtex:
                        raise nif_utils.NifError("Multiple emissive textures in mesh '%s', material '%s'.\n"
                                                 " Make sure there is only one texture set as Influence > emit" % (b_obj.name, b_mat.name))

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True

                    self.glow_mtex = b_mat_texslot

                # specular
                elif b_mat_texslot.use_map_specular or b_mat_texslot.use_map_color_spec:
                    # multi-check
                    if self.gloss_mtex:
                        raise nif_utils.NifError("Multiple specular gloss textures in mesh '%s', material '%s'.\n"
                                                 "Make sure there is only one texture set as Influence > specular" % (b_obj.name, b_mat.name))

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True

                    # got the gloss map
                    self.gloss_mtex = b_mat_texslot

                # bump map
                elif b_mat_texslot.use_map_normal and \
                        b_mat_texslot.texture.use_normal_map == False:
                    # multi-check
                    if self.bump_mtex:
                        raise nif_utils.NifError("Multiple bump/normal texture in mesh '%s', material '%s'.\n"
                                                 "Make sure there is only one texture set as Influence > normal" % (b_obj.name, b_mat.name))

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True

                    self.bump_mtex = b_mat_texslot

                # normal map
                elif b_mat_texslot.use_map_normal and b_mat_texslot.texture.use_normal_map:
                    # multi-check
                    if self.normal_mtex:
                        raise nif_utils.NifError("Multiple bump/normal textures in mesh '%s', material '%s'."
                                                 " Make sure there is only one texture set as Influence > normal" % (b_obj.name, b_mat.name))
                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True
                    self.normal_mtex = b_mat_texslot

                # darken
                elif b_mat_texslot.use_map_color_diffuse and b_mat_texslot.blend_type == 'DARKEN':

                    if self.dark_mtex:
                        raise nif_utils.NifError("Multiple Darken textures in mesh '%s', material '%s'."
                                                 " Make sure there is only one texture with Influence > Blend Type > Dark" % (b_obj.name, b_mat.name))

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True
                    # got the dark map
                    self.dark_mtex = b_mat_texslot

                # diffuse
                elif b_mat_texslot.use_map_color_diffuse:
                    if self.base_mtex:
                        raise nif_utils.NifError("Multiple Diffuse textures in mesh '%s', material '%s'.\n"
                                                 "Make sure there is only one texture with Influence > Diffuse > color" % (b_obj.name, b_mat.name))

                    self.base_mtex = b_mat_texslot

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True

                        '''
                        # in this case, Blender replaces the texture transparant parts with the underlying material color...
                        # in NIF, material alpha is multiplied with texture alpha channel...
                        # how can we emulate the NIF alpha system (simply multiplying material alpha with texture alpha) when MapTo.ALPHA is turned on?
                        # require the Blender material alpha to be 0.0 (no material color can show up), and use the "Var" slider in the texture blending mode tab!
                        # but...
    
                        if mesh_mat_transparency > NifOp.props.epsilon:
                            raise nif_utils.NifError(
                                "Cannot export this type of"
                                " transparency in material '%s': "
                                " instead, try to set alpha to 0.0"
                                " and to use the 'Var' slider"
                                " in the 'Map To' tab under the"
                                " material buttons."
                                %b_mat.name)
                        if (b_mat.animation_data and b_mat.animation_data.action.fcurves['Alpha']):
                            raise nif_utils.NifError(
                                "Cannot export animation for"
                                " this type of transparency"
                                " in material '%s':"
                                " remove alpha animation,"
                                " or turn off MapTo.ALPHA,"
                                " and try again."
                                %b_mat.name)
    
                        mesh_mat_transparency = b_mat_texslot.varfac # we must use the "Var" value
                        '''

                # detail
                elif b_mat_texslot.use_map_color_diffuse:
                    if self.detail_mtex:
                        raise nif_utils.NifError("Multiple detail textures in mesh '%s', material '%s'.\n" 
                                                 " Make sure there is only one texture with Influence Diffuse > color" % (b_obj.name, b_mat.name))
                    # extra diffuse consider as detail texture

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True
                    self.detail_mtex = b_mat_texslot

                # reflection
                elif b_mat_texslot.use_map_mirror or b_mat_texslot.use_map_raymir:
                    # multi-check
                    if self.glow_mtex:
                        raise nif_utils.NifError("Multiple reflection textures in mesh '%s', material '%s'.\n"
                                                 "Make sure there is only one texture set as Influence > Mirror/Ray Mirror" % (b_obj.name, b_mat.name))
                    # got the reflection map
                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True
                    self.ref_mtex = b_mat_texslot

                # unsupported map
                else:
                    raise nif_utils.NifError("Do not know how to export texture '%s', in mesh '%s', material '%s'.\n"
                                             "Either delete it, or if this texture is to be your base texture.\n"
                                             "Go to the Shading Panel Material Buttons, and set texture 'Map To' to 'COL'."
                        % (b_mat_texslot.texture.name, b_obj.name, b_mat.name))

            # nif only support UV-mapped textures
            else:
                NifLog.warn("Non-UV texture in mesh '{0}', material '{1}'.\nEither delete all non-UV textures or "
                            "create a UV map for every texture associated with selected object and run the script again.".format(b_obj.name, b_mat.name))
Ejemplo n.º 12
0
    def export_collision_object(self, b_obj, layer, n_havok_mat):
        """Export object obj as box, sphere, capsule, or convex hull.
        Note: polyheder is handled by export_collision_packed."""

        # find bounding box data
        if not b_obj.data.vertices:
            NifLog.warn("Skipping collision object {0} without vertices.".format(b_obj))
            return None
        b_vertlist = [vert.co for vert in b_obj.data.vertices]

        minx = min([b_vert[0] for b_vert in b_vertlist])
        miny = min([b_vert[1] for b_vert in b_vertlist])
        minz = min([b_vert[2] for b_vert in b_vertlist])
        maxx = max([b_vert[0] for b_vert in b_vertlist])
        maxy = max([b_vert[1] for b_vert in b_vertlist])
        maxz = max([b_vert[2] for b_vert in b_vertlist])
        
        calc_bhkshape_radius = (maxx - minx + maxy - miny + maxz - minz) / (6.0 * self.HAVOK_SCALE)
        if(b_obj.game.radius - calc_bhkshape_radius > NifOp.props.epsilon):
            radius = calc_bhkshape_radius
        else:
            radius = b_obj.game.radius
        
        if b_obj.game.collision_bounds_type in {'BOX', 'SPHERE'}:
            # note: collision settings are taken from lowerclasschair01.nif
            coltf = self.nif_export.objecthelper.create_block("bhkConvexTransformShape", b_obj)
            coltf.material = n_havok_mat
            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
            hktf = mathutils.Matrix(
                self.nif_export.objecthelper.get_object_matrix(b_obj).as_list())
            # the translation part must point to the center of the data
            # so calculate the center in local coordinates
            center = mathutils.Vector(((minx + maxx) / 2.0, (miny + maxy) / 2.0, (minz + maxz) / 2.0))
            # and transform it to global coordinates
            center = center * hktf
            hktf[0][3] = center[0]
            hktf[1][3] = center[1]
            hktf[2][3] = center[2]
            # we need to store the transpose of the matrix
            hktf.transpose()
            coltf.transform.set_rows(*hktf)
            # fix matrix for havok coordinate system
            coltf.transform.m_41 /= self.HAVOK_SCALE
            coltf.transform.m_42 /= self.HAVOK_SCALE
            coltf.transform.m_43 /= self.HAVOK_SCALE

            if b_obj.game.collision_bounds_type == 'BOX':
                colbox = self.nif_export.objecthelper.create_block("bhkBoxShape", b_obj)
                coltf.shape = colbox
                colbox.material = n_havok_mat
                colbox.radius = radius
                colbox.unknown_8_bytes[0] = 0x6b
                colbox.unknown_8_bytes[1] = 0xee
                colbox.unknown_8_bytes[2] = 0x43
                colbox.unknown_8_bytes[3] = 0x40
                colbox.unknown_8_bytes[4] = 0x3a
                colbox.unknown_8_bytes[5] = 0xef
                colbox.unknown_8_bytes[6] = 0x8e
                colbox.unknown_8_bytes[7] = 0x3e
                # fix dimensions for havok coordinate system
                colbox.dimensions.x = (maxx - minx) / (2.0 * self.HAVOK_SCALE)
                colbox.dimensions.y = (maxy - miny) / (2.0 * self.HAVOK_SCALE)
                colbox.dimensions.z = (maxz - minz) / (2.0 * self.HAVOK_SCALE)
                colbox.minimum_size = min(colbox.dimensions.x, colbox.dimensions.y, colbox.dimensions.z)

            elif b_obj.game.collision_bounds_type == 'SPHERE':
                colsphere = self.nif_export.objecthelper.create_block("bhkSphereShape", b_obj)
                coltf.shape = colsphere
                colsphere.material = n_havok_mat
                # take average radius and
                # Todo find out what this is: fix for havok coordinate system (6 * 7 = 42)
                colsphere.radius = radius

            return coltf

        elif b_obj.game.collision_bounds_type in {'CYLINDER', 'CAPSULE'}:
            # take average radius and calculate end points
            localradius = (maxx + maxy - minx - miny) / 4.0
            transform = b_obj.matrix_local.transposed()
            vert1 = mathutils.Vector( [ (maxx + minx)/2.0,
                                       (maxy + miny)/2.0,
                                       maxz - localradius ] )
            vert2 = mathutils.Vector( [ (maxx + minx) / 2.0,
                                       (maxy + miny) / 2.0,
                                       minz + localradius ] )
            vert1 = vert1 * transform
            vert2 = vert2 * transform

            # check if end points are far enough from each other
            if (vert1 - vert2).length < NifOp.props.epsilon:
                NifLog.warn("End points of cylinder {0} too close, converting to sphere.".format(b_obj))
                # change type
                b_obj.game.collision_bounds_type = 'SPHERE'
                # instead of duplicating code, just run the function again
                return self.export_collision_object(b_obj, layer, n_havok_mat)

            # end points are ok, so export as capsule
            colcaps = self.nif_export.objecthelper.create_block("bhkCapsuleShape", b_obj)
            colcaps.material = n_havok_mat
            colcaps.first_point.x = vert1[0] / self.HAVOK_SCALE
            colcaps.first_point.y = vert1[1] / self.HAVOK_SCALE
            colcaps.first_point.z = vert1[2] / self.HAVOK_SCALE
            colcaps.second_point.x = vert2[0] / self.HAVOK_SCALE
            colcaps.second_point.y = vert2[1] / self.HAVOK_SCALE
            colcaps.second_point.z = vert2[2] / self.HAVOK_SCALE

            # set radius, with correct scale
            size_x = b_obj.scale.x
            size_y = b_obj.scale.y
            size_z = b_obj.scale.z

            colcaps.radius = localradius * (size_x + size_y) * 0.5
            colcaps.radius_1 = colcaps.radius
            colcaps.radius_2 = colcaps.radius

            # fix havok coordinate system for radii
            colcaps.radius /= self.HAVOK_SCALE
            colcaps.radius_1 /= self.HAVOK_SCALE
            colcaps.radius_2 /= self.HAVOK_SCALE
            return colcaps

        elif b_obj.game.collision_bounds_type == 'CONVEX_HULL':
            b_mesh = b_obj.data
            b_transform_mat = mathutils.Matrix(self.nif_export.objecthelper.get_object_matrix(b_obj).as_list())

            b_rot_quat = b_transform_mat.decompose()[1]
            b_scale_vec = b_transform_mat.decompose()[0]
            '''
            scale = math.avg(b_scale_vec.to_tuple())
            if scale < 0:
                scale = - (-scale) ** (1.0 / 3)
            else:
                scale = scale ** (1.0 / 3)
            rotation /= scale
            '''

            # calculate vertices, normals, and distances
            vertlist = [b_transform_mat * vert.co for vert in b_mesh.vertices]
            fnormlist = [b_rot_quat * b_face.normal for b_face in b_mesh.polygons]
            fdistlist = [(b_transform_mat * (-1 * b_mesh.vertices[b_mesh.polygons[b_face.index].vertices[0]].co)).dot(
                            b_rot_quat.to_matrix() * b_face.normal)
                         for b_face in b_mesh.polygons ]

            # remove duplicates through dictionary
            vertdict = {}
            for i, vert in enumerate(vertlist):
                vertdict[(int(vert[0]*self.nif_export.VERTEX_RESOLUTION),
                          int(vert[1]*self.nif_export.VERTEX_RESOLUTION),
                          int(vert[2]*self.nif_export.VERTEX_RESOLUTION))] = i
            fdict = {}
            for i, (norm, dist) in enumerate(zip(fnormlist, fdistlist)):
                fdict[(int(norm[0]*self.nif_export.NORMAL_RESOLUTION),
                       int(norm[1]*self.nif_export.NORMAL_RESOLUTION),
                       int(norm[2]*self.nif_export.NORMAL_RESOLUTION),
                       int(dist*self.nif_export.VERTEX_RESOLUTION))] = i
            # sort vertices and normals
            vertkeys = sorted(vertdict.keys())
            fkeys = sorted(fdict.keys())
            vertlist = [ vertlist[vertdict[hsh]] for hsh in vertkeys ]
            fnormlist = [ fnormlist[fdict[hsh]] for hsh in fkeys ]
            fdistlist = [ fdistlist[fdict[hsh]] for hsh in fkeys ]

            if len(fnormlist) > 65535 or len(vertlist) > 65535:
                raise nif_utils.NifError(
                    "ERROR%t|Too many polygons/vertices."
                    " Decimate/split your b_mesh and try again.")

            colhull = self.nif_export.objecthelper.create_block("bhkConvexVerticesShape", b_obj)
            colhull.material = n_havok_mat
            colhull.radius = radius
            colhull.unknown_6_floats[2] = -0.0 # enables arrow detection
            colhull.unknown_6_floats[5] = -0.0 # enables arrow detection
            # note: unknown 6 floats are usually all 0
            colhull.num_vertices = len(vertlist)
            colhull.vertices.update_size()
            for vhull, vert in zip(colhull.vertices, vertlist):
                vhull.x = vert[0] / self.HAVOK_SCALE
                vhull.y = vert[1] / self.HAVOK_SCALE
                vhull.z = vert[2] / self.HAVOK_SCALE
                # w component is 0
            colhull.num_normals = len(fnormlist)
            colhull.normals.update_size()
            for nhull, norm, dist in zip(colhull.normals, fnormlist, fdistlist):
                nhull.x = norm[0]
                nhull.y = norm[1]
                nhull.z = norm[2]
                nhull.w = dist / self.HAVOK_SCALE

            return colhull

        else:
            raise nif_utils.NifError(
                'cannot export collision type %s to collision shape list'
                % b_obj.game.collision_bounds_type)
Ejemplo n.º 13
0
    def export_constraints(self, b_obj, root_block):
        """Export the constraints of an object.

        @param b_obj: The object whose constraints to export.
        @param root_block: The root of the nif tree (required for update_a_b)."""
        if isinstance(b_obj, bpy.types.Bone):
            # bone object has its constraints stored in the posebone
            # so now we should get the posebone, but no constraints for
            # bones are exported anyway for now
            # so skip this object
            return

        if not hasattr(b_obj, "constraints"):
            # skip text buffers etc
            return

        for b_constr in b_obj.constraints:
            # rigid body joints
            if b_constr.type == 'RIGID_BODY_JOINT':
                if NifOp.props.game not in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'):
                    NifLog.warn("Only Oblivion/Fallout/Skyrim rigid body constraints currently supported: Skipping {0}.".format(b_constr))
                    continue
                # check that the object is a rigid body
                for otherbody, otherobj in self.nif_export.dict_blocks.items():
                    if isinstance(otherbody, NifFormat.bhkRigidBody) \
                        and otherobj is b_obj:
                        hkbody = otherbody
                        break
                else:
                    # no collision body for this object
                    raise nif_utils.NifError(
                        "Object %s has a rigid body constraint,"
                        " but is not exported as collision object"
                        % b_obj.name)
                # yes there is a rigid body constraint
                # is it of a type that is supported?
                if b_constr.pivot_type == 'CONE_TWIST':
                    # ball
                    if b_obj.rigid_body.enabled == True:
                        hkconstraint = self.nif_export.objecthelper.create_block(
                            "bhkRagdollConstraint", b_constr)
                    else:
                        hkconstraint = self.nif_export.objecthelper.create_block(
                            "bhkMalleableConstraint", b_constr)
                        hkconstraint.type = 7
                    hkdescriptor = hkconstraint.ragdoll
                elif b_constr.pivot_type == 'HINGE':
                    # hinge
                    if b_obj.rigid_body.enabled == True:
                        hkconstraint = self.nif_export.objecthelper.create_block(
                            "bhkLimitedHingeConstraint", b_constr)
                    else:
                        hkconstraint = self.nif_export.objecthelper.create_block(
                            "bhkMalleableConstraint", b_constr)
                        hkconstraint.type = 2
                    hkdescriptor = hkconstraint.limited_hinge
                else:
                    raise nif_utils.NifError(
                        "Unsupported rigid body joint type (%i),"
                        " only ball and hinge are supported."
                        % b_constr.type)

                # defaults and getting object properties for user
                # settings (should use constraint properties, but
                # blender does not have those...)
                if b_constr.limit_angle_max_x != 0:
                    max_angle = b_constr.limit_angle_max_x
                else:
                    max_angle = 1.5
                if b_constr.limit_angle_min_x != 0:
                    min_angle = b_constr.limit_angle_min_x
                else:
                    min_angle = 0.0
                # friction: again, just picking a reasonable value if
                # no real value given
                if b_obj.niftools_constraint.LHMaxFriction != 0:
                    max_friction = b_obj.niftools_constraint.LHMaxFriction
                    
                
                else:
                    if isinstance(hkconstraint,
                                  NifFormat.bhkMalleableConstraint):
                        # malleable typically have 0
                        # (perhaps because they have a damping parameter)
                        max_friction = 0
                    else:
                        # non-malleable typically have 10
                        if NifOp.props.game == 'FALLOUT_3':
                            max_friction = 100
                        else: # oblivion
                            max_friction = 10

                # parent constraint to hkbody
                hkbody.num_constraints += 1
                hkbody.constraints.update_size()
                hkbody.constraints[-1] = hkconstraint

                # export hkconstraint settings
                hkconstraint.num_entities = 2
                hkconstraint.entities.update_size()
                hkconstraint.entities[0] = hkbody
                # is there a target?
                targetobj = b_constr.target
                if not targetobj:
                    NifLog.warn("Constraint {0} has no target, skipped".format(b_constr))
                    continue
                # find target's bhkRigidBody
                for otherbody, otherobj in self.nif_export.dict_blocks.items():
                    if isinstance(otherbody, NifFormat.bhkRigidBody) \
                        and otherobj == targetobj:
                        hkconstraint.entities[1] = otherbody
                        break
                else:
                    # not found
                    raise nif_utils.NifError(
                        "Rigid body target not exported in nif tree"
                        " check that %s is selected during export." % targetobj)
                # priority
                hkconstraint.priority = 1
                # extra malleable constraint settings
                if isinstance(hkconstraint, NifFormat.bhkMalleableConstraint):
                    # unknowns
                    hkconstraint.unknown_int_2 = 2
                    hkconstraint.unknown_int_3 = 1
                    # force required to keep bodies together
                    hkconstraint.tau = b_obj.niftools_constraint.tau
                    hkconstraint.damping = b_obj.niftools_constraint.damping

                # calculate pivot point and constraint matrix
                pivot = mathutils.Vector([
                    b_constr.pivot_x,
                    b_constr.pivot_y,
                    b_constr.pivot_z,
                    ])
                constr_matrix = mathutils.Euler((
                    b_constr.axis_x,
                    b_constr.axis_y,
                    b_constr.axis_z))
                constr_matrix = constr_matrix.to_matrix()

                # transform pivot point and constraint matrix into bhkRigidBody
                # coordinates (also see import_nif.py, the
                # NifImport.import_bhk_constraints method)

                # the pivot point v' is in object coordinates
                # however nif expects it in hkbody 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' * O * T * B' * B^{-1} * R^{-1}

                # for the rotation matrix, we transform in the same way
                # but ignore all translation parts

                # assume R is unit transform...

                # apply object transform relative to the bone head
                # (this is O * T * B' * B^{-1} at once)
                transform = mathutils.Matrix(
                    b_obj.matrix_local)
                pivot = pivot * transform
                constr_matrix = constr_matrix * transform.to_3x3()

                # export hkdescriptor pivot point
                hkdescriptor.pivot_a.x = pivot[0] / self.HAVOK_SCALE
                hkdescriptor.pivot_a.y = pivot[1] / self.HAVOK_SCALE
                hkdescriptor.pivot_a.z = pivot[2] / self.HAVOK_SCALE
                # export hkdescriptor axes and other parameters
                # (also see import_nif.py NifImport.import_bhk_constraints)
                axis_x = mathutils.Vector([1,0,0]) * constr_matrix
                axis_y = mathutils.Vector([0,1,0]) * constr_matrix
                axis_z = mathutils.Vector([0,0,1]) * constr_matrix
                    
                if isinstance(hkdescriptor, NifFormat.RagdollDescriptor):
                    # z axis is the twist vector
                    hkdescriptor.twist_a.x = axis_z[0]
                    hkdescriptor.twist_a.y = axis_z[1]
                    hkdescriptor.twist_a.z = axis_z[2]
                    # x axis is the plane vector
                    hkdescriptor.plane_a.x = axis_x[0]
                    hkdescriptor.plane_a.y = axis_x[1]
                    hkdescriptor.plane_a.z = axis_x[2]
                    # angle limits
                    # take them twist and plane to be 45 deg (3.14 / 4 = 0.8)

                    hkdescriptor.plane_min_angle = b_constr.limit_angle_min_x
                    hkdescriptor.plane_max_angle = b_constr.limit_angle_max_x

                    hkdescriptor.cone_max_angle = b_constr.limit_angle_max_y

                    hkdescriptor.twist_min_angle = b_constr.limit_angle_min_z
                    hkdescriptor.twist_max_angle = b_constr.limit_angle_max_z
                    
                    # same for maximum cone angle
                    hkdescriptor.max_friction = max_friction
                elif isinstance(hkdescriptor, NifFormat.LimitedHingeDescriptor):
                    # y axis is the zero angle vector on the plane of rotation
                    hkdescriptor.perp_2_axle_in_a_1.x = axis_y[0]
                    hkdescriptor.perp_2_axle_in_a_1.y = axis_y[1]
                    hkdescriptor.perp_2_axle_in_a_1.z = axis_y[2]
                    # x axis is the axis of rotation
                    hkdescriptor.axle_a.x = axis_x[0]
                    hkdescriptor.axle_a.y = axis_x[1]
                    hkdescriptor.axle_a.z = axis_x[2]
                    # z is the remaining axis determining the positive
                    # direction of rotation
                    hkdescriptor.perp_2_axle_in_a_2.x = axis_z[0]
                    hkdescriptor.perp_2_axle_in_a_2.y = axis_z[1]
                    hkdescriptor.perp_2_axle_in_a_2.z = axis_z[2]
                    # angle limits
                    # typically, the constraint on one side is defined
                    # by the z axis
                    hkdescriptor.min_angle = min_angle
                    # the maximum axis is typically about 90 degrees
                    # 3.14 / 2 = 1.5
                    hkdescriptor.max_angle = max_angle
                    # friction
                    hkdescriptor.max_friction = max_friction
                else:
                    raise ValueError("unknown descriptor %s"
                                     % hkdescriptor.__class__.__name__)

                # do AB
                hkconstraint.update_a_b(root_block)