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'}
Пример #2
0
    def determine_texture_types(self, b_mat):
        """Checks all texture nodes of a material and checks their labels for relevant texture cues.
        Stores all slots as class properties."""
        self.b_mat = b_mat
        self._reset_fields()

        for b_texture_node in self.get_used_textslots(b_mat):
            shown_label = b_texture_node.label
            if shown_label == '':
                shown_label = b_texture_node.image.name
            NifLog.debug(
                f"Found node {b_texture_node.name} of type {shown_label}")

            # go over all slots
            for slot_name in self.slots.keys():
                if slot_name in shown_label:
                    # slot has already been populated
                    if self.slots[slot_name]:
                        raise NifError(
                            f"Multiple {slot_name} textures in material '{b_mat.name}''.\n"
                            f"Make sure there is only one texture node labeled as '{slot_name}'"
                        )
                    # it's a new slot so store it
                    self.slots[slot_name] = b_texture_node
                    break
            # unsupported texture type
            else:
                raise NifError(
                    f"Do not know how to export texture node '{b_texture_node.name}' in material '{b_mat.name}' with label '{shown_label}'."
                    f"Delete it or change its label.")
Пример #3
0
    def init(operator, context):
        NifOp.op = operator
        NifOp.props = operator.properties
        NifOp.context = context

        # init loggers logging level
        NifLog.init(operator)
Пример #4
0
    def fix_pose(self, n_armature, n_node, armature_space_bind_store,
                 armature_space_pose_store):
        """reposition non-skeletal bones to maintain their local orientation to their skeletal parents"""
        for n_child_node in n_node.children:
            # only process nodes
            if not isinstance(n_child_node, NifFormat.NiNode):
                continue
            if n_child_node not in armature_space_bind_store and n_child_node in armature_space_pose_store:
                NifLog.debug(
                    f"Calculating bind pose for non-skeletal bone {n_child_node.name}"
                )
                # get matrices for n_node (the parent) - fallback to getter if it is not in the store
                n_armature_pose = armature_space_pose_store.get(
                    n_node, n_node.get_transform(n_armature))
                # get bind of parent node or pose if it has no bind pose
                n_armature_bind = armature_space_bind_store.get(
                    n_node, n_armature_pose)

                # the child must have a pose, no need for a fallback
                n_child_armature_pose = armature_space_pose_store[n_child_node]
                # get the relative transform of n_child_node from pose * inverted parent pose
                n_child_local_pose = n_child_armature_pose * n_armature_pose.get_inverse(
                    fast=False)
                # get object space transform by multiplying with bind of parent bone
                armature_space_bind_store[
                    n_child_node] = n_child_local_pose * n_armature_bind

            self.fix_pose(n_armature, n_child_node, armature_space_bind_store,
                          armature_space_pose_store)
Пример #5
0
    def import_sequence_stream_helper(self, kf_root, b_armature_obj,
                                      bind_data):
        NifLog.debug('Importing NiSequenceStreamHelper...')
        b_action = self.create_action(b_armature_obj,
                                      kf_root.name.decode(),
                                      retrieve=False)
        # import parallel trees of extra datas and keyframe controllers
        extra = kf_root.extra_data
        controller = kf_root.controller
        while extra and controller:
            # textkeys in the stack do not specify node names, import as markers
            while isinstance(extra, NifFormat.NiTextKeyExtraData):
                self.import_text_key_extra_data(extra, b_action)
                extra = extra.next_extra_data

            # grabe the node name from string data
            bone_name = None
            if isinstance(extra, NifFormat.NiStringExtraData):
                node_name = extra.string_data.decode()
                bone_name = block_registry.get_bone_name_for_blender(node_name)
            # import keyframe controller
            if bone_name in bind_data:
                niBone_bind_scale, niBone_bind_rot_inv, niBone_bind_trans = bind_data[
                    bone_name]
                self.import_keyframe_controller(controller, b_armature_obj,
                                                bone_name, niBone_bind_scale,
                                                niBone_bind_rot_inv,
                                                niBone_bind_trans)
            # grab next pair of extra and controller
            extra = extra.next_extra_data
            controller = controller.next_controller
    def import_material_uv_controller(self, b_material, n_geom):
        """Import UV controller data."""
        # search for the block
        n_ctrl = math.find_controller(n_geom, NifFormat.NiUVController)
        if not n_ctrl:
            return
        NifLog.info("Importing UV controller")

        b_mat_action = self.create_action(b_material, "MaterialAction")

        dtypes = ("offset", 0), ("offset", 1), ("scale", 0), ("scale", 1)
        for n_uvgroup, (data_path, array_ind) in zip(n_ctrl.data.uv_groups,
                                                     dtypes):
            if n_uvgroup.keys:
                interp = self.get_b_interp_from_n_interp(
                    n_uvgroup.interpolation)
                # in blender, UV offset is stored per n_texture slot
                # so we have to repeat the import for each used tex slot
                for i, texture_slot in enumerate(b_material.texture_slots):
                    if texture_slot:
                        fcurves = self.create_fcurves(
                            b_mat_action, f"texture_slots[{i}]." + data_path,
                            (array_ind, ), n_ctrl.flags)
                        for key in n_uvgroup.keys:
                            if "offset" in data_path:
                                self.add_key(fcurves, key.time, (-key.value, ),
                                             interp)
                            else:
                                self.add_key(fcurves, key.time, (key.value, ),
                                             interp)
Пример #7
0
def import_version_info(data):
    scene = bpy.context.scene.niftools_scene
    scene.nif_version = data._version_value_._value
    scene.user_version = data._user_version_value_._value
    scene.user_version_2 = data._user_version_2_value_._value

    # filter possible games by nif version
    possible_games = []
    for game, versions in NifFormat.games.items():
        if game != '?':
            if scene.nif_version in versions:
                game_enum = _game_to_enum(game)
                # go to next game if user version for this game does not match defined
                if game_enum in scene.USER_VERSION:
                    if scene.USER_VERSION[game_enum] != scene.user_version:
                        continue
                # or user version in scene is not 0 when this game has no associated user version
                elif scene.user_version != 0:
                    continue
                # same checks for user version 2
                if game_enum in scene.USER_VERSION_2:
                    if scene.USER_VERSION_2[game_enum] != scene.user_version_2:
                        continue
                elif scene.user_version_2 != 0:
                    continue
                # passed all checks, add to possible games list
                possible_games.append(game_enum)
    if len(possible_games) == 1:
        scene.game = possible_games[0]
    elif len(possible_games) > 1:
        scene.game = possible_games[0]
        # todo[version] - check if this nif's version is marked as default for any of the possible games and use that
        NifLog.warn(
            f"Game set to '{possible_games[0]}', but multiple games qualified")
Пример #8
0
 def process_nimaterial_property(self, prop):
     """Import a NiMaterialProperty based material"""
     NiMaterial().import_material(self.n_block, self.b_mat, prop)
     # TODO [animation][material] merge this call into import_material
     MaterialAnimation().import_material_controllers(
         self.n_block, self.b_mat)
     NifLog.debug("NiMaterialProperty property processed")
    def export_bsxflags_upb(self, root_block, root_objects):
        # TODO [object][property] Fixme
        NifLog.info("Checking collision")
        # activate oblivion/Fallout 3 collision and physics
        if bpy.context.scene.niftools_scene.game in ('OBLIVION', 'FALLOUT_3',
                                                     'SKYRIM'):
            b_obj = self.has_collision()
            if b_obj:
                # enable collision
                bsx = block_store.create_block("BSXFlags")
                bsx.name = 'BSX'
                root_block.add_extra_data(bsx)
                found_bsx = False
                for root_object in root_objects:
                    if root_object.niftools.bsxflags:
                        if found_bsx:
                            raise NifError(
                                "Multiple objects have BSXFlags. Only one onject may contain this data"
                            )
                        else:
                            found_bxs = True
                            bsx.integer_data = root_object.niftools.bsxflags

                # 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 = block_store.create_block("NiStringExtraData")
                    upb.name = 'UPB'
                    if b_obj.niftools.upb == '':
                        upb.string_data = UPB_DEFAULT
                    else:
                        upb.string_data = b_obj.niftools.upb.encode()
                    root_block.add_extra_data(upb)
Пример #10
0
    def import_bhkcapsule_shape(self, bhk_shape):
        """Import a BhkCapsule block as a simple cylinder collision object"""
        NifLog.debug(f"Importing {bhk_shape.__class__.__name__}")

        radius = bhk_shape.radius * self.HAVOK_SCALE
        p_1 = bhk_shape.first_point
        p_2 = bhk_shape.second_point
        length = (p_1 - p_2).norm() * self.HAVOK_SCALE
        first_point = p_1 * self.HAVOK_SCALE
        second_point = p_2 * self.HAVOK_SCALE
        minx = miny = -radius
        maxx = maxy = +radius
        minz = -radius - length / 2
        maxz = length / 2 + radius

        # create blender object
        b_obj = Object.box_from_extents("capsule", minx, maxx, miny, maxy,
                                        minz, maxz)
        # here, these are not encoded as a direction so we must first calculate the direction
        b_obj.matrix_local = self.center_origin_to_matrix(
            (first_point + second_point) / 2, first_point - second_point)
        self.set_b_collider(b_obj,
                            bounds_type="CAPSULE",
                            display_type="CAPSULE",
                            radius=radius,
                            n_obj=bhk_shape)
        return [b_obj]
Пример #11
0
    def process_nispecular_property(self, prop):
        """SpecularProperty based specular"""

        # TODO [material][property]
        if NifData.data.version == 0x14000004:
            self.b_mat.specular_intensity = 0.0  # no specular prop
        NifLog.debug("NiSpecularProperty property processed")
Пример #12
0
    def import_bhk_ridgidbody_t(self, bhk_shape):
        """Imports a BhkRigidBody block and applies the transform to the collision objects"""
        NifLog.debug(f"Importing {bhk_shape.__class__.__name__}")

        # import shapes
        collision_objs = self.import_bhk_shape(bhk_shape.shape)

        # find transformation matrix in case of the T version
        # set rotation
        b_rot = bhk_shape.rotation
        transform = mathutils.Quaternion([b_rot.w, b_rot.x, b_rot.y,
                                          b_rot.z]).to_matrix().to_4x4()

        # set translation
        b_trans = bhk_shape.translation
        transform.translation = mathutils.Vector(
            (b_trans.x, b_trans.y, b_trans.z)) * self.HAVOK_SCALE

        # apply transform
        for b_col_obj in collision_objs:
            b_col_obj.matrix_local = b_col_obj.matrix_local @ transform

        self._import_bhk_rigid_body(bhk_shape, collision_objs)

        # and return a list of transformed collision shapes
        return collision_objs
def register():
    # addon updater code and configurations in case of broken version, try to register the updater first
    # so that users can revert back to a working version
    NifLog.debug("Starting registration")
    configure_autoupdater()

    register_modules(MODS, __name__)
Пример #14
0
    def export_text_keys(self, b_action):
        """Process b_action's pose markers and return an extra string data block."""
        if NifOp.props.animation == 'GEOM_NIF':
            # animation group extra data is not present in geometry only files
            return
        NifLog.info("Exporting animation groups")

        self.add_dummy_markers(b_action)

        # add a NiTextKeyExtraData block
        n_text_extra = block_store.create_block("NiTextKeyExtraData",
                                                b_action.pose_markers)

        # create a text key for each frame descriptor
        n_text_extra.num_text_keys = len(b_action.pose_markers)
        n_text_extra.text_keys.update_size()
        f0, f1 = b_action.frame_range
        for key, marker in zip(n_text_extra.text_keys, b_action.pose_markers):
            f = marker.frame
            if (f < f0) or (f > f1):
                NifLog.warn(
                    f"Marker out of animated range ({f} not between [{f0}, {f1}])"
                )

            key.time = f / self.fps
            key.value = marker.name.replace('/', '\r\n')

        return n_text_extra
Пример #15
0
    def apply_skin_deformation(n_data):
        """ Process all geometries in NIF tree to apply their skin """
        # get all geometries with skin
        n_geoms = [
            g for g in n_data.get_global_iterator()
            if isinstance(g, NifFormat.NiGeometry) and g.is_skin()
        ]

        # make sure that each skin is applied only once to avoid distortions when a model is referred to twice
        for n_geom in set(n_geoms):
            NifLog.info('Applying skin deformation on geometry {0}'.format(
                n_geom.name))
            skininst = n_geom.skin_instance
            skindata = skininst.data
            if skindata.has_vertex_weights:
                vertices = n_geom.get_skin_deformation()[0]
            else:
                NifLog.info(
                    "PyFFI does not support this type of skinning, so here's a workaround..."
                )
                vertices = VertexGroup.get_skin_deformation_from_partition(
                    n_geom)

            # finally we can actually set the data
            for vold, vnew in zip(n_geom.data.vertices, vertices):
                vold.x = vnew.x
                vold.y = vnew.y
                vold.z = vnew.z
Пример #16
0
    def export_root_node(self, root_objects, filebase):
        """ Exports a nif's root node; use blender root if there is only one, else create a meta root """
        # TODO [collsion] detect root collision -> root collision node (can be mesh or empty)
        #     self.nif_export.collisionhelper.export_collision(b_obj, n_parent)
        #     return None  # done; stop here
        self.n_root = None
        # there is only one root object so that will be our final root
        if len(root_objects) == 1:
            b_obj = root_objects[0]
            self.export_node(b_obj, None)

        # there is more than one root object so we create a meta root
        else:
            NifLog.info("Created meta root because blender scene had multiple root objects")
            self.n_root = types.create_ninode()
            self.n_root.name = "Scene Root"
            for b_obj in root_objects:
                self.export_node(b_obj, self.n_root)

        # TODO [object] How dow we know we are selecting the right node in the case of multi-root?
        # making root block a fade node
        root_type = b_obj.niftools.rootnode
        if bpy.context.scene.niftools_scene.game in ('FALLOUT_3', 'SKYRIM') and root_type == 'BSFadeNode':
            NifLog.info("Making root block a BSFadeNode")
            fade_root_block = NifFormat.BSFadeNode().deepcopy(self.n_root)
            fade_root_block.replace_global_node(self.n_root, fade_root_block)
            self.n_root = fade_root_block

        # various extra datas
        object_property = ObjectDataProperty()
        object_property.export_bsxflags_upb(self.n_root, root_objects)
        object_property.export_inventory_marker(self.n_root, root_objects)
        object_property.export_weapon_location(self.n_root, b_obj)
        types.export_furniture_marker(self.n_root, filebase)
        return self.n_root
Пример #17
0
    def import_bhksphere_shape(self, bhk_shape):
        """Import a BhkSphere block as a simple sphere collision object"""
        NifLog.debug(f"Importing {bhk_shape.__class__.__name__}")

        r = bhk_shape.radius * self.HAVOK_SCALE
        b_obj = Object.box_from_extents("sphere", -r, r, -r, r, -r, r)
        self.set_b_collider(b_obj, display_type="SPHERE", bounds_type='SPHERE', radius=r, n_obj=bhk_shape)
        return [b_obj]
 def import_controller_manager(self, n_block, b_obj, b_armature):
     ctrlm = n_block.controller
     if ctrlm and isinstance(ctrlm, NifFormat.NiControllerManager):
         NifLog.debug(f'Importing NiControllerManager')
         if b_armature:
             self.get_bind_data(b_armature)
         for ctrl in ctrlm.controller_sequences:
             self.import_kf_root(ctrl, b_armature)
Пример #19
0
    def get_extend_from_flags(flags):
        if flags & 6 == 4:  # 0b100
            return "CONSTANT"
        elif flags & 6 == 0:  # 0b000
            return "CYCLIC"

        NifLog.warn("Unsupported cycle mode in nif, using clamped.")
        return "CONSTANT"
Пример #20
0
    def export_texture_filename(b_texture_node):
        """Returns image file name from b_texture_node.

        @param b_texture_node: The b_texture_node object in blender.
        @return: The file name of the image used in the b_texture_node.
        """

        if not isinstance(b_texture_node, bpy.types.ShaderNodeTexImage):
            raise io_scene_niftools.utils.logging.NifError(
                f"Expected a Shader node texture, got {type(b_texture_node)}")
        # get filename from image

        # TODO [b_texture_node] still needed? can b_texture_node.image be None in current blender?
        # check that image is loaded
        if b_texture_node.image is None:
            raise io_scene_niftools.utils.logging.NifError(
                f"Image type texture has no file loaded ('{b_texture_node.name}')"
            )

        filename = b_texture_node.image.filepath

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

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

        # sanitize file path
        if bpy.context.scene.niftools_scene.game not in ('MORROWIND',
                                                         'OBLIVION',
                                                         'FALLOUT_3',
                                                         'SKYRIM'):
            # strip b_texture_node file path
            filename = os.path.basename(filename)

        else:
            # strip the data files prefix from the b_texture_node's file name
            filename = filename.lower()
            idx = filename.find("textures")
            if idx >= 0:
                filename = filename[idx:]
            elif not os.path.exists(bpy.path.abspath(filename)):
                pass
            else:
                NifLog.warn(
                    f"{filename} does not reside in a 'Textures' folder; texture path will be stripped and textures may not display in-game"
                )
                filename = os.path.basename(filename)
        # for linux export: fix path separators
        return filename.replace('/', '\\')
Пример #21
0
 def create_and_link(self, slot_name, n_tex_info):
     """"""
     slot_lower = slot_name.lower().replace(' ', '_')
     import_func_name = f"link_{slot_lower}_node"
     import_func = getattr(self, import_func_name, None)
     if not import_func:
         NifLog.debug(f"Could not find texture linking function {import_func_name}")
         return
     b_texture = self.create_texture_slot(n_tex_info)
     import_func(b_texture)
Пример #22
0
    def get_n_apply_mode_from_b_blend_type(b_blend_type):
        if b_blend_type == "LIGHTEN":
            return NifFormat.ApplyMode.APPLY_HILIGHT
        elif b_blend_type == "MULTIPLY":
            return NifFormat.ApplyMode.APPLY_HILIGHT2
        elif b_blend_type == "MIX":
            return NifFormat.ApplyMode.APPLY_MODULATE

        NifLog.warn("Unsupported blend type ({0}) in material, using apply mode APPLY_MODULATE".format(b_blend_type))
        return NifFormat.ApplyMode.APPLY_MODULATE
Пример #23
0
    def import_bhk_ridgid_body(self, bhk_shape):
        """Imports a BhkRigidBody block and applies the transform to the collision objects"""
        NifLog.debug(f"Importing {bhk_shape.__class__.__name__}")

        # import shapes
        collision_objs = self.import_bhk_shape(bhk_shape.shape)
        self._import_bhk_rigid_body(bhk_shape, collision_objs)

        # and return a list of transformed collision shapes
        return collision_objs
 def add_dummy_markers(self, b_action):
     # if we exported animations, but no animation groups are defined,
     # define a default animation group
     NifLog.info("Checking action pose markers.")
     if not b_action.pose_markers:
         NifLog.info("Defining default action pose markers.")
         for frame, text in zip(b_action.frame_range,
                                ("Idle: Start/Idle: Loop Start",
                                 "Idle: Loop Stop/Idle: Stop")):
             marker = b_action.pose_markers.new(text)
             marker.frame = frame
 def store_pose_matrices(self, n_node, n_root):
     """Stores the nif armature space matrix of a node tree"""
     # check that n_block is indeed a bone
     if not self.is_bone(n_node):
         return None
     NifLog.debug(f"Storing pose matrix for {n_node.name}")
     # calculate the transform relative to root, ie. turn nif local into nif armature space
     self.pose_store[n_node] = n_node.get_transform(n_root)
     # move down the hierarchy
     for n_child in n_node.children:
         self.store_pose_matrices(n_child, n_root)
Пример #26
0
def get_version_data():
    """ Returns NifFormat.Data of the correct version and user versions """
    b_scene = bpy.context.scene.niftools_scene
    game = b_scene.game
    version = b_scene.nif_version
    NifLog.info(f"Writing NIF version 0x{version:08X}")

    # get user version and user version 2 for export
    user_version = b_scene.user_version if b_scene.user_version else b_scene.USER_VERSION.get(game, 0)
    user_version_2 = b_scene.user_version_2 if b_scene.user_version_2 else b_scene.USER_VERSION_2.get(game, 0)

    return version, NifFormat.Data(version, user_version, user_version_2)
Пример #27
0
 def load_image(tex_path):
     """Returns an image or a generated image if none was found"""
     name = os.path.basename(tex_path)
     if name not in bpy.data.images:
         try:
             b_image = bpy.data.images.load(tex_path)
         except:
             NifLog.warn(f"Texture '{name}' not found or not supported and no alternate available")
             b_image = bpy.data.images.new(name=name, width=1, height=1, alpha=True)
     else:
         b_image = bpy.data.images[name]
     return b_image
def configure_autoupdater():
    NifLog.debug("Configuring auto-updater")
    addon_updater_ops.register(bl_info)
    addon_updater_ops.updater.select_link = select_zip_file
    addon_updater_ops.updater.use_releases = True
    addon_updater_ops.updater.remove_pre_update_patterns = [
        "*.py", "*.pyc", "*.xml", "*.exe", "*.rst", "VERSION", "*.xsd"
    ]
    addon_updater_ops.updater.user = "******"
    addon_updater_ops.updater.repo = "blender_niftools_addon"
    addon_updater_ops.updater.website = "https://github.com/niftools/blender_niftools_addon/"
    addon_updater_ops.updater.version_min_update = (0, 0, 4)
Пример #29
0
 def set_extrapolation(extend_type, fcurves):
     if extend_type == "CONSTANT":
         for fcurve in fcurves:
             fcurve.extrapolation = 'CONSTANT'
     elif extend_type == "CYCLIC":
         for fcurve in fcurves:
             fcurve.modifiers.new('CYCLES')
     # don't support reverse for now, not sure if it is even possible in blender
     else:
         NifLog.warn("Unsupported extrapolation mode, using clamped.")
         for fcurve in fcurves:
             fcurve.extrapolation = 'CONSTANT'
Пример #30
0
    def get_n_interp_from_b_interp(b_ipol):
        if b_ipol == "LINEAR":
            return NifFormat.KeyType.LINEAR_KEY
        elif b_ipol == "BEZIER":
            return NifFormat.KeyType.QUADRATIC_KEY
        elif b_ipol == "CONSTANT":
            return NifFormat.KeyType.CONST_KEY

        NifLog.warn(
            f"Unsupported interpolation mode ({b_ipol}) in blend, using quadratic/bezier."
        )
        return NifFormat.KeyType.QUADRATIC_KEY