Exemplo n.º 1
0
    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
Exemplo n.º 2
0
 def import_billboard(n_node, b_obj):
     """ Import a NiBillboardNode """
     if isinstance(n_node, NifFormat.NiBillboardNode) and not isinstance(
             b_obj, bpy.types.Bone):
         # find camera object
         for obj in bpy.context.scene.objects:
             if obj.type == 'CAMERA':
                 b_obj_camera = obj
                 break
         # none exists, create one
         else:
             b_obj_camera_data = bpy.data.cameras.new("Camera")
             b_obj_camera = Object.create_b_obj(None, b_obj_camera_data)
         # make b_obj track camera object
         constr = b_obj.constraints.new('TRACK_TO')
         constr.target = b_obj_camera
         constr.track_axis = 'TRACK_Z'
         constr.up_axis = 'UP_Y'
Exemplo n.º 3
0
 def import_empty(n_block):
     """Creates and returns a grouping empty."""
     b_empty = Object.create_b_obj(n_block, None)
     # TODO [flags] Move out to generic processing
     b_empty.niftools.flags = n_block.flags
     return b_empty
Exemplo n.º 4
0
class NifImport(NifCommon):
    def __init__(self, operator, context):
        NifCommon.__init__(self, operator, context)

    def execute(self):
        """Main import function."""
        self.load_files()  # needs to be first to provide version info.

        self.armaturehelper = Armature()
        self.boundhelper = Bound()
        self.bhkhelper = BhkCollision()
        self.constrainthelper = Constraint()
        self.objecthelper = Object()
        self.object_anim = ObjectAnimation()
        self.transform_anim = TransformAnimation()

        # find and store this list now of selected objects as creating new objects adds them to the selection list
        self.SELECTED_OBJECTS = bpy.context.selected_objects[:]

        # catch nif import errors
        try:
            # check that one armature is selected in 'import geometry + parent
            # to armature' mode
            if NifOp.props.process == "GEOMETRY_ONLY":
                if len(self.SELECTED_OBJECTS
                       ) != 1 or self.SELECTED_OBJECTS[0].type != 'ARMATURE':
                    raise io_scene_niftools.utils.logging.NifError(
                        "You must select exactly one armature in 'Import Geometry Only' mode."
                    )

            NifLog.info("Importing data")
            # calculate and set frames per second
            if NifOp.props.animation:
                Animation.set_frames_per_second(NifData.data.roots)

            # merge skeleton roots and transform geometry into the rest pose
            if NifOp.props.merge_skeleton_roots:
                pyffi.spells.nif.fix.SpellMergeSkeletonRoots(
                    data=NifData.data).recurse()
            if NifOp.props.send_geoms_to_bind_pos:
                pyffi.spells.nif.fix.SpellSendGeometriesToBindPosition(
                    data=NifData.data).recurse()
            if NifOp.props.send_detached_geoms_to_node_pos:
                pyffi.spells.nif.fix.SpellSendDetachedGeometriesToNodePosition(
                    data=NifData.data).recurse()
            if NifOp.props.apply_skin_deformation:
                VertexGroup.apply_skin_deformation(NifData.data)

            # store scale correction
            bpy.context.scene.niftools_scene.scale_correction = NifOp.props.scale_correction
            self.apply_scale(NifData.data, NifOp.props.scale_correction)

            # import all root blocks
            for block in NifData.data.roots:
                root = block
                # root hack for corrupt better bodies meshes and remove geometry from better bodies on skeleton import
                for b in (b for b in block.tree() if
                          isinstance(b, NifFormat.NiGeometry) and b.is_skin()):
                    # check if root belongs to the children list of the skeleton root (can only happen for better bodies meshes)
                    if root in [
                            c for c in b.skin_instance.skeleton_root.children
                    ]:
                        # fix parenting and update transform accordingly
                        b.skin_instance.data.set_transform(
                            root.get_transform() *
                            b.skin_instance.data.get_transform())
                        b.skin_instance.skeleton_root = root
                        # delete non-skeleton nodes if we're importing skeleton only
                        if NifOp.props.process == "SKELETON_ONLY":
                            nonbip_children = (child for child in root.children
                                               if child.name[:6] != 'Bip01 ')
                            for child in nonbip_children:
                                root.remove_child(child)

                # import this root block
                NifLog.debug(f"Root block: {root.get_global_display()}")
                self.import_root(root)

        except NifError:
            return {'CANCELLED'}

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

    def load_files(self):
        NifData.init(NifFile.load_nif(NifOp.props.filepath))
        if NifOp.props.override_scene_info:
            scene.import_version_info(NifData.data)

    def import_root(self, root_block):
        """Main import function."""
        # check that this is not a kf file
        if isinstance(
                root_block,
            (NifFormat.NiSequence, NifFormat.NiSequenceStreamHelper)):
            raise io_scene_niftools.utils.logging.NifError(
                "Use the KF import operator to load KF files.")

        # divinity 2: handle CStreamableAssetData
        if isinstance(root_block, NifFormat.CStreamableAssetData):
            root_block = root_block.root

        # sets the root block parent to None, so that when crawling back the script won't barf
        root_block._parent = None

        # set the block parent through the tree, to ensure I can always move backward
        self.set_parents(root_block)

        # mark armature nodes and bones
        self.armaturehelper.mark_armatures_bones(root_block)

        # import the keyframe notes
        # if NifOp.props.animation:
        #     self.animationhelper.import_text_keys(root_block)

        # read the NIF tree
        if isinstance(root_block,
                      (NifFormat.NiNode, NifFormat.NiTriBasedGeom)):
            b_obj = self.import_branch(root_block)
            ObjectProperty().import_extra_datas(root_block, b_obj)

            # now all havok objects are imported, so we are ready to import the havok constraints
            self.constrainthelper.import_bhk_constraints()

            # parent selected meshes to imported skeleton
            if NifOp.props.process == "SKELETON_ONLY":
                # update parenting & armature modifier
                for child in bpy.context.selected_objects:
                    if isinstance(child, bpy.types.Object) and not isinstance(
                            child.data, bpy.types.Armature):
                        child.parent = b_obj
                        for mod in child.modifiers:
                            if mod.type == "ARMATURE":
                                mod.object = b_obj

        elif isinstance(root_block, NifFormat.NiCamera):
            NifLog.warn('Skipped NiCamera root')

        elif isinstance(root_block, NifFormat.NiPhysXProp):
            NifLog.warn('Skipped NiPhysXProp root')

        else:
            NifLog.warn(
                f"Skipped unsupported root block type '{root_block.__class__}' (corrupted nif?)."
            )

    def import_collision(self, n_node):
        """ Imports a NiNode's collision_object, if present"""
        if n_node.collision_object:
            if isinstance(n_node.collision_object,
                          NifFormat.bhkNiCollisionObject):
                return self.bhkhelper.import_bhk_shape(
                    n_node.collision_object.body)
            elif isinstance(n_node.collision_object,
                            NifFormat.NiCollisionData):
                return self.boundhelper.import_bounding_volume(
                    n_node.collision_object.bounding_volume)
        return []

    def import_branch(self, n_block, b_armature=None, n_armature=None):
        """Read the content of the current NIF tree branch to Blender recursively.

        :param n_block: The nif block to import.
        :param b_armature: The blender armature for the current branch.
        :param n_armature: The corresponding nif block for the armature for  the current branch.
        """
        if not n_block:
            return None

        NifLog.info(f"Importing data for block '{n_block.name.decode()}'")
        if isinstance(n_block, NifFormat.NiTriBasedGeom
                      ) and NifOp.props.process != "SKELETON_ONLY":
            return self.objecthelper.import_geometry_object(
                b_armature, n_block)

        elif isinstance(n_block, NifFormat.NiNode):
            # import object
            if self.armaturehelper.is_armature_root(n_block):
                # all bones in the tree are also imported by import_armature
                if NifOp.props.process != "GEOMETRY_ONLY":
                    b_obj = self.armaturehelper.import_armature(n_block)
                else:
                    n_name = block_store.import_name(n_block)
                    b_obj = math.get_armature()
                    NifLog.info(
                        f"Merging nif tree '{n_name}' with armature '{b_obj.name}'"
                    )
                    if n_name != b_obj.name:
                        NifLog.warn(
                            f"Using Nif block '{n_name}' as armature '{b_obj.name}' but names do not match"
                        )
                b_armature = b_obj
                n_armature = n_block

            elif self.armaturehelper.is_bone(n_block):
                # bones have already been imported during import_armature
                n_name = block_store.import_name(n_block)
                if n_name in b_armature.data.bones:
                    b_obj = b_armature.data.bones[n_name]
                else:
                    # this is a fallback for a weird bug, when a node is child of a NiLodNode in a skeletal nif
                    b_obj = self.objecthelper.create_b_obj(n_block,
                                                           None,
                                                           name=n_name)
                b_obj.niftools.flags = n_block.flags

            else:
                # import as an empty
                b_obj = NiTypes.import_empty(n_block)

            # find children
            b_children = []
            n_children = [child for child in n_block.children]
            for n_child in n_children:
                b_child = self.import_branch(n_child,
                                             b_armature=b_armature,
                                             n_armature=n_armature)
                if b_child and isinstance(b_child, bpy.types.Object):
                    b_children.append(b_child)

            # import collision objects & bounding box
            if NifOp.props.process != "SKELETON_ONLY":
                b_children.extend(self.import_collision(n_block))
                b_children.extend(
                    self.boundhelper.import_bounding_box(n_block))

            # set bind pose for children
            self.objecthelper.set_object_bind(b_obj, b_children, b_armature)

            # import extra node data, such as node type
            NiTypes.import_root_collision(n_block, b_obj)
            NiTypes.import_billboard(n_block, b_obj)
            NiTypes.import_range_lod_data(n_block, b_obj, b_children)

            # set object transform, this must be done after all children objects have been parented to b_obj
            if isinstance(b_obj, bpy.types.Object):
                # note: bones and this object's children already have their matrix set
                b_obj.matrix_local = math.import_matrix(n_block)

                # import object level animations (non-skeletal)
                if NifOp.props.animation:
                    # self.animationhelper.import_text_keys(n_block)
                    self.transform_anim.import_transforms(n_block, b_obj)
                    self.object_anim.import_visibility(n_block, b_obj)

            return b_obj

        # all else is currently discarded
        return None

    def set_parents(self, n_block):
        """Set the parent block recursively through the tree, to allow
        crawling back as needed."""
        if isinstance(n_block, NifFormat.NiNode):
            # list of non-null children
            children = [child for child in n_block.children if child]
            for child in children:
                child._parent = n_block
                self.set_parents(child)