def import_texture_extra_shader(self, b_mat, n_texture_prop, extra_datas):
        # extra texture shader slots
        for shader_tex_desc in n_texture_prop.shader_textures:

            if not shader_tex_desc.is_used:
                continue

            # it is used, figure out the slot it is used for
            for extra in extra_datas:
                if extra.integer_data == shader_tex_desc.map_index:
                    shader_name = extra.name
                    break
            else:
                NifLog.warn("No slot for shader texture {0}.".format(
                    shader_tex_desc.texture_data.source.file_name))
                continue
            try:
                extra_shader_index = (
                    self.nif_import.EXTRA_SHADER_TEXTURES.index(shader_name))
            except ValueError:
                # shader_name not in self.EXTRA_SHADER_TEXTURES
                NifLog.warn("No slot for shader texture {0}.".format(
                    shader_tex_desc.texture_data.source.file_name))
                continue

            self.import_shader_by_type(shader_tex_desc, extra_shader_index)
    def import_material_color_controller(self, b_material, b_channel, n_geom,
                                         n_target_color):
        # find material color controller with matching target color
        n_matprop = nif_utils.find_property(n_geom,
                                            NifFormat.NiMaterialProperty)
        if not n_matprop:
            return
        for ctrl in n_matprop.get_controllers():
            if isinstance(ctrl, NifFormat.NiMaterialColorController):
                if ctrl.get_target_color() == n_target_color:
                    n_matcolor_ctrl = ctrl
                    break
        else:
            return
        NifLog.info(
            "Importing material color controller for target color {0} into blender channel {0}"
            .format(n_target_color, b_channel))
        # import data as curves
        b_mat_action = self.nif_import.animationhelper.create_action(
            b_material, "MaterialAction")

        fcurves = create_fcurves(b_mat_action, b_channel, range(3))
        interp = self.nif_import.animationhelper.get_b_interp_from_n_interp(
            n_matcolor_ctrl.data.data.interpolation)
        self.nif_import.animationhelper.set_extrapolation(
            n_matcolor_ctrl.flags, fcurves)
        for key in n_matcolor_ctrl.data.data.keys:
            self.nif_import.animationhelper.add_key(fcurves, key.time,
                                                    key.value.as_list(),
                                                    interp)
    def import_kf_standalone(self, kf_root, b_armature_obj, bind_data):
        """
        Import a kf animation. Needs a suitable armature in blender scene.
        """

        NifLog.info("Importing KF tree")

        # check that this is an Oblivion style kf file
        if not isinstance(kf_root, NifFormat.NiControllerSequence):
            raise nif_utils.NifError("non-Oblivion .kf import not supported")

        # import text keys
        self.import_text_keys(kf_root)

        self.create_action(b_armature_obj, kf_root.name.decode())
        # go over all controlled blocks (NiKeyframeController)
        for controlledblock in kf_root.controlled_blocks:
            # nb: this yielded just an empty bytestring
            # nodename = controlledblock.get_node_name()
            kfc = controlledblock.controller
            bone_name = armature.get_bone_name_for_blender(
                controlledblock.target_name)
            if bone_name in bind_data:
                niBone_bind_scale, niBone_bind_rot_inv, niBone_bind_trans = bind_data[
                    bone_name]
                self.armature_animation.import_keyframe_controller(
                    kfc, b_armature_obj, bone_name, niBone_bind_scale,
                    niBone_bind_rot_inv, niBone_bind_trans)
    def import_material_uv_controller(self, b_material, n_geom):
        """Import UV controller data."""
        # search for the block
        n_ctrl = nif_utils.find_controller(n_geom, NifFormat.NiUVController)
        if not (n_ctrl and n_ctrl.data):
            return
        NifLog.info("Importing UV controller")

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

        dtypes = ("offset", 0), ("offset", 1), ("scale", 0), ("scale", 1)
        for n_uvgroup, (data_path, array_ind) in zip(n_ctrl.data.uv_groups,
                                                     dtypes):
            if n_uvgroup.keys:
                interp = self.nif_import.animationhelper.get_b_interp_from_n_interp(
                    n_uvgroup.interpolation)
                #in blender, UV offset is stored per texture slot
                #so we have to repeat the import for each used tex slot
                for i, texture_slot in enumerate(b_material.texture_slots):
                    if texture_slot:
                        fcurves = create_fcurves(
                            b_mat_action,
                            "texture_slots[" + str(i) + "]." + data_path,
                            (array_ind, ))
                        for key in n_uvgroup.keys:
                            if "offset" in data_path:
                                self.nif_import.animationhelper.add_key(
                                    fcurves, key.time, (-key.value, ), interp)
                            else:
                                self.nif_import.animationhelper.add_key(
                                    fcurves, key.time, (key.value, ), interp)
                        self.nif_import.animationhelper.set_extrapolation(
                            n_ctrl.flags, fcurves)
    def get_extend_from_flags(self, flags):
        if flags & 6 == 4:  # 0b100
            return "CONST"
        elif flags & 6 == 0:  # 0b000
            return "CYCLIC"

        NifLog.warn("Unsupported cycle mode in nif, using clamped.")
        return "CONST"
Example #6
0
    def set_alpha(self, b_mat, ShaderProperty, n_alpha_prop):
        NifLog.debug("Alpha prop detected")
        b_mat.use_transparency = True
        if hasattr(ShaderProperty, 'alpha'):
            b_mat.alpha = (1 - ShaderProperty.alpha)
        else:
            b_mat.alpha = 0
        b_mat.transparency_method = 'Z_TRANSPARENCY'  # enable z-buffered transparency
        b_mat.offset_z = n_alpha_prop.threshold  # Transparency threshold
        b_mat.niftools_alpha.alphaflag = n_alpha_prop.flags

        return b_mat
    def get_n_apply_mode_from_b_blend_type(self, 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
 def get_b_interp_from_n_interp(self, n_ipol):
     if n_ipol == NifFormat.KeyType.LINEAR_KEY:
         return "LINEAR"
     elif n_ipol == NifFormat.KeyType.QUADRATIC_KEY:
         return "BEZIER"
     elif n_ipol == 0:
         # guessing, not documented in nif.xml
         return "CONSTANT"
     NifLog.warn(
         "Unsupported interpolation mode ({0}) in nif, using quadratic/bezier."
         .format(n_ipol))
     return "BEZIER"
    def get_n_interp_from_b_interp(self, 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(
            "Unsupported interpolation mode ({0}) in blend, using quadratic/bezier."
            .format(b_ipol))
        return NifFormat.KeyType.QUADRATIC_KEY
Example #10
0
    def export_tex_desc(self, texdesc=None, uvlayers=None, b_mat_texslot=None):
        """Helper function for export_texturing_property to export each texture
        slot."""
        try:
            texdesc.uv_set = uvlayers.index(
                b_mat_texslot.uv_layer) if b_mat_texslot.uv_layer else 0
        except ValueError:  # mtex.uv_layer not in uvlayers list
            NifLog.warn(
                "Bad uv layer name '{0}' in texture '{1}'. Using first uv layer"
                .format(b_mat_texslot.uv_layer, b_mat_texslot.texture.name))
            texdesc.uv_set = 0  # assume 0 is active layer

        texdesc.source = self.export_source_texture(b_mat_texslot.texture)
Example #11
0
    def import_texture_source(self, source):
        """Convert a NiSourceTexture block, or simply a path string,
        to a Blender Texture object, return the Texture object and
        stores it in the self.nif_import.dict_textures dictionary to avoid future
        duplicate imports.
        """
        # if the source block is not linked then return None
        if not source:
            return None

        # calculate the texture hash key
        texture_hash = self.get_texture_hash(source)

        try:
            # look up the texture in the dictionary of imported textures
            # and return it if found
            return self.nif_import.dict_textures[texture_hash]
        except KeyError:
            pass

        b_image = None

        if (isinstance(source, NifFormat.NiSourceTexture)
                and not source.use_external):
            fn, b_image = self.import_embedded_texture_source(source)
        else:
            fn, b_image = self.import_source(source)

        # create a stub image if the image could not be loaded

        b_text_name = os.path.basename(fn)
        if not b_image:
            NifLog.warn(
                "Texture '{0}' not found or not supported and no alternate available"
                .format(fn))
            b_image = bpy.data.images.new(name=b_text_name,
                                          width=1,
                                          height=1,
                                          alpha=False)
            b_image.filepath = fn

        # create a texture
        b_texture = bpy.data.textures.new(name=b_text_name, type='IMAGE')
        b_texture.image = b_image
        b_texture.use_interpolation = True
        b_texture.use_mipmap = True

        # save texture to avoid duplicate imports, and return it
        self.nif_import.dict_textures[texture_hash] = b_texture
        return b_texture
    def import_shader_by_type(self, b_mat, shader_tex_desc,
                              extra_shader_index):
        if extra_shader_index == 0:
            # EnvironmentMapIndex
            if shader_tex_desc.texture_data.source.file_name.lower(
            ).startswith("rrt_engine_env_map"):
                # sid meier's railroads: env map generated by engine
                # we can skip this
                NifLog.info(
                    "Skipping environment map texture. Env Map is generated by Engine"
                )
            envTexDesc = shader_tex_desc.texture_data
            self.has_envtex = True
            self.env_map = self.import_image_texture(b_mat, envTexDesc)

        elif extra_shader_index == 1:
            # NormalMapIndex
            bumpTexDesc = shader_tex_desc.texture_data
            self.has_bumptex = True
            self.bump_map = self.import_image_texture(b_mat, bumpTexDesc)

        elif extra_shader_index == 2:
            # SpecularIntensityIndex
            glossTexDesc = shader_tex_desc.texture_data
            self.has_glosstex = True
            self.gloss_map = self.import_image_texture(b_mat, glossTexDesc)

        elif extra_shader_index == 3:
            # EnvironmentIntensityIndex (this is reflection)
            refTexDesc = shader_tex_desc.texture_data
            self.has_reftex = True
            self.reflection_map = self.reflection_map = self.import_image_texture(
                b_mat, refTexDesc)

        elif extra_shader_index == 4:
            # LightCubeMapIndex
            if shader_tex_desc.texture_data.source.file_name.lower(
            ).startswith("rrt_cube_light_map"):
                # sid meier's railroads: light map generated by engine
                # we can skip this
                NifLog.info("Ignoring Env Map as generated by Engine")
            NifLog.warn("Skipping light cube texture.")

        elif extra_shader_index == 5:
            # ShadowTextureIndex
            NifLog.warn("Skipping shadow texture.")

        else:
            NifLog.warn("Unknown texture type found in extra_shader_index")
    def __init__(self, operator):
        """Common initialization functions for executing the import/export operators: """

        NifOp.init(operator)

        # print scripts info
        from . import bl_info
        niftools_ver = (".".join(str(i) for i in bl_info["version"]))

        NifLog.info(
            "Executing - Niftools : Blender Nif Plugin v{0} (running on Blender {1}, PyFFI {2})"
            .format(niftools_ver, bpy.app.version_string, pyffi.__version__))

        # find and store this list now of selected objects as creating new objects adds them to the selection list
        self.selected_objects = bpy.context.selected_objects[:]
    def import_bhk_shape(self, bhkshape):
        """Imports any supported collision shape as list of blender meshes."""

        if self.nif_import.data._user_version_value_._value == 12:
            if self.nif_import.data._user_version_2_value_._value == 83:
                self.HAVOK_SCALE = self.nif_import.HAVOK_SCALE * 10
            else:
                self.HAVOK_SCALE = self.nif_import.HAVOK_SCALE

        if isinstance(bhkshape, NifFormat.bhkTransformShape):
            return self.import_bhktransform(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkRigidBody):
            return self.import_bhkridgidbody(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkBoxShape):
            return self.import_bhkbox_shape(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkSphereShape):
            return self.import_bhksphere_shape(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkCapsuleShape):
            return self.import_bhkcapsule_shape(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkConvexVerticesShape):
            return self.import_bhkconvex_vertices_shape(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkPackedNiTriStripsShape):
            return self.import_bhkpackednitristrips_shape(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkNiTriStripsShape):
            self.havok_mat = bhkshape.material
            return reduce(operator.add, (self.import_bhk_shape(strips)
                                         for strips in bhkshape.strips_data))

        elif isinstance(bhkshape, NifFormat.NiTriStripsData):
            return self.import_nitristrips(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkMoppBvTreeShape):
            return self.import_bhk_shape(bhkshape.shape)

        elif isinstance(bhkshape, NifFormat.bhkListShape):
            return reduce(operator.add, (self.import_bhk_shape(subshape)
                                         for subshape in bhkshape.sub_shapes))

        NifLog.warn("Unsupported bhk shape {0}".format(
            bhkshape.__class__.__name__))
        return []
 def get_b_blend_type_from_n_apply_mode(self, n_apply_mode):
     # TODO: - Check out n_apply_modes
     if n_apply_mode == NifFormat.ApplyMode.APPLY_MODULATE:
         return "MIX"
     elif n_apply_mode == NifFormat.ApplyMode.APPLY_REPLACE:
         return "COLOR"
     elif n_apply_mode == NifFormat.ApplyMode.APPLY_DECAL:
         return "OVERLAY"
     elif n_apply_mode == NifFormat.ApplyMode.APPLY_HILIGHT:
         return "LIGHTEN"
     elif n_apply_mode == NifFormat.ApplyMode.APPLY_HILIGHT2:  # used by Oblivion for parallax
         return "MULTIPLY"
     else:
         NifLog.warn(
             "Unknown apply mode (%i) in material, using blend type 'MIX'".
             format(n_apply_mode))
         return "MIX"
    def import_bone_animation(self, n_block, b_armature_obj, bone_name):
        """
        Imports an animation contained in the NIF itself.
        """
        if NifOp.props.animation:
            NifLog.debug('Importing animation for bone %s'.format(bone_name))

            bone_bm = nif_utils.import_matrix(n_block)  # base pose
            niBone_bind_scale, niBone_bind_rot, niBone_bind_trans = nif_utils.decompose_srt(
                bone_bm)
            niBone_bind_rot_inv = niBone_bind_rot.to_4x4().inverted()
            kfc = nif_utils.find_controller(n_block,
                                            NifFormat.NiKeyframeController)

            self.import_keyframe_controller(kfc, b_armature_obj, bone_name,
                                            niBone_bind_scale,
                                            niBone_bind_rot_inv,
                                            niBone_bind_trans)
 def set_frames_per_second(self, roots):
     """Scan all blocks and set a reasonable number for FPS to this class and the scene."""
     # find all key times
     key_times = []
     for root in roots:
         for kfd in root.tree(block_type=NifFormat.NiKeyframeData):
             key_times.extend(key.time for key in kfd.translations.keys)
             key_times.extend(key.time for key in kfd.scales.keys)
             key_times.extend(key.time for key in kfd.quaternion_keys)
             key_times.extend(key.time for key in kfd.xyz_rotations[0].keys)
             key_times.extend(key.time for key in kfd.xyz_rotations[1].keys)
             key_times.extend(key.time for key in kfd.xyz_rotations[2].keys)
         for kfi in root.tree(block_type=NifFormat.NiBSplineInterpolator):
             if not kfi.basis_data:
                 # skip bsplines without basis data (eg bowidle.kf in
                 # Oblivion)
                 continue
             key_times.extend(
                 point * (kfi.stop_time - kfi.start_time) /
                 (kfi.basis_data.num_control_points - 2)
                 for point in range(kfi.basis_data.num_control_points - 2))
         for uvdata in root.tree(block_type=NifFormat.NiUVData):
             for uvgroup in uvdata.uv_groups:
                 key_times.extend(key.time for key in uvgroup.keys)
     fps = self.fps
     # not animated, return a reasonable default
     if not key_times:
         return
     # calculate FPS
     key_times = sorted(set(key_times))
     lowest_diff = sum(
         abs(int(time * fps + 0.5) - (time * fps)) for time in key_times)
     # for test_fps in range(1,120): #disabled, used for testing
     for test_fps in [20, 24, 25, 35]:
         diff = sum(
             abs(int(time * test_fps + 0.5) - (time * test_fps))
             for time in key_times)
         if diff < lowest_diff:
             lowest_diff = diff
             fps = test_fps
     NifLog.info("Animation estimated at %i frames per second." % fps)
     self.fps = fps
     bpy.context.scene.render.fps = fps
     bpy.context.scene.frame_set(0)
Example #18
0
    def export_source_texture(self, texture=None, filename=None):
        """Export a NiSourceTexture.

        :param texture: The texture object in blender to be exported.
        :param filename: The full or relative path to the texture file
            (this argument is used when exporting NiFlipControllers
            and when exporting default shader slots that have no use in
            being imported into Blender).
        :return: The exported NiSourceTexture block.
        """

        # create NiSourceTexture
        srctex = NifFormat.NiSourceTexture()
        srctex.use_external = True
        if not filename is None:
            # preset filename
            srctex.file_name = filename
        elif not texture is None:
            srctex.file_name = self.export_texture_filename(texture)
        else:
            # this probably should not happen
            NifLog.warn(
                "Exporting source texture without texture or filename (bug?).")

        # fill in default values (TODO: can we use 6 for everything?)
        if bpy.context.scene.niftools_scene.nif_version >= 0x0A000100:
            srctex.pixel_layout = 6
        else:
            srctex.pixel_layout = 5
        srctex.use_mipmaps = 1
        srctex.alpha_format = 3
        srctex.unknown_byte = 1

        # search for duplicate
        for block in self.nif_export.nif_export.dict_blocks:
            if isinstance(block, NifFormat.NiSourceTexture) and block.get_hash(
            ) == srctex.get_hash():
                return block

        # no identical source texture found, so use and register
        # the new one
        return self.nif_export.nif_export.objecthelper.register_block(
            srctex, texture)
    def import_object_vis_controller(self, n_node, b_obj):
        """Import vis controller for blender object."""

        n_vis_ctrl = nif_utils.find_controller(n_node,
                                               NifFormat.NiVisController)
        if not (n_vis_ctrl and n_vis_ctrl.data):
            return
        NifLog.info("Importing vis controller")

        b_obj_action = self.nif_import.animationhelper.create_action(
            b_obj, b_obj.name + "-Anim")

        fcurves = create_fcurves(b_obj_action, "hide", (0, ))
        for key in n_vis_ctrl.data.keys:
            self.nif_import.animationhelper.add_key(fcurves, key.time,
                                                    (key.value, ), "CONSTANT")
        #get extrapolation from flags and set it to fcurves
        self.nif_import.animationhelper.set_extrapolation(
            n_vis_ctrl.flags, fcurves)
Example #20
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))
    def import_material_alpha_controller(self, b_material, n_geom):
        # find alpha controller
        n_matprop = nif_utils.find_property(n_geom,
                                            NifFormat.NiMaterialProperty)
        if not n_matprop:
            return
        n_alphactrl = nif_utils.find_controller(n_matprop,
                                                NifFormat.NiAlphaController)
        if not (n_alphactrl and n_alphactrl.data):
            return
        NifLog.info("Importing alpha controller")

        b_mat_action = self.nif_import.animationhelper.create_action(
            b_material, "MaterialAction")
        fcurves = create_fcurves(b_mat_action, "alpha", (0, ))
        interp = self.nif_import.animationhelper.get_b_interp_from_n_interp(
            n_alphactrl.data.data.interpolation)
        self.nif_import.animationhelper.set_extrapolation(
            n_alphactrl.flags, fcurves)
        for key in n_alphactrl.data.data.keys:
            self.nif_import.animationhelper.add_key(fcurves, key.time,
                                                    (key.value, ), interp)
Example #22
0
 def complete_bone_tree(self, bone, skelroot):
     """Make sure that the bones actually form a tree all the way
     down to the armature node. Call this function on all bones of
     a skin instance.
     """
     # we must already have marked this one as a bone
     assert skelroot in self.nif_import.dict_armatures  # debug
     assert bone in self.nif_import.dict_armatures[skelroot]  # debug
     # get the node parent, this should be marked as an armature or as a bone
     boneparent = bone._parent
     if boneparent != skelroot:
         # parent is not the skeleton root
         if boneparent not in self.nif_import.dict_armatures[skelroot]:
             # neither is it marked as a bone: so mark the parent as a bone
             self.nif_import.dict_armatures[skelroot].append(boneparent)
             # store the coordinates for realignement autodetection
             NifLog.debug("'{0}' is a bone of armature '{1}'".format(
                 boneparent.name, skelroot.name))
         # now the parent is marked as a bone
         # recursion: complete the bone tree,
         # this time starting from the parent bone
         self.complete_bone_tree(boneparent, skelroot)
Example #23
0
def decompose_srt(matrix):
    """Decompose Blender transform matrix as a scale, rotation matrix, and
    translation vector."""

    # get matrix components
    trans_vec, rot_quat, scale_vec = matrix.decompose()

    #obtain a combined scale and rotation matrix to test determinate
    rotmat = rot_quat.to_matrix()
    scalemat = mathutils.Matrix(
        ((scale_vec[0], 0.0, 0.0), (0.0, scale_vec[1], 0.0), (0.0, 0.0,
                                                              scale_vec[2])))
    scale_rot = scalemat * rotmat

    # and fix their sign
    if (scale_rot.determinant() < 0): scale_vec.negate()
    # only uniform scaling
    # allow rather large error to accomodate some nifs
    if abs(scale_vec[0] - scale_vec[1]) + abs(scale_vec[1] -
                                              scale_vec[2]) > 0.02:
        NifLog.warn("Non-uniform scaling not supported." +
                    " Workaround: apply size and rotation (CTRL-A).")
    return [scale_vec[0], rotmat, trans_vec]
Example #24
0
    def import_embedded_texture_source(self, source):
        fn = None

        # find a file name (but avoid overwriting)
        n = 0
        while True:
            fn = "image%03i.dds" % n
            tex = os.path.join(os.path.dirname(NifOp.props.filepath), fn)
            if not os.path.exists(tex):
                break
            n += 1
        if self.nif_import.IMPORT_EXPORTEMBEDDEDTEXTURES:
            # save embedded texture as dds file
            stream = open(tex, "wb")
            try:
                NifLog.info("Saving embedded texture as {0}".format(tex))
                source.pixel_data.save_as_dds(stream)
            except ValueError:
                # value error means that the pixel format is not supported
                b_image = None
            else:
                # saving dds succeeded so load the file
                b_image = bpy.ops.image.open(tex)
                # Blender will return an image object even if the
                # file format is not supported,
                # so to check if the image is actually loaded an error
                # is forced via "b_image.size"
                try:
                    b_image.size
                except:  # RuntimeError: couldn't load image data in Blender
                    b_image = None  # not supported, delete image object
            finally:
                stream.close()
        else:
            b_image = None
        return [fn, b_image]
Example #25
0
    def load_kf(file_path):
        """Loads a Kf file from the given path"""
        NifLog.info("Loading {0}".format(file_path))
        
        kf_file = NifFormat.Data()

        # open keyframe file for binary reading
        with open(file_path, "rb") as kf_stream:
            # check if nif file is valid
            kf_file.inspect_version_only(kf_stream)
            if kf_file.version >= 0:
                # it is valid, so read the file
                NifLog.info("KF file version: {0}".format(kf_file.version, "x"))
                NifLog.info("Reading keyframe file")
                kf_file.read(kf_stream)
            elif kf_file.version == -1:
                raise NifError("Unsupported KF version.")
            else:                    
                raise NifError("Not a KF file.")
            
        return kf_file    
Example #26
0
    def load_nif(file_path):
        """Loads a nif from the given file path"""
        NifLog.info("Importing {0}".format(file_path))

        nif_data = NifFormat.Data()

        # open file for binary reading
        with open(file_path, "rb") as nif_stream:
            # check if nif file is valid
            nif_data.inspect_version_only(nif_stream)
            if nif_data.version >= 0:
                # it is valid, so read the file
                NifLog.info("NIF file version: {0}".format(
                    nif_data.version, "x"))
                NifLog.info("Reading file")
                nif_data.read(nif_stream)
            elif nif_data.version == -1:
                raise NifError("Unsupported NIF version.")
            else:
                raise NifError("Not a NIF file.")

        return nif_data
Example #27
0
    def load_egm(file_path):
        """Loads an egm file from the given path"""
        NifLog.info("Loading {0}".format(file_path))

        egm_file = EgmFormat.Data()

        # open keyframe file for binary reading
        with open(file_path, "rb") as egm_stream:
            # check if nif file is valid
            egm_file.inspect_version_only(egm_stream)
            if egm_file.version >= 0:
                # it is valid, so read the file
                NifLog.info("EGM file version: {0}".format(
                    egm_file.version, "x"))
                NifLog.info("Reading FaceGen egm file")
                egm_file.read(egm_stream)
            elif egm_file.version == -1:
                raise NifError("Unsupported EGM version.")
            else:
                raise NifError("Not a EGM file.")

        return egm_file
    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
Example #29
0
    def import_constraint(self, hkbody):
        """Imports a bone havok constraint as Blender object constraint."""
        assert (isinstance(hkbody, NifFormat.bhkRigidBody))

        # check for constraints
        if not hkbody.constraints:
            return

        # find objects
        if len(self.nif_import.dict_havok_objects[hkbody]) != 1:
            NifLog.warn(
                "Rigid body with no or multiple shapes, constraints skipped")
            return

        b_hkobj = self.nif_import.dict_havok_objects[hkbody][0]

        NifLog.info("Importing constraints for %s" % b_hkobj.name)

        # now import all constraints
        for hkconstraint in hkbody.constraints:

            # check constraint entities
            if not hkconstraint.num_entities == 2:
                NifLog.warn("Constraint with more than 2 entities, skipped")
                continue
            if not hkconstraint.entities[0] is hkbody:
                NifLog.warn("First constraint entity not self, skipped")
                continue
            if not hkconstraint.entities[
                    1] in self.nif_import.dict_havok_objects:
                NifLog.warn("Second constraint entity not imported, skipped")
                continue

            # get constraint descriptor
            if isinstance(hkconstraint, NifFormat.bhkRagdollConstraint):
                hkdescriptor = hkconstraint.ragdoll
                b_hkobj.rigid_body.enabled = True
            elif isinstance(hkconstraint, NifFormat.bhkLimitedHingeConstraint):
                hkdescriptor = hkconstraint.limited_hinge
                b_hkobj.rigid_body.enabled = True
            elif isinstance(hkconstraint, NifFormat.bhkHingeConstraint):
                hkdescriptor = hkconstraint.hinge
                b_hkobj.rigid_body.enabled = True
            elif isinstance(hkconstraint, NifFormat.bhkMalleableConstraint):
                if hkconstraint.type == 7:
                    hkdescriptor = hkconstraint.ragdoll
                    b_hkobj.rigid_body.enabled = False
                elif hkconstraint.type == 2:
                    hkdescriptor = hkconstraint.limited_hinge
                    b_hkobj.rigid_body.enabled = False
                else:
                    NifLog.warn("Unknown malleable type ({0}), skipped".format(
                        str(hkconstraint.type)))
                # extra malleable constraint settings
                ### damping parameters not yet in Blender Python API
                ### tau (force between bodies) not supported by Blender
            else:
                NifLog.warn("Unknown constraint type ({0}), skipped".format(
                    hkconstraint.__class__.__name__))
                continue

            # add the constraint as a rigid body joint
            b_constr = b_hkobj.constraints.new('RIGID_BODY_JOINT')
            b_constr.name = b_hkobj.name
            b_constr.show_pivot = True

            # note: rigidbodyjoint parameters (from Constraint.c)
            # CONSTR_RB_AXX 0.0
            # CONSTR_RB_AXY 0.0
            # CONSTR_RB_AXZ 0.0
            # CONSTR_RB_EXTRAFZ 0.0
            # CONSTR_RB_MAXLIMIT0 0.0
            # CONSTR_RB_MAXLIMIT1 0.0
            # CONSTR_RB_MAXLIMIT2 0.0
            # CONSTR_RB_MAXLIMIT3 0.0
            # CONSTR_RB_MAXLIMIT4 0.0
            # CONSTR_RB_MAXLIMIT5 0.0
            # CONSTR_RB_MINLIMIT0 0.0
            # CONSTR_RB_MINLIMIT1 0.0
            # CONSTR_RB_MINLIMIT2 0.0
            # CONSTR_RB_MINLIMIT3 0.0
            # CONSTR_RB_MINLIMIT4 0.0
            # CONSTR_RB_MINLIMIT5 0.0
            # CONSTR_RB_PIVX 0.0
            # CONSTR_RB_PIVY 0.0
            # CONSTR_RB_PIVZ 0.0
            # CONSTR_RB_TYPE 12
            # LIMIT 63
            # PARSIZEY 63
            # TARGET [Object "capsule.002"]

            # limit 3, 4, 5 correspond to angular limits along x, y and z
            # and are measured in degrees

            # pivx/y/z is the pivot point

            # set constraint target
            b_constr.target = \
                self.nif_import.dict_havok_objects[hkconstraint.entities[1]][0]
            # set rigid body type (generic)
            b_constr.pivot_type = 'GENERIC_6_DOF'
            # limiting parameters (limit everything)
            b_constr.use_angular_limit_x = True
            b_constr.use_angular_limit_y = True
            b_constr.use_angular_limit_z = True

            # get pivot point
            pivot = mathutils.Vector(
                (hkdescriptor.pivot_a.x * self.HAVOK_SCALE,
                 hkdescriptor.pivot_a.y * self.HAVOK_SCALE,
                 hkdescriptor.pivot_a.z * self.HAVOK_SCALE))

            # get z- and x-axes of the constraint
            # (also see export_nif.py NifImport.export_constraints)
            if isinstance(hkdescriptor, NifFormat.RagdollDescriptor):
                b_constr.pivot_type = 'CONE_TWIST'
                # for ragdoll, take z to be the twist axis (central axis of the
                # cone, that is)
                axis_z = mathutils.Vector(
                    (hkdescriptor.twist_a.x, hkdescriptor.twist_a.y,
                     hkdescriptor.twist_a.z))
                # for ragdoll, let x be the plane vector
                axis_x = mathutils.Vector(
                    (hkdescriptor.plane_a.x, hkdescriptor.plane_a.y,
                     hkdescriptor.plane_a.z))
                # set the angle limits
                # (see http://niftools.sourceforge.net/wiki/Oblivion/Bhk_Objects/Ragdoll_Constraint
                # for a nice picture explaining this)
                b_constr.limit_angle_min_x = \
                    hkdescriptor.plane_min_angle
                b_constr.limit_angle_max_x = \
                    hkdescriptor.plane_max_angle

                b_constr.limit_angle_min_y = \
                    -hkdescriptor.cone_max_angle
                b_constr.limit_angle_max_y = \
                    hkdescriptor.cone_max_angle

                b_constr.limit_angle_min_z = \
                    hkdescriptor.twist_min_angle
                b_constr.limit_angle_max_z = \
                    hkdescriptor.twist_max_angle

                b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction

            elif isinstance(hkdescriptor, NifFormat.LimitedHingeDescriptor):
                # for hinge, y is the vector on the plane of rotation defining
                # the zero angle
                axis_y = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_1.x,
                                           hkdescriptor.perp_2_axle_in_a_1.y,
                                           hkdescriptor.perp_2_axle_in_a_1.z))
                # for hinge, take x to be the the axis of rotation
                # (this corresponds with Blender's convention for hinges)
                axis_x = mathutils.Vector(
                    (hkdescriptor.axle_a.x, hkdescriptor.axle_a.y,
                     hkdescriptor.axle_a.z))
                # for hinge, z is the vector on the plane of rotation defining
                # the positive direction of rotation
                axis_z = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_2.x,
                                           hkdescriptor.perp_2_axle_in_a_2.y,
                                           hkdescriptor.perp_2_axle_in_a_2.z))
                # they should form a orthogonal basis
                if (mathutils.Vector.cross(axis_x, axis_y) -
                        axis_z).length > 0.01:
                    # either not orthogonal, or negative orientation
                    if (mathutils.Vector.cross(-axis_x, axis_y) -
                            axis_z).length > 0.01:
                        NifLog.warn(
                            "Axes are not orthogonal in {0}; Arbitrary orientation has been chosen"
                            .format(hkdescriptor.__class__.__name__))
                        axis_z = mathutils.Vector.cross(axis_x, axis_y)
                    else:
                        # fix orientation
                        NifLog.warn(
                            "X axis flipped in {0} to fix orientation".format(
                                hkdescriptor.__class__.__name__))
                        axis_x = -axis_x
                # getting properties with no blender constraint
                # equivalent and setting as obj properties
                b_constr.limit_angle_max_x = hkdescriptor.max_angle
                b_constr.limit_angle_min_x = hkdescriptor.min_angle
                b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction

                if hasattr(hkconstraint, "tau"):
                    b_hkobj.niftools_constraint.tau = hkconstraint.tau
                    b_hkobj.niftools_constraint.damping = hkconstraint.damping

            elif isinstance(hkdescriptor, NifFormat.HingeDescriptor):
                # for hinge, y is the vector on the plane of rotation defining
                # the zero angle
                axis_y = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_1.x,
                                           hkdescriptor.perp_2_axle_in_a_1.y,
                                           hkdescriptor.perp_2_axle_in_a_1.z))
                # for hinge, z is the vector on the plane of rotation defining
                # the positive direction of rotation
                axis_z = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_2.x,
                                           hkdescriptor.perp_2_axle_in_a_2.y,
                                           hkdescriptor.perp_2_axle_in_a_2.z))
                # take x to be the the axis of rotation
                # (this corresponds with Blender's convention for hinges)
                axis_x = mathutils.Vector.cross(axis_y, axis_z)
                b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction
            else:
                raise ValueError("unknown descriptor %s" %
                                 hkdescriptor.__class__.__name__)

            # transform pivot point and constraint matrix into object
            # coordinates
            # (also see export_nif.py NifImport.export_constraints)

            # the pivot point v is in hkbody coordinates
            # however blender expects it in object coordinates, v'
            # v * R * B = v' * O * T * B'
            # with R = rigid body transform (usually unit tf)
            # B = nif bone matrix
            # O = blender object transform
            # T = bone tail matrix (translation in Y direction)
            # B' = blender bone matrix
            # so we need to cancel out the object transformation by
            # v' = v * R * B * B'^{-1} * T^{-1} * O^{-1}

            # the local rotation L at the pivot point must be such that
            # (axis_z + v) * R * B = ([0 0 1] * L + v') * O * T * B'
            # so (taking the rotation parts of all matrices!!!)
            # [0 0 1] * L = axis_z * R * B * B'^{-1} * T^{-1} * O^{-1}
            # and similarly
            # [1 0 0] * L = axis_x * R * B * B'^{-1} * T^{-1} * O^{-1}
            # hence these give us the first and last row of L
            # which is exactly enough to provide the euler angles

            # multiply with rigid body transform
            if isinstance(hkbody, NifFormat.bhkRigidBodyT):
                # set rotation
                transform = mathutils.Quaternion(
                    (hkbody.rotation.w, hkbody.rotation.x, hkbody.rotation.y,
                     hkbody.rotation.z)).to_matrix()
                transform.resize_4x4()
                # set translation
                transform[0][3] = hkbody.translation.x * self.HAVOK_SCALE
                transform[1][3] = hkbody.translation.y * self.HAVOK_SCALE
                transform[2][3] = hkbody.translation.z * self.HAVOK_SCALE
                # apply transform
                pivot = pivot * transform
                transform = transform.to_3x3()
                axis_z = axis_z * transform
                axis_x = axis_x * transform

            # TODO: update this to use the new bone system
            # # next, cancel out bone matrix correction
            # # note that B' = X * B with X = self.nif_import.dict_bones_extra_matrix[B]
            # # so multiply with the inverse of X
            # for niBone in self.nif_import.dict_bones_extra_matrix:
            # if niBone.collision_object \
            # and niBone.collision_object.body is hkbody:
            # transform = mathutils.Matrix(
            # self.nif_import.dict_bones_extra_matrix[niBone])
            # transform.invert()
            # pivot = pivot * transform
            # transform = transform.to_3x3()
            # axis_z = axis_z * transform
            # axis_x = axis_x * transform
            # break

            # cancel out bone tail translation
            if b_hkobj.parent_bone:
                pivot[1] -= b_hkobj.parent.data.bones[
                    b_hkobj.parent_bone].length

            # cancel out object transform
            transform = mathutils.Matrix(b_hkobj.matrix_local)
            transform.invert()
            pivot = pivot * transform
            transform = transform.to_3x3()
            axis_z = axis_z * transform
            axis_x = axis_x * transform

            # set pivot point
            b_constr.pivot_x = pivot[0]
            b_constr.pivot_y = pivot[1]
            b_constr.pivot_z = pivot[2]

            # set euler angles
            constr_matrix = mathutils.Matrix(
                (axis_x, mathutils.Vector.cross(axis_z, axis_x), axis_z))
            constr_euler = constr_matrix.to_euler()
            b_constr.axis_x = constr_euler.x
            b_constr.axis_y = constr_euler.y
            b_constr.axis_z = constr_euler.z
            # DEBUG
            assert ((axis_x - mathutils.Vector(
                (1, 0, 0)) * constr_matrix).length < 0.0001)
            assert ((axis_z - mathutils.Vector(
                (0, 0, 1)) * constr_matrix).length < 0.0001)

            # the generic rigid body type is very buggy... so for simulation
            # purposes let's transform it into ball and hinge
            if isinstance(hkdescriptor, NifFormat.RagdollDescriptor):
                # cone_twist
                b_constr.pivot_type = 'CONE_TWIST'
            elif isinstance(
                    hkdescriptor,
                (NifFormat.LimitedHingeDescriptor, NifFormat.HingeDescriptor)):
                # (limited) hinge
                b_constr.pivot_type = 'HINGE'
            else:
                raise ValueError("unknown descriptor %s" %
                                 hkdescriptor.__class__.__name__)
Example #30
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)