<commit_before>#!BPY

""" Registration info for Blender menus:
Name: 'NetImmerse/Gamebryo (.nif & .kf)...'
Blender: 241
Group: 'Import'
Tooltip: 'Import NIF File Format (.nif & .kf)'
"""

## in the future:
#Submenu: 'Import NIF File...' nif
#Submenu: 'Import KFM File...' kfm
#Submenu: 'Import KF File...' kf
# ...

__author__ = "The NifTools team, http://niftools.sourceforge.net/"
__url__ = ("blender", "elysiun", "http://niftools.sourceforge.net/")
__version__ = "1.5.4"
__bpydoc__ = """\
This script imports Netimmerse and Gamebryo .NIF files to Blender.

Supported:<br>
    Bones.<br>
    Vertex weight skinning.<br>
    Animation groups ("Anim" text buffer).<br>
    Texture flipping (via text buffer named to the texture).<br>
    Packed textures ("packed" button next to Reload in the Image tab).<br>
    Hidden meshes (object drawtype "Wire")<br>
    Geometry morphing (vertex keys)<br>

Missing:<br>
    Particle effects, cameras, lights.<br>

Known issues:<br>
    Ambient and emit colors are obtained by multiplication with the diffuse
color.<br>

Config options (Scripts->System->Scripts Config Editor->Import):<br>
    textures dir: Semi-colon separated list of texture directories.<br>
    import dir: Default import directory.<br>
    seams import: Enable to avoid cracks in UV seams. Disable if importing
large NIF files takes too long.<br>
"""

# nif_import.py version 1.5.4
# --------------------------------------------------------------------------
# ***** BEGIN LICENSE BLOCK *****
# 
# BSD License
# 
# Copyright (c) 2005, NIF File Format Library and Tools
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the NIF File Format Library and Tools project may not be
#    used to endorse or promote products derived from this software
#    without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ***** END LICENCE BLOCK *****
# Note: Versions of this script previous to 1.0.6 were released under the GPL license
# --------------------------------------------------------------------------
#
# Credits:
# Portions of this programs are (were) derived (through the old tested method of cut'n paste)
# from the obj import script obj_import.py: OBJ Import v0.9 by <NAME> (AKA Ideasman)
# (No more. I rewrote the lot. Nevertheless I wouldn't have been able to start this without Ideasman's
# script to read from!)
# Binary conversion functions are courtesy of SJH. Couldn't find the full name, and couldn't find any
# license info, I got the code for these from http://projects.blender.org/pipermail/bf-python/2004-July/001676.html
# The file reading strategy was 'inspired' by the NifToPoly script included with the 
# DAOC mapper, which used to be available at http://www.randomly.org/projects/mapper/ and was written and 
# is copyright 2002 of <NAME>. His domain and e-mail address are however no longer reacheable.
# No part of the original code is included here, as I pretty much rewrote everything, hence this is the 
# only mention of the original copyright. An updated version of the script is included with the DAOC Mappergui
# application, available at http://nathrach.republicofnewhome.org/mappergui.html
#
# Thanks go to:
# <NAME> (AKA Ideasman, Cambo) for making code clear enough to be used as a learning resource.
#   Hey, this is my first ever python script!
# SJH for the binary conversion functions. Got the code off a forum somewhere, posted by Ideasman,
#   I suppose it's allright to use it
# <NAME> (AKA Taharez), for helping me a lot with the file format, and with some debugging even
#   though he doesn't 'do Python'
# <NAME> (AKA timmeh), for taking some of his time to help me get to terms with the way
#   the UV maps work in Blender
# Amorilia (don't know your name buddy), for bugfixes and testing.

import Blender, sys
from Blender import BGL
from Blender import Draw
from Blender.Mathutils import *


try:
    from niflib import *
except:
    err = """--------------------------
ERROR\nThis script requires the NIFLIB Python SWIG wrapper, niflib.py & _niflib.dll.
Make sure these files reside in your Python path or in your Blender scripts folder.
If you don't have them: http://niftools.sourceforge.net/
--------------------------"""
    print err
    Blender.Draw.PupMenu("ERROR%t|NIFLIB not found, check console for details")
    raise

# Attempt to load psyco to speed things up
#try:
#	import psyco
#	psyco.full()
#	print 'using psyco'
#except:
#	#print 'psyco is not present on this system'
#	pass

# dictionary of texture files, to reuse textures
TEXTURES = {}

# dictionary of materials, to reuse materials
MATERIALS = {}

# dictionary of names, to map NIF names to correct Blender names
NAMES = {}

# dictionary of bones, maps Blender bone name to NIF block
BONES = {}

# dictionary of bones, maps Blender bone name to matrix that maps the
# NIF bone matrix on the Blender bone matrix
# B' = X * B, where B' is the Blender bone matrix, and B is the NIF bone matrix
BONES_EXTRA_MATRIX = {}

# dictionary of bones that belong to a certain armature
# maps NIF armature name to list of NIF bone name
BONE_LIST = {}

# correction matrices list, the order is +X, +Y, +Z, -X, -Y, -Z
BONE_CORRECTION_MATRICES = (\
            Matrix([ 0.0,-1.0, 0.0],[ 1.0, 0.0, 0.0],[ 0.0, 0.0, 1.0]),\
            Matrix([ 1.0, 0.0, 0.0],[ 0.0, 1.0, 0.0],[ 0.0, 0.0, 1.0]),\
            Matrix([ 1.0, 0.0, 0.0],[ 0.0, 0.0, 1.0],[ 0.0,-1.0, 0.0]),\
            Matrix([ 0.0, 1.0, 0.0],[-1.0, 0.0, 0.0],[ 0.0, 0.0, 1.0]),\
            Matrix([-1.0, 0.0, 0.0],[ 0.0,-1.0, 0.0],[ 0.0, 0.0, 1.0]),\
            Matrix([ 1.0, 0.0, 0.0],[ 0.0, 0.0,-1.0],[ 0.0, 1.0, 0.0]))

# some variables

USE_GUI = 0 # BROKEN, don't set to 1, we will design a GUI for importer & exporter jointly
EPSILON = 0.005 # used for checking equality with floats, NOT STORED IN CONFIG
MSG_LEVEL = 2 # verbosity level

K_R2D = 3.14159265358979/180.0 # radians to degrees conversion constant
K_D2R = 180.0/3.14159265358979 # degrees to radians conversion constant

# 
# Process config files.
# 

# configuration default values
TEXTURES_DIR = 'C:\\Program Files\\Bethesda\\Morrowind\\Data Files\\Textures' # Morrowind: this will work on a standard installation
IMPORT_DIR = ''
SEAMS_IMPORT = True

# tooltips
tooltips = {
    'TEXTURES_DIR': "Texture directory.",
    'IMPORT_DIR': "Default import directory.",
    'SEAMS_IMPORT': "Import seams? Enable to avoid cracks in UV seams. Disable if importing large NIF files takes too long.",
}

# bounds
limits = {
}

# update registry
def update_registry():
    # populate a dict with current config values:
    d = {}
    d['TEXTURES_DIR'] = TEXTURES_DIR
    d['IMPORT_DIR'] = IMPORT_DIR
    d['SEAMS_IMPORT'] = SEAMS_IMPORT
    d['limits'] = limits
    d['tooltips'] = tooltips
    # store the key
    Blender.Registry.SetKey('nif_import', d, True)
    read_registry()

# Now we check if our key is available in the Registry or file system:
def read_registry():
    global TEXTURES_DIR, IMPORT_DIR, SEAMS_IMPORT
    regdict = Blender.Registry.GetKey('nif_import', True)
    # If this key already exists, update config variables with its values:
    if regdict:
        try:
            TEXTURES_DIR = regdict['TEXTURES_DIR'] 
            IMPORT_DIR = regdict['IMPORT_DIR']
            SEAMS_IMPORT = regdict['SEAMS_IMPORT']
            tmp_limits = regdict['limits']     # just checking if it's there
            tmp_tooltips = regdict['tooltips'] # just checking if it's there
        # if data was corrupted (or a new version of the script changed
        # (expanded, removed, renamed) the config vars and users may have
        # the old config file around):
        except: update_registry() # rewrite it
    else: # if the key doesn't exist yet, use our function to create it:
        update_registry()

read_registry()



# check export script config key for scale correction

SCALE_CORRECTION = 10.0 # same default value as in export script

rd = Blender.Registry.GetKey('nif_export', True)
if rd:
    try:
        SCALE_CORRECTION = rd['SCALE_CORRECTION']
    except: pass

# check General scripts config key for default behaviors

VERBOSE = True
CONFIRM_OVERWRITE = True

rd = Blender.Registry.GetKey('General', True)
if rd:
    try:
        VERBOSE = rd['verbose']
        CONFIRM_OVERWRITE = rd['confirm_overwrite']
    except: pass

# Little wrapper for debug messages
def msg(message='-', level=2):
    if VERBOSE:
        if level <= MSG_LEVEL:
            print message

#
# A simple custom exception class.
#
class NIFImportError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)


#
# Emulates the act of pressing the "home" key
#
def fit_view():
    Draw.Redraw(1)
    winid = Blender.Window.GetScreenInfo(Blender.Window.Types.VIEW3D)[0]['id']
    Blender.Window.SetKeyQualifiers(0)
    Blender.Window.QAdd(winid, Draw.HOMEKEY, 1)
    Blender.Window.QHandle(winid)
    Blender.Window.QAdd(winid, Draw.HOMEKEY, 0)
    Draw.Redraw(1)
    
#
# Main import function.
#
def import_nif(filename):
    Blender.Window.DrawProgressBar(0.0, "Initializing")
    # texture dirs
    global NIF_DIR, TEX_DIR
    NIF_DIR = Blender.sys.dirname(filename)
    idx = NIF_DIR.lower().find('meshes')
    if ( idx >= 0 ):
        TEX_DIR = NIF_DIR[:idx] + 'textures'
    else:
        TEX_DIR = None
    try: # catch NIFImportErrors
        # read the NIF file
        ver = CheckNifHeader(filename)
        if ( ver == VER_INVALID ):
            raise NIFImportError("Not a NIF file.")
        elif ( ver == VER_UNSUPPORTED ):
            raise NIFImportError("Unsupported NIF version.")
        Blender.Window.DrawProgressBar(0.33, "Reading file")
        root_block = ReadNifTree(filename)
        Blender.Window.DrawProgressBar(0.66, "Importing data")
        import_main(root_block)
    except NIFImportError, e: # in that case, we raise a menu instead of an exception
        Blender.Window.DrawProgressBar(1.0, "Import Failed")
        print 'NIFImportError: ' + e.value
        Blender.Draw.PupMenu('ERROR%t|' + e.value)
        return
    Blender.Window.DrawProgressBar(1.0, "Finished")



#
# Main import function.
#
def import_main(root_block):
    # scene info
    global b_scene
    b_scene = Blender.Scene.GetCurrent()
    # used to control the progress bar
    global block_count, blocks_read, read_progress
    block_count = NiObject_NumObjectsInMemory()
    read_progress = 0.0
    blocks_read = 0.0
    # preprocessing:
    # scale tree
    scale_tree(root_block, 1.0/SCALE_CORRECTION)
    # mark armature nodes and bones
    # and merge armatures that are bones of others armatures
    mark_armatures_bones(root_block)
    merge_armatures()
    if VERBOSE and MSG_LEVEL >= 3:
        for arm_name in BONE_LIST.keys():
            print "armature '%s':"%arm_name
            for bone_name in BONE_LIST[arm_name]:
                print "  bone '%s'"%bone_name
    # read the NIF tree
    if not is_armature_root(root_block):
        # it's a ninode?
        root_node = DynamicCastToNiNode(root_block.Ptr());
        if root_node != NULL: 
            # yes, we'll process all children of the root node
            # (this prevents us having to create an empty as a root)
            blocks = root_node.Ptr().GetChildren()
            # import the extras
            textkey = find_extra(root_block, "NiTextKeyExtraData")
            if textkey != NULL: fb_textkey(textkey)
        else:
            # this fixes an issue with nifs where the first block is a NiTriShape
            blocks = [ root_block ]
        for niBlock in blocks:
            b_obj = read_branch(niBlock)
    else:
        b_obj = read_branch(root_block)
    # store bone matrix offsets for re-export
    if len(BONES_EXTRA_MATRIX.keys()) > 0: fb_bonemat()
    # store original names for re-export
    if len(NAMES) > 0: fb_fullnames()
    b_scene.update(1) # do a full update to make sure all transformations get applied
    #fit_view()
    #b_scene.getCurrentCamera()
    
# Reads the content of the current NIF tree branch to Blender recursively
def read_branch(niBlock):
    # used to control the progress bar
    global block_count, blocks_read, read_progress
    blocks_read += 1.0
    if (blocks_read/(block_count+1)) >= (read_progress + 0.1):
        read_progress = blocks_read/(block_count+1)
        Blender.Window.DrawProgressBar(read_progress, "Importing data")
    if niBlock != NULL:
        if niBlock.Ptr().IsDerivedType( NiTriBasedGeom_TypeConst() ):
            # it's a shape node
            return fb_mesh(niBlock)
        elif niBlock.Ptr().IsDerivedType( NiNode_TypeConst() ):
            # it's a parent node
            # import object + children
            niNode = DynamicCastToNiNode( niBlock.Ptr() )
            niChildren = niNode.Ptr().GetChildren()
            b_obj = None
            if is_armature_root(niBlock):
                # the whole bone branch is imported by fb_armature as well
                b_obj = fb_armature(niBlock)
                # now also do the meshes
                read_armature_branch(b_obj, niBlock, niBlock)
            else:
                # it's a grouping node
                b_obj = fb_empty(niBlock)
                b_children_list = []
                for child in niChildren:
                    b_child_obj = read_branch(child)
                    if b_child_obj: b_children_list.append(b_child_obj)
                b_obj.makeParent(b_children_list)
            b_obj.setMatrix(fb_matrix(niBlock))
            # import the animations
            set_animation(niBlock, b_obj)
            # import the extras
            textkey = find_extra(niBlock, "NiTextKeyExtraData")
            if not textkey.is_null(): fb_textkey(textkey)
            return b_obj
        else:
            return None

# Reads the content of the current NIF tree branch to Blender
# recursively, as meshes parented to a given armature. Note that
# niBlock must have been imported previously as an armature, along
# with all its bones. This function only imports meshes.
def read_armature_branch(b_armature, niArmature, niBlock):
    """
    Reads the content of the current NIF tree branch to Blender
    recursively, as meshes parented to a given armature. Note that
    niBlock must have been imported previously as an armature, along
    with all its bones. This function only imports meshes.
    """
    # check if the child is non-null
    if niBlock != NULL:
        btype = niBlock.GetBlockType()
        # bone or group node?
        # is it an AParentNode?
        niNode = DynamicCastToNiNode(niBlock.Ptr())
        if niNode != NULL:
            niChildren = niNode.Ptr().GetChildren()
            for niChild in niChildren:
                b_mesh = read_armature_branch(b_armature, niArmature, niChild)
                if b_mesh:
                    # correct the transform
                    # it's parented to the armature!
                    armature_matrix_inverse = fb_global_matrix(niArmature)
                    armature_matrix_inverse.invert()
                    b_mesh.setMatrix(fb_global_matrix(niChild) * armature_matrix_inverse)
                    # add a vertex group if it's parented to a bone
                    par_bone = get_closest_bone(niChild)
                    if not par_bone.is_null():
                        # set vertex index 1.0 for all vertices that don't yet have a vertex weight
                        # this will mimick the fact that the mesh is parented to the bone
                        b_meshData = b_mesh.getData(mesh=True)
                        verts = [ v.index for v in b_meshData.verts ] # copy vertices, as indices
                        for groupName in b_meshData.getVertGroupNames():
                            for v in b_meshData.getVertsFromGroup(groupName):
                                try:
                                    verts.remove(v)
                                except ValueError: # remove throws value-error if vertex was already removed previously
                                    pass
                        if verts:
                            groupName = NAMES[par_bone.Ptr().GetName()]
                            b_meshData.addVertGroup(groupName)
                            b_meshData.assignVertsToGroup(groupName, verts, 1.0, Blender.Mesh.AssignModes.REPLACE)
                    # make it parent of the armature
                    b_armature.makeParentDeform([b_mesh])
        # mesh?
        elif niBlock.IsDerivedType( NiTriBasedGeom_TypeConst() ):
            return fb_mesh(niBlock)
        # anything else: throw away
        else:
            return None


#
# hash functions, for object comparison and dictionary keys
#
def float_hash(x):
    return int(x*200)

def float3_hash(x):
    return (float_hash(x[0]), float_hash(x[1]), float_hash(x[2]))

# blk_ref hash
def block_hash(b):
    if b == NULL:
        return None
    else:
        return b.Ptr()

# NiSourceTexture hash
def texsource_hash(texsource):
    return (\
        block_hash(texsource["Controller"].asLink()),\
        str(texsource["Texture Source"].asTexSource().fileName),\
        texsource["Pixel Layout"].asInt(),\
        texsource["Use Mipmaps"].asInt(),\
        texsource["Alpha Format"].asInt()\
    )

# TexDesc hash
def texdesc_hash(texdesc):
    if texdesc.isUsed:
        return (texdesc.clampMode, texdesc.filterMode, texsource_hash(texdesc.source))
    else:<commit_msg>Preparing the 1.5.6 release.<commit_after>#!BPY

""" Registration info for Blender menus:
Name: 'NetImmerse/Gamebryo (.nif & .kf)...'
Blender: 241
Group: 'Import'
Tooltip: 'Import NIF File Format (.nif & .kf)'
"""

## in the future:
#Submenu: 'Import NIF File...' nif
#Submenu: 'Import KFM File...' kfm
#Submenu: 'Import KF File...' kf
# ...

__author__ = "The NifTools team, http://niftools.sourceforge.net/"
__url__ = ("blender", "elysiun", "http://niftools.sourceforge.net/")
__version__ = "1.5.6"
__bpydoc__ = """\
This script imports Netimmerse and Gamebryo .NIF files to Blender.

Supported:<br>
    Bones.<br>
    Vertex weight skinning.<br>
    Animation groups ("Anim" text buffer).<br>
    Texture flipping (via text buffer named to the texture).<br>
    Packed textures ("packed" button next to Reload in the Image tab).<br>
    Hidden meshes (object drawtype "Wire")<br>
    Geometry morphing (vertex keys)<br>

Missing:<br>
    Particle effects, cameras, lights.<br>

Known issues:<br>
    Ambient and emit colors are obtained by multiplication with the diffuse
color.<br>

Config options (Scripts->System->Scripts Config Editor->Import):<br>
    textures dir: Semi-colon separated list of texture directories.<br>
    import dir: Default import directory.<br>
    seams import: Enable to avoid cracks in UV seams. Disable if importing
large NIF files takes too long.<br>
"""

# nif_import.py version 1.5.4
# --------------------------------------------------------------------------
# ***** BEGIN LICENSE BLOCK *****
# 
# BSD License
# 
# Copyright (c) 2005, NIF File Format Library and Tools
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the NIF File Format Library and Tools project may not be
#    used to endorse or promote products derived from this software
#    without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ***** END LICENCE BLOCK *****
# Note: Versions of this script previous to 1.0.6 were released under the GPL license
# --------------------------------------------------------------------------
#
# Credits:
# Portions of this programs are (were) derived (through the old tested method of cut'n paste)
# from the obj import script obj_import.py: OBJ Import v0.9 by <NAME> (AKA Ideasman)
# (No more. I rewrote the lot. Nevertheless I wouldn't have been able to start this without Ideasman's
# script to read from!)
# Binary conversion functions are courtesy of SJH. Couldn't find the full name, and couldn't find any
# license info, I got the code for these from http://projects.blender.org/pipermail/bf-python/2004-July/001676.html
# The file reading strategy was 'inspired' by the NifToPoly script included with the 
# DAOC mapper, which used to be available at http://www.randomly.org/projects/mapper/ and was written and 
# is copyright 2002 of <NAME>. His domain and e-mail address are however no longer reacheable.
# No part of the original code is included here, as I pretty much rewrote everything, hence this is the 
# only mention of the original copyright. An updated version of the script is included with the DAOC Mappergui
# application, available at http://nathrach.republicofnewhome.org/mappergui.html
#
# Thanks go to:
# <NAME> (AKA Ideasman, Cambo) for making code clear enough to be used as a learning resource.
#   Hey, this is my first ever python script!
# SJH for the binary conversion functions. Got the code off a forum somewhere, posted by Ideasman,
#   I suppose it's allright to use it
# <NAME> (AKA Taharez), for helping me a lot with the file format, and with some debugging even
#   though he doesn't 'do Python'
# <NAME> (AKA timmeh), for taking some of his time to help me get to terms with the way
#   the UV maps work in Blender
# Amorilia (don't know your name buddy), for bugfixes and testing.

import Blender, sys
from Blender import BGL
from Blender import Draw
from Blender.Mathutils import *


try:
    from niflib import *
except:
    err = """--------------------------
ERROR\nThis script requires the NIFLIB Python SWIG wrapper, niflib.py & _niflib.dll.
Make sure these files reside in your Python path or in your Blender scripts folder.
If you don't have them: http://niftools.sourceforge.net/
--------------------------"""
    print err
    Blender.Draw.PupMenu("ERROR%t|NIFLIB not found, check console for details")
    raise

# Attempt to load psyco to speed things up
#try:
#	import psyco
#	psyco.full()
#	print 'using psyco'
#except:
#	#print 'psyco is not present on this system'
#	pass

# dictionary of texture files, to reuse textures
TEXTURES = {}

# dictionary of materials, to reuse materials
MATERIALS = {}

# dictionary of names, to map NIF names to correct Blender names
NAMES = {}

# dictionary of bones, maps Blender bone name to NIF block
BONES = {}

# dictionary of bones, maps Blender bone name to matrix that maps the
# NIF bone matrix on the Blender bone matrix
# B' = X * B, where B' is the Blender bone matrix, and B is the NIF bone matrix
BONES_EXTRA_MATRIX = {}

# dictionary of bones that belong to a certain armature
# maps NIF armature name to list of NIF bone name
BONE_LIST = {}

# correction matrices list, the order is +X, +Y, +Z, -X, -Y, -Z
BONE_CORRECTION_MATRICES = (\
            Matrix([ 0.0,-1.0, 0.0],[ 1.0, 0.0, 0.0],[ 0.0, 0.0, 1.0]),\
            Matrix([ 1.0, 0.0, 0.0],[ 0.0, 1.0, 0.0],[ 0.0, 0.0, 1.0]),\
            Matrix([ 1.0, 0.0, 0.0],[ 0.0, 0.0, 1.0],[ 0.0,-1.0, 0.0]),\
            Matrix([ 0.0, 1.0, 0.0],[-1.0, 0.0, 0.0],[ 0.0, 0.0, 1.0]),\
            Matrix([-1.0, 0.0, 0.0],[ 0.0,-1.0, 0.0],[ 0.0, 0.0, 1.0]),\
            Matrix([ 1.0, 0.0, 0.0],[ 0.0, 0.0,-1.0],[ 0.0, 1.0, 0.0]))

# some variables

USE_GUI = 0 # BROKEN, don't set to 1, we will design a GUI for importer & exporter jointly
EPSILON = 0.005 # used for checking equality with floats, NOT STORED IN CONFIG
MSG_LEVEL = 2 # verbosity level

K_R2D = 3.14159265358979/180.0 # radians to degrees conversion constant
K_D2R = 180.0/3.14159265358979 # degrees to radians conversion constant

# 
# Process config files.
# 

# configuration default values
TEXTURES_DIR = 'C:\\Program Files\\Bethesda\\Morrowind\\Data Files\\Textures' # Morrowind: this will work on a standard installation
IMPORT_DIR = ''
SEAMS_IMPORT = True

# tooltips
tooltips = {
    'TEXTURES_DIR': "Texture directory.",
    'IMPORT_DIR': "Default import directory.",
    'SEAMS_IMPORT': "Import seams? Enable to avoid cracks in UV seams. Disable if importing large NIF files takes too long.",
}

# bounds
limits = {
}

# update registry
def update_registry():
    # populate a dict with current config values:
    d = {}
    d['TEXTURES_DIR'] = TEXTURES_DIR
    d['IMPORT_DIR'] = IMPORT_DIR
    d['SEAMS_IMPORT'] = SEAMS_IMPORT
    d['limits'] = limits
    d['tooltips'] = tooltips
    # store the key
    Blender.Registry.SetKey('nif_import', d, True)
    read_registry()

# Now we check if our key is available in the Registry or file system:
def read_registry():
    global TEXTURES_DIR, IMPORT_DIR, SEAMS_IMPORT
    regdict = Blender.Registry.GetKey('nif_import', True)
    # If this key already exists, update config variables with its values:
    if regdict:
        try:
            TEXTURES_DIR = regdict['TEXTURES_DIR'] 
            IMPORT_DIR = regdict['IMPORT_DIR']
            SEAMS_IMPORT = regdict['SEAMS_IMPORT']
            tmp_limits = regdict['limits']     # just checking if it's there
            tmp_tooltips = regdict['tooltips'] # just checking if it's there
        # if data was corrupted (or a new version of the script changed
        # (expanded, removed, renamed) the config vars and users may have
        # the old config file around):
        except: update_registry() # rewrite it
    else: # if the key doesn't exist yet, use our function to create it:
        update_registry()

read_registry()



# check export script config key for scale correction

SCALE_CORRECTION = 10.0 # same default value as in export script

rd = Blender.Registry.GetKey('nif_export', True)
if rd:
    try:
        SCALE_CORRECTION = rd['SCALE_CORRECTION']
    except: pass

# check General scripts config key for default behaviors

VERBOSE = True
CONFIRM_OVERWRITE = True

rd = Blender.Registry.GetKey('General', True)
if rd:
    try:
        VERBOSE = rd['verbose']
        CONFIRM_OVERWRITE = rd['confirm_overwrite']
    except: pass

# Little wrapper for debug messages
def msg(message='-', level=2):
    if VERBOSE:
        if level <= MSG_LEVEL:
            print message

#
# A simple custom exception class.
#
class NIFImportError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)


#
# Emulates the act of pressing the "home" key
#
def fit_view():
    Draw.Redraw(1)
    winid = Blender.Window.GetScreenInfo(Blender.Window.Types.VIEW3D)[0]['id']
    Blender.Window.SetKeyQualifiers(0)
    Blender.Window.QAdd(winid, Draw.HOMEKEY, 1)
    Blender.Window.QHandle(winid)
    Blender.Window.QAdd(winid, Draw.HOMEKEY, 0)
    Draw.Redraw(1)
    
#
# Main import function.
#
def import_nif(filename):
    Blender.Window.DrawProgressBar(0.0, "Initializing")
    # texture dirs
    global NIF_DIR, TEX_DIR
    NIF_DIR = Blender.sys.dirname(filename)
    idx = NIF_DIR.lower().find('meshes')
    if ( idx >= 0 ):
        TEX_DIR = NIF_DIR[:idx] + 'textures'
    else:
        TEX_DIR = None
    try: # catch NIFImportErrors
        # read the NIF file
        ver = CheckNifHeader(filename)
        if ( ver == VER_INVALID ):
            raise NIFImportError("Not a NIF file.")
        elif ( ver == VER_UNSUPPORTED ):
            raise NIFImportError("Unsupported NIF version.")
        Blender.Window.DrawProgressBar(0.33, "Reading file")
        root_block = ReadNifTree(filename)
        Blender.Window.DrawProgressBar(0.66, "Importing data")
        import_main(root_block)
    except NIFImportError, e: # in that case, we raise a menu instead of an exception
        Blender.Window.DrawProgressBar(1.0, "Import Failed")
        print 'NIFImportError: ' + e.value
        Blender.Draw.PupMenu('ERROR%t|' + e.value)
        return
    Blender.Window.DrawProgressBar(1.0, "Finished")



#
# Main import function.
#
def import_main(root_block):
    # scene info
    global b_scene
    b_scene = Blender.Scene.GetCurrent()
    # used to control the progress bar
    global block_count, blocks_read, read_progress
    block_count = BlocksInMemory()
    read_progress = 0.0
    blocks_read = 0.0
    # preprocessing:
    # scale tree
    scale_tree(root_block, 1.0/SCALE_CORRECTION)
    # mark armature nodes and bones
    # and merge armatures that are bones of others armatures
    mark_armatures_bones(root_block)
    merge_armatures()
    if VERBOSE and MSG_LEVEL >= 3:
        for arm_name in BONE_LIST.keys():
            print "armature '%s':"%arm_name
            for bone_name in BONE_LIST[arm_name]:
                print "  bone '%s'"%bone_name
    # read the NIF tree
    if not is_armature_root(root_block):
        if not root_block["Children"].is_null(): # it's a ninode?
            # yes, we'll process all children of the root node
            # (this prevents us having to create an empty as a root)
            blocks = root_block["Children"].asLinkList()
            # import the extras
            textkey = find_extra(root_block, "NiTextKeyExtraData")
            if not textkey.is_null(): fb_textkey(textkey)
        else:
            # this fixes an issue with nifs where the first block is a NiTriShape
            blocks = [ root_block ]
        for niBlock in blocks:
            b_obj = read_branch(niBlock)
    else:
        b_obj = read_branch(root_block)
    # store bone matrix offsets for re-export
    if len(BONES_EXTRA_MATRIX.keys()) > 0: fb_bonemat()
    # store original names for re-export
    if len(NAMES) > 0: fb_fullnames()
    b_scene.update(1) # do a full update to make sure all transformations get applied
    #fit_view()
    #b_scene.getCurrentCamera()
    
# Reads the content of the current NIF tree branch to Blender recursively
def read_branch(niBlock):
    # used to control the progress bar
    global block_count, blocks_read, read_progress
    blocks_read += 1.0
    if (blocks_read/(block_count+1)) >= (read_progress + 0.1):
        read_progress = blocks_read/(block_count+1)
        Blender.Window.DrawProgressBar(read_progress, "Importing data")
    if not niBlock.is_null():
        btype = niBlock.GetBlockType()
        if btype == "NiTriShape" or btype == "NiTriStrips":
            # it's a shape node
            return fb_mesh(niBlock)
        elif not niBlock["Children"].is_null():
            # it's a parent node
            # import object + children
            niChildren = niBlock["Children"].asLinkList()
            b_obj = None
            if is_armature_root(niBlock):
                # the whole bone branch is imported by fb_armature as well
                b_obj = fb_armature(niBlock)
                # now also do the meshes
                read_armature_branch(b_obj, niBlock, niBlock)
            else:
                # it's a grouping node
                b_obj = fb_empty(niBlock)
                b_children_list = []
                for child in niChildren:
                    b_child_obj = read_branch(child)
                    if b_child_obj: b_children_list.append(b_child_obj)
                b_obj.makeParent(b_children_list)
            b_obj.setMatrix(fb_matrix(niBlock))
            # import the animations
            set_animation(niBlock, b_obj)
            # import the extras
            textkey = find_extra(niBlock, "NiTextKeyExtraData")
            if not textkey.is_null(): fb_textkey(textkey)
            return b_obj
        else:
            return None

# Reads the content of the current NIF tree branch to Blender
# recursively, as meshes parented to a given armature. Note that
# niBlock must have been imported previously as an armature, along
# with all its bones. This function only imports meshes.
def read_armature_branch(b_armature, niArmature, niBlock):
    """
    Reads the content of the current NIF tree branch to Blender
    recursively, as meshes parented to a given armature. Note that
    niBlock must have been imported previously as an armature, along
    with all its bones. This function only imports meshes.
    """
    # check if the child is non-null
    if not niBlock.is_null():
        btype = niBlock.GetBlockType()
        # bone or group node?
        # is it an AParentNode?
        if not niBlock["Children"].is_null():
            niChildren = niBlock["Children"].asLinkList()
            for niChild in niChildren:
                b_mesh = read_armature_branch(b_armature, niArmature, niChild)
                if b_mesh:
                    # correct the transform
                    # it's parented to the armature!
                    armature_matrix_inverse = fb_global_matrix(niArmature)
                    armature_matrix_inverse.invert()
                    b_mesh.setMatrix(fb_global_matrix(niChild) * armature_matrix_inverse)
                    # add a vertex group if it's parented to a bone
                    par_bone = get_closest_bone(niChild)
                    if not par_bone.is_null():
                        # set vertex index 1.0 for all vertices that don't yet have a vertex weight
                        # this will mimick the fact that the mesh is parented to the bone
                        b_meshData = b_mesh.getData(mesh=True)
                        verts = [ v.index for v in b_meshData.verts ] # copy vertices, as indices
                        for groupName in b_meshData.getVertGroupNames():
                            for v in b_meshData.getVertsFromGroup(groupName):
                                try:
                                    verts.remove(v)
                                except ValueError: # remove throws value-error if vertex was already removed previously
                                    pass
                        if verts:
                            groupName = NAMES[par_bone["Name"].asString()]
                            b_meshData.addVertGroup(groupName)
                            b_meshData.assignVertsToGroup(groupName, verts, 1.0, Blender.Mesh.AssignModes.REPLACE)
                    # make it parent of the armature
                    b_armature.makeParentDeform([b_mesh])
        # mesh?
        elif btype == "NiTriShape" or btype == "NiTriStrips":
            return fb_mesh(niBlock)
        # anything else: throw away
        else:
            return None


#
# hash functions, for object comparison and dictionary keys
#
def float_hash(x):
    return int(x*200)

def float3_hash(x):
    return (float_hash(x[0]), float_hash(x[1]), float_hash(x[2]))

# blk_ref hash
def block_hash(b):
    if b.is_null():
        return None
    else:
        return b.GetBlockNum()

# NiSourceTexture hash
def texsource_hash(texsource):
    return (\
        block_hash(texsource["Controller"].asLink()),\
        str(texsource["Texture Source"].asTexSource().fileName),\
        texsource["Pixel Layout"].asInt(),\
        texsource["Use Mipmaps"].asInt(),\
        texsource["Alpha Format"].asInt()\
    )

# TexDesc hash
def texdesc_hash(texdesc):
    if texdesc.isUsed:
        return (texdesc.clampMode, texdesc.filterMode, texsource_hash(texdesc.source))
    else:
{'fraction_non_alphanumeric': 0.10149991935917424, 'fraction_numerical': 0.01303693349819902, 'mean_word_length': 2.638079405437121, 'pattern_counts': {'":': 2, '<': 47, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3772933', 'n_tokens_mistral': 11625, 'n_tokens_neox': 10936, 'n_words': 3846}