def execute(self):
        """Main import function."""

        try:
            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")]
            # if an armature is present, prepare the bones for all actions
            b_armature = math.get_armature()
            if b_armature:
                # the axes used for bone correction depend on the armature in our scene
                math.set_bone_orientation(b_armature.data.niftools.axis_forward, b_armature.data.niftools.axis_up)
                # get nif space bind pose of armature here for all anims
                self.transform_anim.get_bind_data(b_armature)
            for kf_file in kf_files:
                kfdata = KFFile.load_kf(kf_file)

                self.apply_scale(kfdata, NifOp.props.scale_correction)

                # calculate and set frames per second
                self.transform_anim.set_frames_per_second(kfdata.roots)
                for kf_root in kfdata.roots:
                    self.transform_anim.import_kf_root(kf_root, b_armature)

        except NifError:
            return {'CANCELLED'}

        NifLog.info("Finished successfully")
        return {'FINISHED'}
    def import_armature(self, n_armature):
        """Scans an armature hierarchy, and returns a whole armature.
        This is done outside the normal node tree scan to allow for positioning
        of the bones before skins are attached."""

        armature_name = block_store.import_name(n_armature)
        b_armature_data = bpy.data.armatures.new(armature_name)
        b_armature_data.display_type = 'STICK'

        # use heuristics to determine a suitable orientation
        forward, up = self.guess_orientation(n_armature)
        # pass them to the matrix utility
        math.set_bone_orientation(forward, up)
        # store axis orientation for export
        b_armature_data.niftools.axis_forward = forward
        b_armature_data.niftools.axis_up = up
        b_armature_obj = Object.create_b_obj(n_armature, b_armature_data)
        b_armature_obj.show_in_front = True

        armature_space_bind_store, armature_space_pose_store = self.import_pose(
            n_armature)

        # make armature editable and create bones
        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
        for n_child in n_armature.children:
            self.import_bone_bind(n_child, armature_space_bind_store,
                                  b_armature_data, n_armature)
        self.fix_bone_lengths(b_armature_data)
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

        # The armature has been created in editmode,
        # now we are ready to set the bone keyframes and store the bones' long names.
        if NifOp.props.animation:
            self.transform_anim.create_action(b_armature_obj,
                                              armature_name + "-Anim")

        for bone_name, b_bone in b_armature_obj.data.bones.items():
            n_block = self.name_to_block[bone_name]
            # the property is only available from object mode!
            block_store.store_longname(b_bone, safe_decode(n_block.name))
            if NifOp.props.animation:
                self.transform_anim.import_transforms(n_block, b_armature_obj,
                                                      bone_name)

        # import pose
        for b_name, n_block in self.name_to_block.items():
            n_pose = armature_space_pose_store[n_block]
            b_pose_bone = b_armature_obj.pose.bones[b_name]
            n_bind = mathutils.Matrix(n_pose.as_list()).transposed()
            b_pose_bone.matrix = math.nif_bind_to_blender_bind(n_bind)
            # force update is required to ensure the transforms are set properly in blender
            bpy.context.view_layer.update()

        return b_armature_obj
Esempio n. 3
0
    def execute(self):
        """Main export function."""

        NifLog.info(f"Exporting {NifOp.props.filepath}")

        # extract directory, base name, extension
        directory = os.path.dirname(NifOp.props.filepath)
        filebase, fileext = os.path.splitext(
            os.path.basename(NifOp.props.filepath))

        if bpy.context.scene.niftools_scene.game == 'NONE':
            raise NifError(
                "You have not selected a game. Please select a game in the scene tab."
            )

        prefix = "x" if bpy.context.scene.niftools_scene.game in (
            'MORROWIND', ) else ""
        self.version, data = scene.get_version_data()
        # todo[anim] - change to KfData, but create_controller() [and maybe more] has to be updated first
        NifData.init(data)

        b_armature = math.get_armature()
        # some scenes may not have an armature, so nothing to do here
        if b_armature:
            math.set_bone_orientation(b_armature.data.niftools.axis_forward,
                                      b_armature.data.niftools.axis_up)

        NifLog.info("Creating keyframe tree")
        kf_root = self.transform_anim.export_kf_root(b_armature)

        # write kf (and xkf if asked)
        ext = ".kf"
        NifLog.info(f"Writing {prefix}{ext} file")

        data.roots = [kf_root]
        data.neosteam = (bpy.context.scene.niftools_scene.game == 'NEOSTEAM')

        # scale correction for the skeleton
        self.apply_scale(data, round(1 / NifOp.props.scale_correction))

        kffile = os.path.join(directory, prefix + filebase + ext)
        with open(kffile, "wb") as stream:
            data.write(stream)

        NifLog.info("Finished successfully")
        return {'FINISHED'}
Esempio n. 4
0
    def execute(self):
        """Main import function."""

        try:
            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 = math.get_armature()
            if not b_armature:
                raise NifError(
                    "No armature was found in scene, can not import KF animation!"
                )

            # the axes used for bone correction depend on the armature in our scene
            math.set_bone_orientation(b_armature.data.niftools.axis_forward,
                                      b_armature.data.niftools.axis_up)

            # 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)

                # use pyffi toaster to scale the tree
                toaster = pyffi.spells.nif.NifToaster()
                toaster.scale = NifOp.props.scale_correction
                pyffi.spells.nif.fix.SpellScale(data=kfdata,
                                                toaster=toaster).recurse()

                # calculate and set frames per second
                self.tranform_anim.set_frames_per_second(kfdata.roots)
                for kf_root in kfdata.roots:
                    self.tranform_anim.import_kf_root(kf_root, b_armature,
                                                      bind_data)

        except NifError:
            return {'CANCELLED'}

        NifLog.info("Finished successfully")
        return {'FINISHED'}
Esempio n. 5
0
    def execute(self):
        """Main export function."""
        if bpy.context.mode != 'OBJECT':
            bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

        NifLog.info(f"Exporting {NifOp.props.filepath}")

        # extract directory, base name, extension
        directory = os.path.dirname(NifOp.props.filepath)
        filebase, fileext = os.path.splitext(
            os.path.basename(NifOp.props.filepath))

        block_store.block_to_obj = {}  # clear out previous iteration

        try:  # catch export errors

            # protect against null nif versions
            if bpy.context.scene.niftools_scene.game == 'NONE':
                raise NifError(
                    "You have not selected a game. Please select a game and"
                    " nif version in the scene tab.")

            # find all objects that do not have a parent
            self.exportable_objects, self.root_objects = self.objecthelper.get_export_objects(
            )
            if not self.exportable_objects:
                NifLog.warn("No objects can be exported!")
                return {'FINISHED'}

            for b_obj in self.exportable_objects:
                if b_obj.type == 'MESH':
                    if b_obj.parent and b_obj.parent.type == 'ARMATURE':
                        for b_mod in b_obj.modifiers:
                            if b_mod.type == 'ARMATURE' and b_mod.use_bone_envelopes:
                                raise NifError(
                                    f"'{b_obj.name}': Cannot export envelope skinning. If you have vertex groups, turn off envelopes.\n"
                                    f"If you don't have vertex groups, select the bones one by one press W to "
                                    f"convert their envelopes to vertex weights, and turn off envelopes."
                                )

                # check for non-uniform transforms
                scale = b_obj.scale
                if abs(scale.x - scale.y) > NifOp.props.epsilon or abs(
                        scale.y - scale.z) > NifOp.props.epsilon:
                    NifLog.warn(
                        f"Non-uniform scaling not supported.\n"
                        f"Workaround: apply size and rotation (CTRL-A) on '{b_obj.name}'."
                    )

            b_armature = math.get_armature()
            # some scenes may not have an armature, so nothing to do here
            if b_armature:
                math.set_bone_orientation(
                    b_armature.data.niftools.axis_forward,
                    b_armature.data.niftools.axis_up)

            prefix = "x" if bpy.context.scene.niftools_scene.game in (
                'MORROWIND', ) else ""
            NifLog.info("Exporting")
            if NifOp.props.animation == 'ALL_NIF':
                NifLog.info("Exporting geometry and animation")
            elif NifOp.props.animation == 'GEOM_NIF':
                # for morrowind: everything except keyframe controllers
                NifLog.info("Exporting geometry only")

            # find nif version to write

            self.version, data = scene.get_version_data()
            NifData.init(data)

            # export the actual root node (the name is fixed later to avoid confusing the exporter with duplicate names)
            root_block = self.objecthelper.export_root_node(
                self.root_objects, filebase)

            # post-processing:
            # ----------------

            NifLog.info("Checking controllers")
            if bpy.context.scene.niftools_scene.game == 'MORROWIND':
                # animations without keyframe animations crash the TESCS
                # if we are in that situation, add a trivial keyframe animation
                has_keyframecontrollers = False
                for block in block_store.block_to_obj:
                    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.transform_anim.create_controller(
                        root_block, root_block.name)

                if NifOp.props.bs_animation_node:
                    for block in block_store.block_to_obj:
                        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 bpy.context.scene.niftools_scene.game in (
                    'OBLIVION', 'FALLOUT_3',
                    'SKYRIM') and filebase.lower() in ('skeleton',
                                                       'skeletonbeast'):
                self.transform_anim.add_dummy_controllers()

            # bhkConvexVerticesShape of children of bhkListShapes need an extra bhkConvexTransformShape (see issue #3308638, reported by Koniption)
            # note: block_store.block_to_obj changes during iteration, so need list copy
            for block in list(block_store.block_to_obj.keys()):
                if isinstance(block, NifFormat.bhkListShape):
                    for i, sub_shape in enumerate(block.sub_shapes):
                        if isinstance(sub_shape,
                                      NifFormat.bhkConvexVerticesShape):
                            coltf = block_store.create_block(
                                "bhkConvexTransformShape")
                            coltf.material = sub_shape.material
                            coltf.unknown_float_1 = 0.1
                            unk_8 = coltf.unknown_8_bytes
                            unk_8[0] = 96
                            unk_8[1] = 120
                            unk_8[2] = 53
                            unk_8[3] = 19
                            unk_8[4] = 24
                            unk_8[5] = 9
                            unk_8[6] = 253
                            unk_8[7] = 4
                            coltf.transform.set_identity()
                            coltf.shape = sub_shape
                            block.sub_shapes[i] = coltf

            # export constraints
            for b_obj in self.exportable_objects:
                if b_obj.constraints:
                    self.constrainthelper.export_constraints(b_obj, root_block)

            object_prop = ObjectProperty()
            object_prop.export_root_node_properties(root_block)

            # 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 block_store.block_to_obj:
                    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
            data.roots = [root_block]
            scale_correction = bpy.context.scene.niftools_scene.scale_correction
            if abs(scale_correction) > NifOp.props.epsilon:
                self.apply_scale(data, round(1 / NifOp.props.scale_correction))
                # also scale egm
                if EGMData.data:
                    EGMData.data.apply_scale(1 / scale_correction)

            # generate mopps (must be done after applying scale!)
            if bpy.context.scene.niftools_scene.game in ('OBLIVION',
                                                         'FALLOUT_3',
                                                         'SKYRIM'):
                for block in block_store.block_to_obj:
                    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."
                            )

            # export nif file:
            # ----------------
            if bpy.context.scene.niftools_scene.game == 'EMPIRE_EARTH_II':
                ext = ".nifcache"
            else:
                ext = ".nif"
            NifLog.info(f"Writing {ext} file")

            # make sure we have the right file extension
            if fileext.lower() != ext:
                NifLog.warn(
                    f"Changing extension from {fileext} to {ext} on output file"
                )
            niffile = os.path.join(directory, prefix + filebase + ext)

            data.roots = [root_block]
            # todo [export] I believe this is redundant and setting modification only is the current way?
            data.neosteam = (
                bpy.context.scene.niftools_scene.game == 'NEOSTEAM')
            if bpy.context.scene.niftools_scene.game == 'NEOSTEAM':
                data.modification = "neosteam"
            elif bpy.context.scene.niftools_scene.game == 'ATLANTICA':
                data.modification = "ndoors"
            elif bpy.context.scene.niftools_scene.game == 'HOWLING_SWORD':
                data.modification = "jmihs1"

            with open(niffile, "wb") as stream:
                data.write(stream)

            # export egm file:
            # -----------------
            if EGMData.data:
                ext = ".egm"
                NifLog.info(f"Writing {ext} file")

                egmfile = os.path.join(directory, filebase + ext)
                with open(egmfile, "wb") as stream:
                    EGMData.data.write(stream)

            # save exported file (this is used by the test suite)
            self.root_blocks = [root_block]

        except NifError:
            return {'CANCELLED'}

        NifLog.info("Finished")
        return {'FINISHED'}