. initial commit in addons of bel and io_directx_bel
authorJerome Mahieux <jerome.le.chat@free.fr>
Mon, 23 Jan 2012 07:38:34 +0000 (07:38 +0000)
committerJerome Mahieux <jerome.le.chat@free.fr>
Mon, 23 Jan 2012 07:38:34 +0000 (07:38 +0000)
. corrected a bug about referenced token parenting
. corrected a bug about non parented meshes
. armatures/empties importation enabled by default
. last run importer options are saved in a 'last_run' preset,
  so it can be replayed or saved under another name once a
  particular .x profile has been defined
. tagged script for 2.6.1

bel/__init__.py [new file with mode: 0644]
bel/fs.py [new file with mode: 0644]
bel/image.py [new file with mode: 0644]
bel/material.py [new file with mode: 0644]
bel/mesh.py [new file with mode: 0644]
bel/ob.py [new file with mode: 0644]
bel/uv.py [new file with mode: 0644]
io_directx_bel/README [new file with mode: 0644]
io_directx_bel/__init__.py [new file with mode: 0644]
io_directx_bel/import_x.py [new file with mode: 0644]
io_directx_bel/templates_x.py [new file with mode: 0644]

diff --git a/bel/__init__.py b/bel/__init__.py
new file mode 100644 (file)
index 0000000..0ac6fd6
--- /dev/null
@@ -0,0 +1,23 @@
+# set a given name to a unique
+# blender data name in its collection
+def bpyname(name,collection,suffix=4) :
+    name = name[:20-suffix]
+    tpl = '%s.%.'+str(suffix)+'d'
+    bname = name
+    id = 0
+    while bname in collection :
+        id += 1
+        bname = tpl%(name,id)
+    return bname
+
+## check if there's nested lists in a list. used by functions that need
+# list(s) of vertices/faces/edges etc as input
+# @param lst a list of vector or a list of list of vectors
+# @returns always nested list(s)
+# a boolean True if was nested, False if was not
+def nested(lst) :
+    try :
+        t = lst[0][0][0]
+        return lst, True
+    except :
+        return [lst], False
\ No newline at end of file
diff --git a/bel/fs.py b/bel/fs.py
new file mode 100644 (file)
index 0000000..c9398fb
--- /dev/null
+++ b/bel/fs.py
@@ -0,0 +1,71 @@
+# v0.1
+import bpy
+from os import path as os_path, listdir as os_listdir
+from bpy import path as bpy_path
+
+# cross platform paths (since ms conform to / path ;) )
+# maybe add utf8 replace to old ascii blender builtin
+# // can be omitted for relative
+def clean(path) :
+    path = path.strip().replace('\\','/')
+    if ('/') not in path : path = '//'+path
+    return path
+    
+## test for existence of a file or a dir
+def exist(path) :
+    if isfile(path) or isdir(path) : return True
+    return False
+
+## test for existence of a file
+def isfile(path) :
+    if os_path.isfile(path) : return True
+    # could be blender relative
+    path = bpy_path.abspath(path)
+    if os_path.isfile(path) : return True
+    return False
+
+## test for existence of a dir
+def isdir(path) :
+    if os_path.isdir(path) : return True
+    # could be blender relative
+    path = bpy_path.abspath(path)
+    if os_path.isdir(path) : return True
+    return False
+
+## returns a list of every absolute filepath
+# to each file within the 'ext' extensions
+# from a folder and its subfolders
+def scanDir(path,ext='all') :
+    files = []
+    fields = os_listdir(path)
+    if ext != 'all' and type(ext) != list : ext = [ext]
+    for item in fields :
+        if os_path.isfile(path + '/' + item) and (ext == 'all' or item.split('.')[-1] in ext) :
+            #print('  file %s'%item)
+            files.append(path + '/' + item)
+        elif os_path.isdir(path + '/' + item) :
+            print('folder %s/%s :'%(path,item))
+            files.extend(scanDir(path + '/' + item))
+    return files
+
+def saveOptions(operator_name, tokens, filename='last_run'):
+    target_path = os_path.join("operator", operator_name)
+    target_path = os_path.join("presets", target_path)
+    target_path = bpy.utils.user_resource('SCRIPTS',target_path,create=True)
+    if target_path:
+        filepath = os_path.join(target_path, filename) + ".py"
+        file_preset = open(filepath, 'w')
+        file_preset.write("import bpy\nop = bpy.context.active_operator\n\n")
+        properties_blacklist = bpy.types.Operator.bl_rna.properties.keys()
+        for key, value in tokens.items() :
+            if key not in properties_blacklist :
+                # convert thin wrapped sequences to simple lists to repr()
+                try:
+                    value = value[:]
+                except:
+                    pass
+    
+                file_preset.write("op.%s = %r\n" % (key, value))
+
+        file_preset.close()
+    
diff --git a/bel/image.py b/bel/image.py
new file mode 100644 (file)
index 0000000..043775a
--- /dev/null
@@ -0,0 +1,264 @@
+import bpy
+import bpy.path
+import bel
+import bel.fs
+
+debuglevel = 0
+
+def dprint(str,l=2) :
+    if l <= debuglevel :
+        print(str)
+
+# create or retrieve a bdata image
+# given its path 
+def new(path, name=False, relative = True, premul = True) :
+    path = bel.fs.clean(path)
+    # check file
+    if bel.fs.isfile(path) == False :
+        dprint('Texture image not found')
+        return False
+
+    if relative :
+        try :
+            path = bpy.path.relpath(path)
+            path = bel.fs.clean(path)
+        except : 
+            print('cant turn path into relative one (.blend and img path drive letters ?) ')
+        
+    # retrieve paths to image file from existing image slot
+    # returns img if paths match
+    for img in bpy.data.images :
+        if img.filepath != '' :
+            if bpy.path.abspath(path) == bpy.path.abspath(bel.fs.clean(img.filepath)) :
+                return img
+
+    # create a unique name in image slot
+    if name == False : 
+        name = bpy.path.basename(path)
+    name = bel.bpyname(name,bpy.data.images.keys())
+
+    # finally :
+    img = bpy.data.images.load(filepath=path)
+    img.name = name
+    img.use_premultiply = premul
+    return img
+
+
+def applyShader(mat,config) :
+
+    # matslot.link = 'DATA'
+    #mat = bpy.data.materials['Female_Body']
+
+    texslot = mat.texture_slots[0]
+    tex = texslot.texture
+    img = tex.image
+    
+    #config = shaders[shadername]
+    alpha = True if 'alpha' in config else False
+
+    ## MAT
+
+    mat.type = 'SURFACE'
+    # diffuse
+    mat.diffuse_color = Color((0.6, 0.6, 0.6))
+    mat.diffuse_intensity = 0.8
+    mat.diffuse_shader = 'LAMBERT'
+    mat.use_diffuse_ramp = False
+
+    # specular
+    mat.specular_color = Color((1.0, 1.0, 1.0))
+    mat.specular_intensity = 0.25
+    mat.specular_shader = 'COOKTORR'
+    mat.use_specular_ramp = False
+    mat.specular_hardness = 1.0
+
+    # shading
+    mat.emit = 0.0
+    mat.ambient = 0.5
+    mat.translucency = 0.0
+    mat.use_shadeless = False
+    mat.use_tangent_shading = False
+    mat.use_cubic = False
+
+    # transparency
+    mat.use_transparency = alpha
+    mat.transparency_method = 'Z_TRANSPARENCY'
+    mat.alpha = not(alpha)
+    mat.specular_alpha = not(alpha)
+    mat.raytrace_transparency.fresnel = 0.0
+    mat.raytrace_transparency.fresnel_factor = 1.25
+
+    # mirror
+    mat.raytrace_mirror.use = False
+
+    # subsurface_scattering
+    mat.subsurface_scattering.use
+
+    # strand
+    # options
+    # shadow
+    mat.use_shadows = True
+    mat.use_transparent_shadows = True
+    mat.use_cast_shadows_only = False
+    mat.shadow_cast_alpha = 1.0
+    mat.use_only_shadow = False
+    mat.shadow_only_type = 'SHADOW_ONLY_OLD'
+    mat.use_cast_buffer_shadows = True
+    mat.shadow_buffer_bias = 0.0
+    mat.use_ray_shadow_bias = True
+    mat.shadow_ray_bias = 0.0
+    mat.use_cast_approximate = True
+
+    # TEXTURE SLOT 0
+
+    # diffuse
+    texslot.diffuse_factor = 1.0
+    texslot.use_map_diffuse = True
+    texslot.diffuse_color_factor = 1.0
+    texslot.use_map_color_diffuse = True
+    texslot.alpha_factor = 1.0
+    texslot.use_map_alpha = alpha
+    texslot.translucency_factor = 0.0
+    texslot.use_map_translucency = False
+
+    # specular
+    texslot.specular_factor = 0.3
+    texslot.use_map_specular = True
+    texslot.specular_color_factor = 1.0
+    texslot.use_map_color_spec = True
+    texslot.hardness_factor = 0.1
+    texslot.use_map_hardness = True
+
+    # shading
+    texslot.ambient_factor = 0.0
+    texslot.use_map_ambient = False
+    texslot.emit_factor = 0.1
+    texslot.use_map_emit = True
+    texslot.mirror_factor = 0.0
+    texslot.use_map_mirror = False
+    texslot.raymir_factor = 0.0
+    texslot.use_map_raymir = False
+
+    # geometry
+    texslot.normal_factor = 0.0
+    texslot.use_map_normal = False
+    texslot.warp_factor = 0.1
+    texslot.use_map_warp = False
+    texslot.displacement_factor = 0.0
+    texslot.use_map_displacement = False
+
+    texslot.blend_type = 'MIX'
+    texslot.invert = False
+    texslot.use_rgb_to_intensity = False
+    texslot.color = Color((1.0, 0.0, 1.0)) # default
+    texslot.use_stencil = False
+    texslot.default_value = 1.0
+
+    # TEXTURE
+    tex.use_alpha = alpha
+    tex.use_preview_alpha = alpha
+
+    # IMAGE
+    if type(img) != type(None) :
+        img.use_premultiply = True
+
+def BSshader(nodes,pointer) :
+    tkm = bpy.context.scene.tkm
+    typ, nodename = pointer.split(' ')
+    RenderShader = nodes[typ][nodename]
+    name = BSname(nodename,RenderShader['Object.Name'])
+    if name in bpy.data.materials :
+        mat = bpy.data.materials[name]
+    else :
+        mat = bpy.data.materials.new(name=name)
+        # Unused
+        DepthWriteEnable = RenderShader['DepthWriteEnable'] if 'DepthWriteEnable' in RenderShader else False # an integer
+        ShaderTransparency = RenderShader['MultiDrawLayer'] if 'MultiDrawLayer' in RenderShader else False # an integer
+        LightEnable = RenderShader['LightEnable'] if 'LightEnable' in RenderShader else False # an integer
+
+        ShaderPhong = BSnode(nodes,RenderShader['Surface'])
+        #print('mat : %s'%ShaderPhong['Material'])
+        RenderMaterial = BSnode(nodes,ShaderPhong['Material'])
+        DiffuseColor = RenderMaterial['DiffuseColor'] if 'DiffuseColor' in RenderMaterial else False
+        SpecularColor = RenderMaterial['SpecularColor'] if 'SpecularColor' in RenderMaterial else False
+        AmbientColor = RenderMaterial['AmbientColor'] if 'AmbientColor' in RenderMaterial else False
+        EmissionColor = RenderMaterial['Shininess'] if 'EmissionColor' in RenderMaterial else False
+        Shininess = RenderMaterial['Shininess'] if 'Shininess' in RenderMaterial else False
+        Transparency = RenderMaterial['Transparency'] if 'Transparency' in RenderMaterial else False
+        for key in RenderMaterial.keys() :
+            if key not in ['DiffuseColor','SpecularColor','AmbientColor','EmissionColor','Shininess','Transparency'] :
+                print('NEW RENDERMATERIAL PROP ! : %s'%key)
+        
+        #print(AmbientColor)
+        if DiffuseColor : mat.diffuse_color = Color(DiffuseColor) #[0][0],DiffuseColor[0][1],DiffuseColor[0][2])
+        if SpecularColor : mat.specular_color = Color(SpecularColor)#[0][0],SpecularColor[0][1],SpecularColor[0][2])
+        if AmbientColor : mat.ambient = AmbientColor[0] # source value is a vector3f with x=y=z 
+        if EmissionColor : mat.emit = EmissionColor[0] # source value is a vector3f with x=y=z 
+        #if Shininess : mat.
+        #alpha is a boolean, whereas Transparency is a float or False
+        if Transparency :
+            mat.use_transparency = True
+            mat.transparency_method = 'Z_TRANSPARENCY'
+            mat.alpha = Transparency
+            mat.specular_alpha = 0
+            alpha = True
+        else : alpha = False
+        texinfluence = False
+        if 'Color' in ShaderPhong :
+            ShaderTexture = BSnode(nodes,ShaderPhong['Color'])
+            texinfluence = 'Color'
+        if 'Reflection' in ShaderPhong :
+            ShaderTexture = BSnode(nodes,ShaderPhong['Reflection'])
+            texinfluence = 'Reflection'
+        if texinfluence == False :
+            print('neither color nor refl. in ShaderPhong %s'%RenderShader['Surface'])
+            print('other props are : %s'%ShaderPhong.keys())
+            return mat
+
+        ShaderTextureName = ShaderTexture['Object.Name']
+
+        Texture2D = BSnode(nodes,ShaderTexture['Texture'])
+        Texture2DName = Texture2D['Object.Name']
+
+        FileObject = BSnode(nodes,Texture2D['Texture.FileObject'])
+        imgpath = FileObject['FileName']
+        imgname = imgpath.split('/')[-1]
+        imgpath = tkm.path_archives+'/Images/Q=Tex032M/'+imgpath
+
+        if imgname not in bpy.data.images :        
+            if os.path.isfile(imgpath+'.png') : ext = '.png'
+            elif os.path.isfile(imgpath+'.jp2') : ext = '.jp2'
+            else :
+                print('Texture image not found ! %s'%Texture2D['Texture.FileObject'])
+                print('path : %s.png or .jp2 '%imgpath)
+                return mat
+            img = bpy.data.images.load(filepath=imgpath+ext)
+            img.name = imgname
+            img.use_premultiply = True
+        else : img = bpy.data.images[imgname]
+        
+        '''
+        texslot = mat.texture_slots[0]
+        mat.texture_slots[0]
+        tex = texslot.texture
+        tex.type = 'IMAGE'
+        img = tex.image        
+        img.name
+        '''
+        #img = bpy.data.images.new(name='imgname',width=640, height=512)
+
+        if ShaderTextureName not in bpy.data.textures :
+            tex = bpy.data.textures.new(name=ShaderTextureName,type='IMAGE')
+            tex.image = img
+            tex.use_alpha = alpha
+            tex.use_preview_alpha = alpha
+        else : tex = bpy.data.textures[ShaderTextureName]
+
+        texslot = mat.texture_slots.create(index=0)
+        texslot.texture = tex
+        texslot.texture_coords = 'UV'
+        texslot.uv_layer = 'UV0'
+        texslot.use_map_alpha = alpha
+        texslot.alpha_factor = 1.0
+
+    return mat
\ No newline at end of file
diff --git a/bel/material.py b/bel/material.py
new file mode 100644 (file)
index 0000000..d39f32c
--- /dev/null
@@ -0,0 +1,32 @@
+import bpy
+
+'''
+given name < 21
+if material name exists :
+naming_method = 0   blender default (increment name)
+naming_method = 1   do nothing, abort creation and use existing
+naming_method = 2   create new, rename existing, 
+naming_method = 3   create new, replace existing
+'''
+
+def new(name, naming_method=0) :
+    if name not in bpy.data.materials or naming_method == 0:
+        return bpy.data.materials.new(name=name)
+    
+    elif naming_method == 1 :
+        return bpy.data.materials[name]
+    
+    mat = bpy.data.materials.new(name=name)
+    
+    if naming_method == 2 :
+        mat.name = name
+        return mat
+    
+    # naming_method = 3 : replace 
+    prevmat = bpy.data.materials[name]
+    for ob in bpy.data.objects :
+        for matslot in ob.material_slots :
+            if matslot.material == prevmat :
+                matslot.material = mat
+    bpy.data.materials.remove(prevmat)
+    return mat
diff --git a/bel/mesh.py b/bel/mesh.py
new file mode 100644 (file)
index 0000000..969525a
--- /dev/null
@@ -0,0 +1,208 @@
+##\file
+# raw extract quick cleaning from blended cities2.6 project. thanks to myself for cooperation, but what a messy code we have here.
+import bpy
+import mathutils
+from mathutils import *
+
+import bel.uv
+import bel.ob
+
+debuglevel = 0
+'''
+wip.. naming behaviour previous to any data
+name exist ?
+no : create
+yes :
+    naming_method = 0   blender default (increment name)
+    naming_method = 1   do nothing, abort creation and use existing
+    naming_method = 2   create new, rename existing, 
+    naming_method = 3   create new, remove existing
+    
+for now, and mesh data, 0 2 or 3
+'''
+
+## material MUST exist before creation of material slots
+## map only uvmap 0 to its image defined in mat  for now (multitex view)
+def write(obname,name, 
+          verts=[], edges=[], faces=[], 
+          matslots=[], mats=[], uvs=[], 
+          groupnames=[], vindices=[], vweights=[],
+          smooth=False,
+          naming_method = 0,
+          ) :
+
+    
+    obj = bpy.data.objects[obname] if obname in bpy.data.objects else False
+    me = bpy.data.meshes[name] if name in bpy.data.meshes else False
+
+    #print(naming_method,type(obj),type(me))
+    #print(obj,me)
+    #print()
+    if naming_method == 1 and me and obj and obj.data == me :
+        #print('%s and %s exist, reuse'%(obj.name,me.name))
+        return obj
+       
+    if naming_method == 3 :
+        if obj : 
+            #print('remove ob %s'%obj.name)
+            bel.ob.remove(obj,False)
+        if me :
+            #print('remove me %s'%me.name)
+            bel.ob.removeData(me)
+    
+
+    me = bpy.data.meshes.new(name)
+    if naming_method == 2 : me.name = name
+    
+    me.from_pydata(verts, edges, faces)
+    me.update()
+
+    if smooth : shadesmooth(me)
+    
+    # material slots
+    matimage=[]
+    if len(matslots) > 0 :
+        for matname in matslots :
+            '''
+            if matname not in bpy.data.materials :
+                mat = bpy.data.materials.new(name=matname)
+                mat.diffuse_color=( random.uniform(0.0,1.0),random.uniform(0.0,1.0),random.uniform(0.0,1.0))
+                mat.use_fake_user = True
+                warn.append('Created missing material : %s'%matname)
+            else :
+            '''
+            mat = bpy.data.materials[matname]
+            me.materials.append(mat)
+            texslot_nb = len(mat.texture_slots)
+            if texslot_nb :
+                texslot = mat.texture_slots[0]
+                if type(texslot) != type(None) :
+                    tex = texslot.texture
+                    if tex.type == 'IMAGE' :
+                        img = tex.image
+                        if type(img) != type(None) :
+                            matimage.append(img)
+                            continue
+            matimage.append(False)
+
+    # map a material to each face
+    if len(mats) > 0 :
+        for fi,f in enumerate(me.faces) :
+            f.material_index = mats[fi]
+
+    # uvs
+    if len(uvs) > 0 :
+        bel.uv.write(me, uvs, matimage)
+
+
+    obj = bpy.data.objects.new(name=obname, object_data=me)
+    if naming_method != 0 :
+        obj.name = obname
+            
+    '''
+    else :
+        ob = bpy.data.objects[name]
+        ob.data = me
+        if naming_method == 2 : ob.name = 
+        ob.parent = None
+        ob.matrix_local = Matrix()
+        print('  reuse object %s'%ob.name)
+    '''
+            
+    # vertexgroups
+    if len(groupnames) > 0 :
+        for gpi, groupname in enumerate(groupnames) :
+            weightsadd(obj, groupname, vindices[gpi], vweights[gpi])
+    
+    # scene link check
+    if obj.name not in bpy.context.scene.objects.keys() :
+        bpy.context.scene.objects.link(obj)
+        
+    return obj
+
+def shadesmooth(me,lst=True) :
+    if type(lst) == list :
+        for fi in lst :
+            me.faces[fi].use_smooth = True
+    else :
+        for fi,face in enumerate(me.faces) :
+            face.use_smooth = True
+def shadeflat(me,lst=True) :
+    if type(lst) == list :
+        for fi in lst :
+            me.faces[fi].use_smooth = False
+    else :
+        for fi,face in enumerate(me.faces) :
+            face.use_smooth = False
+
+def weightsadd(ob, groupname, vindices, vweights=False) :
+    if vweights == False : vweights = [1.0 for i in range(len(vindices))]
+    elif type(vweights) == float : vweights = [vweights for i in range(len(vindices))]
+    group = ob.vertex_groups.new(groupname)
+    for vi,v in enumerate(vindices) :
+        group.add([v], vweights[vi], 'REPLACE')
+
+def matToString(mat) :
+    #print('*** %s %s'%(mat,type(mat)))
+    return str(mat).replace('\n       ','')[6:]
+
+def stringToMat(string) :
+    return Matrix(eval(string))
+
+
+def objectBuild(elm, verts, edges=[], faces=[], matslots=[], mats=[], uvs=[] ) :
+    #print('build element %s (%s)'%(elm,elm.className()))
+    dprint('object build',2)
+    city = bpy.context.scene.city
+    # apply current scale
+    verts = metersToBu(verts)
+    
+    if type(elm) != str :
+        obname = elm.objectName()
+        if obname == 'not built' :
+            obname = elm.name
+    else : obname= elm
+
+    obnew = createMeshObject(obname, True, verts, edges, faces, matslots, mats, uvs)
+    #elm.asElement().pointer = str(ob.as_pointer())
+    if type(elm) != str :
+        if elm.className() == 'outlines' :
+            obnew.lock_scale[2] = True
+            if elm.parent :
+                obnew.parent = elm.Parent().object()
+        else :
+            #otl = elm.asOutline()
+            #ob.parent = otl.object()
+            objectLock(obnew,True)
+        #ob.matrix_local = Matrix() # not used
+        #ob.matrix_world = Matrix() # world
+    return obnew
+
+def dprint(str,l=2) :
+    if l <= debuglevel :
+        print(str)
+
+
+def materialsCheck(bld) :
+    if hasattr(bld,'materialslots') == False :
+        #print(bld.__class__.__name__)
+        builderclass = eval('bpy.types.%s'%(bld.__class__.__name__))
+        builderclass.materialslots=[bld.className()]
+
+    matslots = bld.materialslots
+    if len(matslots) > 0 :
+        for matname in matslots :
+            if matname not in bpy.data.materials :
+                mat = bpy.data.materials.new(name=matname)
+                mat.use_fake_user = True
+                if hasattr(bld,'mat_%s'%(matname)) :
+                    method = 'defined by builder'
+                    matdef = eval('bld.mat_%s'%(matname))
+                    mat.diffuse_color = matdef['diffuse_color']
+                else :
+                    method = 'random'
+                    mat.diffuse_color=( random.uniform(0.0,1.0),random.uniform(0.0,1.0),random.uniform(0.0,1.0))
+                dprint('Created missing material %s (%s)'%(matname,method),2)
+
+
diff --git a/bel/ob.py b/bel/ob.py
new file mode 100644 (file)
index 0000000..486bced
--- /dev/null
+++ b/bel/ob.py
@@ -0,0 +1,116 @@
+import bpy
+from bpy.types import Mesh, PointLamp, SpotLamp, HemiLamp, AreaLamp, SunLamp, Camera, TextCurve, MetaBall, Lattice, Armature
+
+
+def new(name,datatype,naming_method):
+    if name in bpy.data.objects and naming_method :
+        ob = bpy.data.objects[name]
+        if naming_method == 1 :
+            ob.parent = None
+            ob.user_clear()
+        elif naming_method == 2 :
+            ob = bpy.data.objects.new(name,datatype)
+            ob.name = name
+        elif naming_method == 3 :
+            bpy.context.scene.objects.unlink(ob)
+            ob.user_clear()
+            bpy.data.objects.remove(ob)
+            ob = bpy.data.objects.new(name,datatype)
+    else :
+        ob = bpy.data.objects.new(name,datatype)
+    if ob.name not in bpy.context.scene.objects.keys() :
+        bpy.context.scene.objects.link(ob)
+    return ob
+
+## returns an object or a list of objects
+# @param ob 'all', 'active', 'selected', <object>, 'objectname'
+# @return a list of objects or an empty list
+def get(ob) :
+    if type(ob) == str :
+        if ob == 'all' : return bpy.context.scene.objects
+        elif ob == 'active' : return [bpy.context.active_object] if bpy.context.active_object != None else []
+        elif ob == 'selected' : return bpy.context.selected_objects
+        else :
+            try : return [bpy.data.objects[ob]]
+            except : return []
+    return [ob]
+
+
+## remove an object from blender internal
+def remove(ob,with_data=True) :
+    objs = get(ob)
+    #if objs :
+    #    if type(objs) == bpy.types.Object : objs = [objs]
+    for ob in objs :
+            data = ob.data
+            #and_data=False
+            # never wipe data before unlink the ex-user object of the scene else crash (2.58 3 770 2)
+            # if there's more than one user for this data, never wipeOutData. will be done with the last user
+            # if in the list
+            and_data = with_data
+            try :
+                if data.users > 1 :
+                    and_data=False
+            except :
+                and_data=False # empties
+                
+            # odd (pre 2.60) :
+            # ob=bpy.data.objects[ob.name]
+            # if the ob (board) argument comes from bpy.data.groups['aGroup'].objects,
+            #  bpy.data.groups['board'].objects['board'].users_scene
+            ob.name = '_dead'
+            for sc in ob.users_scene :
+                sc.objects.unlink(ob)
+
+            #try :
+                #print('  removing object %s...'%(ob.name)),
+            bpy.data.objects.remove(ob)
+                #print('  done.')
+            #except :
+            #    print('removing failed, but renamed %s and unlinked'%ob.name)
+
+            # never wipe data before unlink the ex-user object of the scene else crash (2.58 3 770 2)
+            if and_data :
+                wipeOutData(data)
+
+
+## remove an object data from blender internal
+## or rename it _dead if there's still users
+def removeData(data) :
+    #print('%s has %s user(s) !'%(data.name,data.users))
+    
+    if data.users <= 0 :
+
+            #data.user_clear()
+            data_type = type(data)
+            
+            # mesh
+            if data_type == Mesh :
+                bpy.data.meshes.remove(data)
+            # lamp
+            elif data_type in [ PointLamp, SpotLamp, HemiLamp, AreaLamp, SunLamp ] :
+                bpy.data.lamps.remove(data)
+            # camera
+            elif data_type == Camera :
+                bpy.data.cameras.remove(data)
+            # Text, Curve
+            elif data_type in [ Curve, TextCurve ] :
+                bpy.data.curves.remove(data)
+            # metaball
+            elif data_type == MetaBall :
+                bpy.data.metaballs.remove(data)
+            # lattice
+            elif data_type == Lattice :
+                bpy.data.lattices.remove(data)
+            # armature
+            elif data_type == Armature :
+                bpy.data.armatures.remove(data)
+            else :
+                print('  data still here : forgot %s type'%type(data))
+        #except :
+            # empty, field
+        #    print('%s has no user_clear attribute ? (%s).'%(data.name,type(data)))
+    else :
+        #print('  not done, %s has %s user'%(data.name,data.users))
+        data.name = '_dead'
+        
\ No newline at end of file
diff --git a/bel/uv.py b/bel/uv.py
new file mode 100644 (file)
index 0000000..1724c31
--- /dev/null
+++ b/bel/uv.py
@@ -0,0 +1,69 @@
+from mathutils import Vector
+import bel
+
+def write(me, uvs, matimage = False) :
+    uvs, nest = bel.nested(uvs)
+    newuvs = []
+    for uvi, uvlist in enumerate(uvs) :
+
+        uv = me.uv_textures.new()
+        uv.name = 'UV%s'%uvi
+        
+        for uvfi, uvface in enumerate(uvlist) :
+            #uv.data[uvfi].use_twoside = True # 2.60 changes mat ways
+            mslotid = me.faces[uvfi].material_index
+            #mat = mesh.materials[mslotid]
+            if matimage :
+                if matimage[mslotid] :
+                    img = matimage[mslotid]
+                    uv.data[uvfi].image=img
+                    #uv.data[uvfi].use_image = True
+            
+            uv.data[uvfi].uv1 = Vector((uvface[0],uvface[1]))
+            uv.data[uvfi].uv2 = Vector((uvface[2],uvface[3]))
+            uv.data[uvfi].uv3 = Vector((uvface[4],uvface[5]))
+            if len(uvface) == 8 :
+                uv.data[uvfi].uv4 = Vector((uvface[6],uvface[7]))
+        newuvs.append(uv)
+    if nest : return newuvs
+    else : return newuvs[0]
+
+
+# face are squared or rectangular, 
+# any orientation
+# vert order width then height 01 and 23 = x 12 and 03 = y
+# normal default when face has been built
+def row(vertices,faces,normals=True) :
+    uvs = []
+    for face in faces :
+        v0 = vertices[face[0]]
+        v1 = vertices[face[1]]
+        v2 = vertices[face[-1]]
+        print(v0,v1)
+        lx = (v1 - v0).length
+        ly = (v2 - v0).length
+        # init uv
+        if len(uvs) == 0 :
+            x = 0
+            y = 0
+        elif normals :
+            x = uvs[-1][2]
+            y = uvs[-1][3]
+        else :
+            x = uvs[-1][0]
+            y = uvs[-1][1]
+        if normals : uvs.append([x,y,x+lx,y,x+lx,y+ly,x,y+ly])
+        else : uvs.append([x+lx,y,x,y,x,y+ly,x+lx,y+ly])
+    return uvs
+
+## convert UV given as verts location to blender format
+# eg : [ [v0x,v0y] , [vnx , vny] ... ] -> [ [ v1x,v1y,v0x,v0y,v4x,v4y] ... ]
+# found in directx
+def asVertsLocation(verts2d, faces) :
+    uv = []
+    for f in faces :
+        uvface = []
+        for vi in f :
+            uvface.extend(verts2d[vi])
+        uv.append(uvface)
+    return uv
diff --git a/io_directx_bel/README b/io_directx_bel/README
new file mode 100644 (file)
index 0000000..7301fb8
--- /dev/null
@@ -0,0 +1,258 @@
+a DirectX importer addon for Blender 2.6\r
+\r
+first goals :\r
+\r
+. to import anything from an .x file.\r
+  obviously verts, faces but uv, armatures, weights, normals...\r
+. import .x in binary format too\r
+\r
+horizon :\r
+. export to .x or mod/co-maintain the existing x exporter.\r
+. this project is also a prototype for a 'Blender Exchange Layer' project.\r
+  BEL would be a common layer logically located between an importer/exporter\r
+  addon and the blender data format, that would allow :\r
+    . to provide a common set of methods to retrieve/inject objects in Blender\r
+    . to provide a common set of transformation and selection tools between an\r
+      import/export script and Blender datas (rotate, rescale, filters...)\r
+    . to provide a common set of parsing helpers for new io addons\r
+\r
+PLY won't be used unfortunately (it's way too slow as far as I tested)\r
+\r
+\r
+TO TEST THE SCRIPT :\r
+  . copy the 'bel' folder in /scripts/modules. can reside in /addons but Blender will complain a bit in the console. (harmless)\r
+  . copy the 'io_directx_bel' in /scripts/addons\r
+  . start blender\r
+  . enable then addon in  user prefs > addons\r
+  . run the script with file > import > directX\r
+\r
+13/01/12 rc 0.16\r
+. committed to svn (and littleneo git as usual)\r
+. corrected a bug about referenced token parenting\r
+. corrected a bug about non parented meshes\r
+. armatures/empties importation enabled by default\r
+. last run importer options are saved in a 'last_run' preset,\r
+  so it can be replayed or saved under another name once a\r
+  particular .x profile has been defined\r
+. tagged script for 2.6.1\r
+\r
+12/01/12 rc 0.15 :)\r
+. name conversion changed from 5 to 4 digits\r
+. matname, imagename and texturename fixes :\r
+. a path test is made at image import time with any existing data images, \r
+  so a same file cant be loaded twice wathever the naming method, / or \, rel or abs etc \r
+  (bel.material.new() and after line 835)\r
+. image and texture names should be ok now (tested with : incrediblylongname.x)\r
+. materials are replaced accordingly in existing objs when using the 'replace' naming method\r
+. fyi, the Dx exporter has the following inconveniences :\r
+    . split linked faces into individual faces\r
+    . inversed uvmapping (y axis) ?\r
+    -> see testfiles/blender_x_export/incrediblylongname.x\r
+   \r
+29 and 31/12/11\r
+. Cosmetics, code cleaning and optimizations\r
+. bpy.ops.object.select_name methods replaced with\r
+    ob.select = True\r
+    bpy.context.scene.objects.active = ob\r
+. corrected a big bug about tokens info appending in dXtree()\r
+. new bpyname() method in bel module. removed bel.common\r
+\r
+26/12/11\r
+. armature import and bone max. length option\r
+\r
+23/11/11\r
+. contrib candidate :)\r
+. solved some naming cases, added bel methods.\r
+. added experimental option about parenting (no armature yet just empties, when needed)\r
+. a bit faster\r
+. added some test files (empty parenting, armature etc)\r
+\r
+22/11/11\r
+campbell feedback (cont):\r
+. added naming methods as options (default is blender name inc. if name exists)\r
+  me and ob remove() should be ok with special cases (empties, mesh with multi-users)\r
+. improved ui\r
+\r
+\r
+21/11/11\r
+campbell feedback :\r
+. converted immutables to tuples : templates_x.py and some other vars.\r
+  http://stackoverflow.com/questions/3340539/why-tuple-is-faster-than-list\r
+. dprint() (console debug) removed, replaced by a inloop tests (a bit faster)\r
+  I'd like to keep it for now for easier debug (eg user feedbacks with 'processing' option)\r
+  \r
+19/11/11\r
+. object parenting support. parsing the x tree from roots using import_dxtree()\r
+  actually faster than before\r
+  \r
+16/11/11\r
+. weight group import\r
+. improved ui a bit and console logs\r
+. x matrices to blender ones conversion\r
+\r
+14/11/11\r
+. global matrix options\r
+. added messy code about binary (not working)\r
+\r
+11/11/11\r
+. import materials and textures (basics) : uv and image mapped (multitex mode)\r
+  and material created with tex slot if any. alpha should be ok.. ?\r
+. added a smooth options\r
+. better tolerance with faulty .x (upper/lower case of template names)\r
+. token names length from x to blender conversion should be ok also (long name cases)\r
+. corrected a parser pointer error after one array parsing case.\r
+. added more templates (for mat and tex support)\r
+. removed texture files from repo in testfile (tex does not match meshes )\r
+  added some other x files for further tests in binary and compressed format\r
+  ( http://assimp.svn.sourceforge.net/viewvc/assimp/trunk/test/models/X/ )\r
+  \r
+08/11/11\r
+. turned into an addon (fork from obj import so unused functions atm)\r
+  enable it in addon, then file > import > directx\r
+. splitted directx parser (io_directx_bel folder) and bel draft \r
+  the bel folder is intended to be located in /scripts/modules (shared components)\r
+  but it's ok in scripts/addons too (tbd)\r
+  bel folder (will) includes anything related to blender data helper (read/write)\r
+. corrected duplicated quotes for x string type\r
+\r
+07/11/11\r
+. uv import\r
+. generic directx token parser. templates items are used to read datas of any token type\r
+  a bit slower but cool since it should support non strict standard directx files\r
+  virtually it can retrieve everything from now supposing the template is know\r
+  by default or given in the file. calls are now like :\r
+               nbslots, mats = readToken('MeshMaterialList001') or\r
+               uv = readToken('uv001') or\r
+               nVerts, verts, nFaces, faces = readToken('Hydralisk_backbone1') etc\r
+. removed the specific mesh parser the 'rigid' file is the last before mutation to\r
+  generic parser. a bit faster but harder to make evolve or adapt. keep it as a faster\r
+  strict 'branch'\r
+. added some default templates\r
+  goals / wip :\r
+  . to compare template declaration in file and default one.\r
+    so either use the default one (strict) or the .x one (could differ)\r
+  . use by the generic data parser to avoid a parser for each kind of token\r
+. cleaner code (grouping methods, function names, docs etc) \r
+  functions separated from calls\r
+  renamed token dict as tokens etc\r
+. added tweaks at the beginning of the file :\r
+       chunksize = 1024     # size of file streams red in a row\r
+       quickmode = False    # this to only find meshes (no parenting, no other tokens than Mesh ones)\r
+       showtree = False     # display the entire token tree in the console\r
+       showtemplate = True  # display template datas found in file\r
+. added a patch for malformed datas of vertices (meshFaces) :\r
+       # patch for malformed datas not completely clear yet ( I guess\r
+       # there's bunch of us when looking at meshface syntax in .x files) :\r
+       # when array members are like 3;v0,v1,v2;,\r
+       # and not like 3;v0;v1;v2;, depending on template declarations.\r
+       # http://paulbourke.net/dataformats/directx/#xfilefrm_Use_of_commas\r
+. the script now generates linked faces (was not my fault !)\r
+  > it seems Dx always separate each face :\r
+  so it defines (vert * linked faces) verts for one needed vert\r
+  the readvertices loop now remove duplicates at source\r
+  it uses a verts lookup list to redirect vert id defined in faces\r
+\r
+06/11/11\r
+. vertices and faces imported from each test files\r
+. added some info to test yourself in README \r
+. switched to binary for .x as text to retrieve eol (pointer bugs). should be ok whatever it's win, mac or unix text format,\r
+  also works with mixed eol.\r
+  it seems python 3.1 can't return a 'line' when data.realine() when read mode is 'rb' (U default and universal ? really ? ;) ) \r
+  when file has mac eol (\r)\r
+  -> read(1024) in binary, decode, and replace any \r with \n. yes, it doubles lines for windows and lines value is wrong for now\r
+  -> but the used pointer value is always ok now whatever the file format and still way faster than a data.tell()\r
+  see CRCF folder to compare output wispwind.x by format.\r
+. files are still splitted into chunks (1024 B) and readable as lines\r
+. references : added 'user' fields when token is used. users store a reference with their childs but with a '*' tag at chr0.\r
+  the tree reflects the changes\r
+. now read anything and add it to the 'tree'. this includes unknow tokens.\r
+. references are recognized. by reference I mean fields like { cube0 } rather than an inline frame cube0 {\r
+  declaration.\r
+  I don't know if one item can be referenced several time or referenced before declaration\r
+  should be.. waiting for a case. for now only one 'parent' token, messages will show up\r
+  multi references to one token if cases arise. \r
+. more permissive syntax : 'frame spam{', 'frame     spam   egg{', 'frame spam egg  {'..\r
+. comments are recognized (inlines ones not done yet, since still no useful data red :) )\r
+. header is red\r
+. found other .x test files here :\r
+  http://www.xbdev.net/3dformats/x/xfileformat.php\r
+  created from 3ds max\r
+. added .x files in repo. line 70 and following to switch.\r
+. some token comes with no names, add a noname<00000> to them\r
+. console gives line number (more useful than char position I guess)\r
+\r
+\r
+05/11/11       day 0 :\r
+\r
+. made some disapointing test with ply (from a speed point of view, else it looks really cool)\r
+. made my own parser\r
+. nothing imported for now, it's more about self-eduction to .x and concept\r
+. but it reads the .x structure and can gather some info\r
+\r
+resource gathered :\r
+\r
+http://paulbourke.net/dataformats/directx/\r
+http://www.informikon.com/various/the-simplest-skeletal-animation-possible.html\r
+http://msdn.microsoft.com/en-us/library/windows/desktop/bb173011%28v=VS.85%29.aspx\r
+http://www.toymaker.info/Games/index.html\r
+\r
+\r
+\r
+step 1 : read main structure :\r
+\r
+    read main token names (any 'template', any 'frame', any 'mesh')\r
+    stores names in a token directory :\r
+        token['template'] for templates :\r
+            token['template'][templatename]\r
+            token['template'][templatename]['pointer']          (int) chr position in .x file (tell() like*)\r
+            token['template'][templatename]['line']             (int) line number in .x file\r
+        token['frame'] for frame and mesh type :\r
+            token['template'][frame or mesh name]\r
+            token['template'][frame or mesh name]['pointer']    (int) chr position in .x file (tell() like*)\r
+            token['template'][frame or mesh name]['line']       (int) line number in .x file\r
+            token['template'][frame or mesh name]['type']       (str) 'ob/bone' or 'mesh'\r
+            token['template'][frame or mesh name]['parent']     (str) frame parent of current item\r
+            token['template'][frame or mesh name]['childs']     (str list) list of child frame or mesh names\r
+            token['template'][frame or mesh name]['matrix']     (int) for now chr position of FrameTransformMatrix\r
+\r
+at the end of step 1 the script prints a tree of these datas\r
+\r
+step 2 : read template definitions :\r
+\r
+    for each template in dict, populate definitions in it.\r
+    it creates new fields in each token['template'][templatename]\r
+    according to values found in .x :\r
+        token['template'][templatename]['uuid']                 (str) <universally unique identifier>\r
+        token['template'][templatename]['members']['name']      (str) member name\r
+        token['template'][templatename]['members']['type']      (str) DWORD,FLOAT etc keywords or template name\r
+        token['template'][templatename]['restriction']          (str) 'open' , 'closed' , or the specidied (restricted) value\r
+\r
+that's all for now.\r
+\r
+idea would be to allow 2 steps importation and random access to file :\r
+\r
+    . first the file is quickly parsed. we only retrieve main info, nothing about verts, faces etc\r
+    info like number of mats, textures, objects/mesh/bone trees\r
+    for now : 150000 lines in 5 secs for step 1\r
+    . then user select what to import\r
+    . then the script retrieve selected datas according to selection, using the 'pointer' value\r
+      to seek() to the needed data, then grab/parse/translate in something usable.\r
+    . template are used at this point to know how to parse a specific part (adaptive parser)\r
+         \r
+    so far this looks fast.\r
+       \r
+tested on windows. can be important because of eol and the code I wrote to compute pointer value.\r
+(data.tell() is slow)\r
+only one .x file tested, header is : xof 0303txt 0032 (windows \r\n eol)\r
+\r
+don't know a lot about .x format :\r
+\r
+uuid : \r
+  are the member/restriction always the same for a same uuid/template ?\r
+  template name can vary for a same uuid ?\r
+syntax :\r
+  blank lines IN a stream of a {} section, after ; ?\r
+  comments // and # IN a stream of data ?\r
+  '{' and '<something>' and '}' on the same line or '{' '}' are always unique ?\r
+  \r
\ No newline at end of file
diff --git a/io_directx_bel/__init__.py b/io_directx_bel/__init__.py
new file mode 100644 (file)
index 0000000..8ce76d1
--- /dev/null
@@ -0,0 +1,303 @@
+# Blender directX importer
+bl_info = {
+    "name": "DirectX Importer",
+    "description": "Import directX Model Format (.x)",
+    "author": "Littleneo (Jerome Mahieux)",
+    "version": (0, 16),
+    "blender": (2, 6, 1),
+    "api": 42615,
+    "location": "File > Import > DirectX (.x)",
+    "warning": "",
+    "wiki_url": "https://github.com/littleneo/directX_blender/wiki",
+    "tracker_url": "https://github.com/littleneo/directX_blender/issues",
+    "category": "Import-Export",
+    "dependencies": ""
+}
+
+if "bpy" in locals():
+    import imp
+    if "import_x" in locals():
+        imp.reload(import_x)
+    #if "export_x" in locals():
+    #    imp.reload(export_x)
+
+
+import bpy
+from bpy.props import (BoolProperty,
+                       FloatProperty,
+                       StringProperty,
+                       EnumProperty,
+                       )
+from bpy_extras.io_utils import (ExportHelper,
+                                 ImportHelper,
+                                 path_reference_mode,
+                                 axis_conversion,
+                                 )
+import bel
+'''
+class DisplayTree(bpy.types.Operator) :
+    bl_idname = 'city.selector'
+    bl_label = 'preview'
+
+    def execute(self,context) :
+'''
+    
+    
+class ImportX(bpy.types.Operator, ImportHelper):
+    '''Load a Direct x File'''
+    bl_idname = "import_scene.x"
+    bl_label = "Import X"
+    bl_options = {'PRESET', 'UNDO'}
+
+    filename_ext = ".x"
+    filter_glob = StringProperty(
+            default="*.x",
+            options={'HIDDEN'},
+            )
+    show_tree = BoolProperty(
+            name="Show x tokens tree",
+            description="display relationships between x items in the console",
+            default=False,
+            )
+    show_templates = BoolProperty(
+            name="Show x templates",
+            description="display templates defined in the .x file",
+            default=False,
+            )
+    show_geninfo = BoolProperty(
+            name="Show processing",
+            description="display details for each imported thing",
+            default=False,
+            )
+    
+    quickmode = BoolProperty(
+            name="Quick mode",
+            description="only retrieve mesh basics",
+            default=False,
+            )
+    
+    parented = BoolProperty(
+            name="Object Relationships",
+            description="import armatures, empties, rebuild parent-childs relations",
+            default=True,
+            )
+    
+    bone_maxlength = FloatProperty(
+            name="Bone length",
+            description="Bones max length",
+            min=0.1, max=10.0,
+            soft_min=0.1, soft_max=10.0,
+            default=1.0,
+            )
+    
+    chunksize = EnumProperty(
+            name="Chunksize",
+            items=(('0', "all", ""),
+                   ('4096', "4KB", ""),
+                   ('2048', "2KB", ""),
+                   ('1024', "1KB", ""),
+                   ),
+            default='2048',
+            description="number of bytes red in a row",
+            )
+    naming_method = EnumProperty(
+            name="Naming method",
+            items=(('0', "increment name if exists", "blender default"),
+                   ('1', "use existing", "this only append new elements"),
+                   ('2', "rename existing", "names are forced"),
+                   ('3', "replace existing", ""),
+                   ),
+            default='0',
+            description="behaviour when a name already exists in Blender Data",
+            )
+    use_ngons = BoolProperty(
+            name="NGons",
+            description="Import faces with more then 4 verts as fgons",
+            default=True,
+            )
+    use_edges = BoolProperty(
+            name="Lines",
+            description="Import lines and faces with 2 verts as edge",
+            default=True,
+            )
+    use_smooth_groups = BoolProperty(
+            name="Smooth Groups",
+            description="Surround smooth groups by sharp edges",
+            default=True,
+            )
+
+    use_split_objects = BoolProperty(
+            name="Object",
+            description="Import OBJ Objects into Blender Objects",
+            default=True,
+            )
+    use_split_groups = BoolProperty(
+            name="Group",
+            description="Import OBJ Groups into Blender Objects",
+            default=True,
+            )
+
+    use_groups_as_vgroups = BoolProperty(
+            name="Poly Groups",
+            description="Import OBJ groups as vertex groups",
+            default=False,
+            )
+
+    use_image_search = BoolProperty(
+            name="Image Search",
+            description="Search subdirs for any assosiated images " \
+                        "(Warning, may be slow)",
+            default=True,
+            )
+
+    split_mode = EnumProperty(
+            name="Split",
+            items=(('ON', "Split", "Split geometry, omits unused verts"),
+                   ('OFF', "Keep Vert Order", "Keep vertex order from file"),
+                   ),
+            )
+
+    global_clamp_size = FloatProperty(
+            name="Clamp Scale",
+            description="Clamp the size to this maximum (Zero to Disable)",
+            min=0.0, max=1000.0,
+            soft_min=0.0, soft_max=1000.0,
+            default=0.0,
+            )
+    
+    axis_forward = EnumProperty(
+            name="Forward",
+            items=(('X', "Left (x)", ""),
+                   ('Y', "Same (y)", ""),
+                   ('Z', "Bottom (z)", ""),
+                   ('-X', "Right (-x)", ""),
+                   ('-Y', "Back (-y)", ""),
+                   ('-Z', "Up (-z)", ""),
+                   ),
+            default='-Z',
+            )
+
+    axis_up = EnumProperty(
+            name="Up",
+            items=(('X', "Right (x)", ""),
+                   ('Y', "Back (y)", ""),
+                   ('Z', "Same (z)", ""),
+                   ('-X', "Left (-x)", ""),
+                   ('-Y', "Front (-y)", ""),
+                   ('-Z', "Bottom (-z)", ""),
+                   ),
+            default='Y',
+            )
+
+    def execute(self, context):
+        from . import import_x
+        if self.split_mode == 'OFF':
+            self.use_split_objects = False
+            self.use_split_groups = False
+        else:
+            self.use_groups_as_vgroups = False
+            
+        keywords = self.as_keywords(ignore=("axis_forward",
+                                            "axis_up",
+                                            "filter_glob",
+                                            "split_mode",
+                                            ))
+
+        keywords["naming_method"] = int(self.naming_method)
+        
+        global_matrix = axis_conversion(from_forward=self.axis_forward,
+                                        from_up=self.axis_up,
+                                        ).to_4x4()
+        keywords["global_matrix"] = global_matrix
+
+    
+        bel.fs.saveOptions('import_scene.x', self.as_keywords(ignore=(
+                                            "filter_glob",
+                                            "filepath",
+                                            )))
+        return import_x.load(self, context, **keywords)
+
+    def draw(self, context):
+        layout = self.layout
+        
+        # import box
+        box = layout.box()
+        col = box.column(align=True)
+        col.label('Import Options :')  
+        col.prop(self, "chunksize")
+        col.prop(self, "use_smooth_groups")
+        actif = not(self.quickmode)
+        row = col.row()
+        row.enabled = actif
+        row.prop(self, "parented")
+        if self.parented :
+            row = col.row()
+            row.enabled = actif
+            row.prop(self, "bone_maxlength")      
+        col.prop(self, "quickmode")
+        
+        # source orientation box
+        box = layout.box()
+        col = box.column(align=True)
+        col.label('Source Orientation :')      
+        col.prop(self, "axis_forward")
+        col.prop(self, "axis_up")
+
+        # naming methods box
+        box = layout.box()
+        col = box.column(align=True)
+        col.label('Naming Method :')
+        col.props_enum(self,"naming_method")
+
+        # info/debug box
+        box = layout.box()
+        col = box.column(align=True)
+        col.label('Info / Debug :')
+        col.prop(self, "show_tree")
+        col.prop(self, "show_templates")
+        col.prop(self, "show_geninfo")
+        
+        #row = layout.row(align=True)
+        #row.prop(self, "use_ngons")
+        #row.prop(self, "use_edges")
+        
+        '''
+        box = layout.box()
+        row = box.row()
+        row.prop(self, "split_mode", expand=True)
+
+        row = box.row()
+        if self.split_mode == 'ON':
+            row.label(text="Split by:")
+            row.prop(self, "use_split_objects")
+            row.prop(self, "use_split_groups")
+        else:
+            row.prop(self, "use_groups_as_vgroups")
+
+        row = layout.split(percentage=0.67)
+        row.prop(self, "global_clamp_size")
+
+        layout.prop(self, "use_image_search")
+        '''
+
+def menu_func_import(self, context):
+    self.layout.operator(ImportX.bl_idname, text="DirectX (.x)")
+
+#def menu_func_export(self, context):
+#    self.layout.operator(ExportX.bl_idname, text="DirectX (.x)")
+
+def register():
+    bpy.utils.register_module(__name__)
+
+    bpy.types.INFO_MT_file_import.append(menu_func_import)
+    #bpy.types.INFO_MT_file_export.append(menu_func_export)
+
+
+def unregister():
+    bpy.utils.unregister_module(__name__)
+
+    bpy.types.INFO_MT_file_import.remove(menu_func_import)
+    #bpy.types.INFO_MT_file_export.remove(menu_func_export)
+
+if __name__ == "__main__":
+    register()
\ No newline at end of file
diff --git a/io_directx_bel/import_x.py b/io_directx_bel/import_x.py
new file mode 100644 (file)
index 0000000..19e60b2
--- /dev/null
@@ -0,0 +1,966 @@
+# Blender directX importer
+# version baby
+
+# litterature explaining the parser directions :
+
+# I don't want to load the whole file as it can be huge : go chunks
+# also I want random access to 3d datas to import pieces, not always everything
+# so step1 is a whole file fast parsing, retrieving tokens name and building the internal dict
+# with no 3d datas inside.
+# step 2 is to call any token by their names and retrieve the 3d datas thanks to a pointer stored in dicts
+# between stp1 and step 2 a script ui should be provided to select, transform etc before import.
+# > I need to know the pointer position of tokens but data.tell() is slow
+# a += pointer computed from line length is way faster. so I need eol -> rb mode
+# and readline() is ok in binary mode 'rb' with \r\n (win) \n (unix) but not \r mac..
+# 2chrs for windows, 1 for mac and lunix > win eol \r\n becomes \n\n (add a line)
+# mac eol \r becomes \n so win lines info are wrong
+# this also allows support for wrong files format (mixed \r and \r\n)
+# for now it only works for text format, but the used methods will be independant of the container type.
+
+# TEST FILES
+# http://assimp.svn.sourceforge.net/viewvc/assimp/trunk/test/models/X/
+
+
+import os
+import re
+import struct, binascii
+import time
+
+import bpy
+import mathutils as bmat
+from mathutils import Vector, Matrix
+
+import bel
+import bel.mesh
+import bel.image
+import bel.uv
+import bel.material
+import bel.ob
+import bel.fs
+
+from .templates_x import *
+
+'''
+# just a temp hack to reload bel everytime
+import imp
+imp.reload(bel)
+imp.reload(bel.fs)
+imp.reload(bel.image)
+imp.reload(bel.material)
+imp.reload(bel.mesh)
+imp.reload(bel.ob)
+imp.reload(bel.uv)
+'''
+
+###################################################
+
+def load(operator, context, filepath,
+         global_clamp_size=0.0,
+         show_tree=False,
+         show_templates=False,
+         show_geninfo=False,
+         quickmode=False,
+         parented=False,
+         bone_maxlength=1.0,
+         chunksize=False,
+         naming_method=0,
+         use_ngons=True,
+         use_edges=True,
+         use_smooth_groups=True,
+         use_split_objects=True,
+         use_split_groups=True,
+         use_groups_as_vgroups=False,
+         use_image_search=True,
+         global_matrix=None,
+         ):
+    
+    
+    if quickmode :
+        parented = False
+    
+    bone_minlength = bone_maxlength / 100.0
+    
+    #global templates, tokens
+    rootTokens = []
+    namelookup = {}
+    imgnamelookup = {}
+    chunksize = int(chunksize)
+    reserved_type = (
+        'dword',
+        'float',
+        'string'
+    )
+
+    '''
+        'array',
+        'Matrix4x4',
+        'Vector',
+    '''
+    '''
+    with * : defined in dXdata
+    
+    WORD     16 bits
+    * DWORD     32 bits
+    * FLOAT     IEEE float
+    DOUBLE     64 bits
+    CHAR     8 bits
+    UCHAR     8 bits
+    BYTE     8 bits
+    * STRING     NULL-terminated string
+    CSTRING     Formatted C-string (currently unsupported)
+    UNICODE     UNICODE string (currently unsupported)
+
+BINARY FORMAT
+# TOKENS in little-endian WORDs
+#define TOKEN_NAME         1
+#define TOKEN_STRING       2
+#define TOKEN_INTEGER      3
+#define TOKEN_GUID         5
+#define TOKEN_INTEGER_LIST 6
+#define TOKEN_FLOAT_LIST   7
+#define TOKEN_OBRACE      10
+#define TOKEN_CBRACE      11
+#define TOKEN_OPAREN      12
+#define TOKEN_CPAREN      13
+#define TOKEN_OBRACKET    14
+#define TOKEN_CBRACKET    15
+#define TOKEN_OANGLE      16
+#define TOKEN_CANGLE      17
+#define TOKEN_DOT         18
+#define TOKEN_COMMA       19
+#define TOKEN_SEMICOLON   20
+#define TOKEN_TEMPLATE    31
+#define TOKEN_WORD        40
+#define TOKEN_DWORD       41
+#define TOKEN_FLOAT       42
+#define TOKEN_DOUBLE      43
+#define TOKEN_CHAR        44
+#define TOKEN_UCHAR       45
+#define TOKEN_SWORD       46
+#define TOKEN_SDWORD      47
+#define TOKEN_VOID        48
+#define TOKEN_LPSTR       49
+#define TOKEN_UNICODE     50
+#define TOKEN_CSTRING     51
+#define TOKEN_ARRAY       52
+    
+    '''
+    
+    # COMMON REGEX
+    space = '[\ \t]{1,}' # at least one space / tab
+    space0 = '[\ \t]{0,}' # zero or more space / tab
+    
+    # DIRECTX REGEX TOKENS
+    r_template = r'template' + space + '[\w]*' + space0 + '\{'
+    if quickmode :
+        r_sectionname = r'Mesh' + space + '[\W-]*'
+    else :
+        r_sectionname = r'[\w]*' + space + '[\w-]*' + space0 + '\{'
+    r_refsectionname = r'\{' + space0 + '[\w-]*' + space0 + '\}'
+    r_endsection = r'\{|\}'
+    
+    # dX comments
+    r_ignore = r'#|//'
+    
+    #r_frame = r'Frame' + space + '[\w]*'
+    #r_matrix = r'FrameTransformMatrix' + space + '\{[\s\d.,-]*'
+    #r_mesh = r'Mesh' + space + '[\W]*'
+
+    ###################
+    ## STEP 1 FUNCTIONS
+    ###################
+    
+    ## HEADER
+    # returns header values or False if directx reco tag is missing
+    # assuming there's never comment header and that xof if the 1st
+    # string of the file
+    '''
+     they look like xof 0303txt 0032
+     4       Magic Number (required) "xof "
+     2       Minor Version 03
+     2       Major Version 02
+     4       Format Type (required) 
+        "txt " Text File
+        "bin " Binary File  
+        "tzip" MSZip Compressed Text File
+        "bzip" MSZip Compressed Binary File
+     4       Float Accuracy "0032" 32 bit or "0064" 64 bit
+    '''
+    def dXheader(data) :
+        l = data.read(4)
+        if l != b'xof ' :
+            print ('no header found !')
+            data.seek(0)
+            return False
+        minor = data.read(2).decode()
+        major = data.read(2).decode()
+        format = data.read(4).decode().strip()
+        accuracy = int(data.read(4).decode())
+        data.seek(0)
+        return ( minor, major, format, accuracy )
+        
+    
+    ##
+    def dXtree(data,quickmode = False) :
+        tokens = {}
+        templates = {}
+        tokentypes = {}
+        c = 0
+        lvl = 0
+        tree = ['']
+        ptr = 0
+        eol = 0
+        trunkated = False
+        previouslvl = False
+        while True :
+        #for l in data.readlines() :
+            lines, trunkated = nextFileChunk(data,trunkated)
+            if lines == None : break
+            for l in lines :
+                
+                # compute pointer position
+                ptr += eol
+                c += 1
+                eol = len(l) + 1
+                #print(c,data.tell(),ptr+eol)
+                #if l != '' : print('***',l)
+                #if l == ''  : break
+                l = l.strip()
+                
+                # remove blank and comment lines
+                if l == '' or re.match(r_ignore,l) :
+                    continue
+                
+                # one line token cases level switch
+                if previouslvl :
+                    lvl -= 1
+                    previouslvl = False
+                
+                #print('%s lines in %.2f\''%(c,time.clock()-t),end='\r')
+                #print(c,len(l)+1,ptr,data.tell())
+                if '{' in l :
+                    lvl += 1
+                    if '}' in l : previouslvl = True #; print('got one line token : \n%s'%l)
+                elif '}' in l :
+                    lvl -= 1
+                #print(c,lvl,tree)
+                
+                if quickmode == False :
+                    ## look for templates
+                    if re.match(r_template,l) :
+                        tname = l.split(' ')[1]
+                        templates[tname] = {'pointer' : ptr, 'line' : c}
+                        continue
+    
+                    ## look for {references}
+                    if re.match(r_refsectionname,l) :
+                        refname = namelookup[ l[1:-1].strip() ]
+                        #print('FOUND reference to %s in %s at line %s (level %s)'%(refname,tree[lvl-1],c,lvl))
+                        #tree = tree[0:lvl]
+                        parent = tree[lvl-1]
+                        # tag it as a reference, since it's not exactly a child.
+                        # put it in childs since order can matter in sub tokens declaration
+                        tokens[parent]['childs'].append('*'+refname)
+                        if refname not in tokens :
+                            print('reference to %s done before its declaration (line %s)\ncreated dummy'%(refname,c))
+                            tokens[refname] = {}
+                        if 'user' not in tokens[refname] : tokens[refname]['users'] = [parent]
+                        else : tokens[refname]['users'].append(parent)
+                        continue
+    
+                ## look for any token or only Mesh token in quickmode
+                if re.match(r_sectionname,l) :
+                    tokenname = getName(l,tokens)
+                    #print('FOUND %s %s %s %s'%(tokenname,c,lvl,tree))
+                    #print('pointer %s %s'%(data.tell(),ptr))
+                    if lvl == 1 : rootTokens.append(tokenname)
+                    typ = l.split(' ')[0].strip().lower()
+                    tree = tree[0:lvl]
+                    if typ not in tokentypes : tokentypes[typ] = [tokenname]
+                    else : tokentypes[typ].append(tokenname)
+                    parent = tree[-1]
+                    if tokenname in tokens :
+                        tokens[tokenname]['pointer'] = ptr
+                        tokens[tokenname]['line'] = c
+                        tokens[tokenname]['parent'] = parent
+                        tokens[tokenname]['childs'] = []
+                        tokens[tokenname]['type'] = typ
+                        
+                    else : tokens[tokenname] = {'pointer': ptr,
+                                                'line'   : c,
+                                                'parent' : parent,
+                                                'childs' : [],
+                                                'users'  : [],
+                                                'type'   : typ
+                                                }
+                    tree.append(tokenname)
+                    if lvl > 1 and quickmode == False :
+                        tokens[parent]['childs'].append(tokenname)
+                    
+        return tokens, templates, tokentypes
+        
+    ## returns file binary chunks
+    def nextFileChunk(data,trunkated=False,chunksize=1024) :
+        if chunksize == 0 : chunk = data.read()
+        else : chunk = data.read(chunksize)
+        if format == 'txt' :
+            lines = chunk.decode('utf-8', errors='ignore')
+            #if stream : return lines.replace('\r','').replace('\n','')
+            lines = lines.replace('\r','\n').split('\n')
+            if trunkated : lines[0] = trunkated + lines[0]
+            if len(lines) == 1 : 
+                if lines[0] == '' : return None, None
+                return lines, False
+            return lines, lines.pop()
+        # wip, todo for binaries
+        else :
+            print(chunk)
+            for word in range(0,len(chunk)) :
+                w = chunk[word:word+4]
+                print(word,w,struct.unpack("<l", w),binascii.unhexlify(w))
+
+    
+    # name unnamed tokens, watchout for x duplicate
+    # for blender, referenced token in x should be named and unique..
+    def getName(l,tokens) :
+        xnam = l.split(' ')[1].strip()
+        
+        #if xnam[0] == '{' : xnam = ''
+        if xnam and xnam[-1] == '{' : xnam = xnam[:-1]
+        
+        name = xnam
+        if len(name) == 0 : name = l.split(' ')[0].strip()
+        
+        namelookup[xnam] = bel.bpyname(name,tokens,4)
+
+        return namelookup[xnam]
+    
+    
+    ###################
+    ## STEP 2 FUNCTIONS
+    ###################
+    # once the internal dict is populated the functions below can be used
+    
+    ## from a list of tokens, displays every child, users and references
+    '''
+      walk_dxtree( [ 'Mesh01', 'Mesh02' ] ) # for particular pieces
+      walk_dxtree(tokens.keys()) for the whole tree
+    '''
+    def walk_dXtree(field,lvl=0,tab='') :
+        for fi, tokenname in enumerate(field) :
+            if lvl > 0 or tokens[tokenname]['parent'] == '' :
+                if tokenname not in tokens :
+                    tokenname = tokenname[1:]
+                    ref = 'ref: '
+                else : ref = False
+                
+                frame_type = tokens[tokenname]['type']
+                line = ('{:7}'.format(tokens[tokenname]['line']))
+                log = ' %s%s (%s)'%( ref if ref else '', tokenname, frame_type )
+                print('%s.%s%s'%(line, tab, log))
+                if fi == len(field) - 1 : tab = tab[:-3] + '   '
+    
+                if ref == False :
+                    for user in tokens[tokenname]['users'] :
+                         print('%s.%s |__ user: %s'%(line, tab.replace('_',' '), user))
+                    walk_dXtree(tokens[tokenname]['childs'],lvl+1,tab.replace('_',' ')+' |__')
+                
+                if fi == len(field) - 1 and len(tokens[tokenname]['childs']) == 0 :
+                    print('%s.%s'%(line,tab))
+    
+    ## remove eol, comments, spaces from a raw block of datas
+    def cleanBlock(block) :
+        while '//' in block :
+            s = block.index('//')
+            e = block.index('\n',s+1)
+            block = block[0:s] + block[e:]
+        while '#' in block :
+            s = block.index('#')
+            e = block.index('\n',s+1)
+            block = block[0:s] + block[e:]
+        block = block.replace('\n','').replace(' ','').replace('\t ','')
+        return block
+        
+    def readToken(tokenname) :
+        token = tokens[tokenname]
+        datatype = token['type'].lower()
+        if datatype in templates : tpl = templates[datatype]
+        elif datatype in defaultTemplates : tpl = defaultTemplates[datatype]
+        else :
+            print("can't find any template to read %s (type : %s)"%(tokenname,datatype))
+            return False
+        #print('> use template %s'%datatype)
+        block = readBlock(data,token)
+        ptr = 0
+        #return dXtemplateData(tpl,block)
+        fields, ptr = dXtemplateData(tpl,block)
+        if datatype in templatesConvert :
+            fields = eval( templatesConvert[datatype] )
+        return fields
+    
+    def dXtemplateData(tpl,block,ptr=0) :
+        #print('dxTPL',block[ptr])
+        pack = []
+        for member in tpl['members'] :
+            #print(member)
+            dataname = member[-1]
+            datatype = member[0].lower()
+            if datatype ==  'array' :
+                datatype = member[1].lower()
+                s = dataname.index('[') + 1
+                e = dataname.index(']')
+                #print(dataname[s:e])
+                length = eval(dataname[s:e])
+                #print("array %s type %s length defined by '%s' : %s"%(dataname[:s-1],datatype,dataname[s:e],length))
+                dataname = dataname[:s-1]
+                datavalue, ptr = dXarray(block, datatype, length, ptr)
+                #print('back to %s'%(dataname))
+            else :
+                length = 1
+                datavalue, ptr = dXdata(block, datatype, length, ptr)
+    
+            #if len(str(datavalue)) > 50 : dispvalue = str(datavalue[0:25]) + ' [...] ' + str(datavalue[-25:])
+            #else : dispvalue = str(datavalue)
+            #print('%s :  %s %s'%(dataname,dispvalue,type(datavalue)))
+            exec('%s = datavalue'%(dataname))
+            pack.append( datavalue )
+        return pack, ptr + 1
+    
+    def dXdata(block,datatype,length,s=0,eof=';') :
+        #print('dxDTA',block[s])
+        # at last, the data we need
+        # should be a ';' but one meet ',' often, like in meshface
+        if datatype == 'dword' :
+            e = block.index(';',s+1)
+            try : field = int(block[s:e])
+            except :
+                e = block.index(',',s+1)
+                field = int(block[s:e])
+            return field, e+1
+        elif datatype == 'float' :
+            e = block.index(eof,s+1)
+            return float(block[s:e]), e+1
+        elif datatype == 'string' :
+            e = block.index(eof,s+1)
+            return str(block[s+1:e-1]) , e+1
+        else :
+            if datatype in templates : tpl = templates[datatype]
+            elif datatype in defaultTemplates : tpl = defaultTemplates[datatype]
+            else :
+                print("can't find any template for type : %s"%(datatype))
+                return False
+            #print('> use template %s'%datatype)
+            fields, ptr = dXtemplateData(tpl,block,s)
+            if datatype in templatesConvert :
+                fields = eval( templatesConvert[datatype] )
+            return fields, ptr
+    
+    def dXarray(block, datatype, length, s=0) :
+        #print('dxARR',block[s])
+        lst = []
+        if datatype in reserved_type :
+            eoi=','
+            for i in range(length) :
+                if i+1 == length : eoi = ';'
+                datavalue, s = dXdata(block,datatype,1,s,eoi)
+                lst.append( datavalue )
+            
+        else :
+            eoi = ';,'
+            for i in range(length) :
+                if i+1 == length : eoi = ';;'
+                #print(eoi)
+                e = block.index(eoi,s)
+                #except : print(block,s) ; popo()
+                datavalue, na = dXdata(block[s:e+1],datatype,1)
+                lst.append( datavalue )
+                s = e + 2
+        return lst, s
+    
+    ###################################################
+
+    ## populate a template with its datas
+    # this make them available in the internal dict. should be used in step 2 for unknown data type at least
+    def readTemplate(data,tpl_name,display=False) :
+        ptr = templates[tpl_name]['pointer']
+        line = templates[tpl_name]['line']
+        #print('> %s at line %s (chr %s)'%(tpl_name,line,ptr))
+        data.seek(ptr)
+        block = ''
+        trunkated = False
+        go = True
+        while go :
+            lines, trunkated = nextFileChunk(data,trunkated,chunksize) # stream ?
+            if lines == None : 
+                break
+            for l in lines :
+                #l = data.readline().decode().strip()
+                block += l.strip()
+                if '}' in l :
+                    go = False
+                    break
+        
+        uuid = re.search(r'<.+>',block).group()
+        templates[tpl_name]['uuid'] = uuid.lower()
+        templates[tpl_name]['members'] = []
+        templates[tpl_name]['restriction'] = 'closed'
+        
+        members = re.search(r'>.+',block).group()[1:-1].split(';')
+        for member in members :
+            if member == '' : continue
+            if member[0] == '[' :
+                templates[tpl_name]['restriction'] = member
+                continue  
+            templates[tpl_name]['members'].append( member.split(' ') )
+    
+        if display : 
+            print('\ntemplate %s :'%tpl_name)
+            for k,v in templates[tpl_name].items() :
+                if k != 'members' :
+                    print('  %s : %s'%(k,v))
+                else :
+                    for member in v :
+                        print('  %s'%str(member)[1:-1].replace(',',' ').replace("'",''))
+                
+            if tpl_name in defaultTemplates :
+                defaultTemplates[tpl_name]['line'] = templates[tpl_name]['line']
+                defaultTemplates[tpl_name]['pointer'] = templates[tpl_name]['pointer']
+                if defaultTemplates[tpl_name] != templates[tpl_name] :
+                    print('! DIFFERS FROM BUILTIN TEMPLATE :')
+                    print('raw template %s :'%tpl_name)
+                    print(templates[tpl_name])
+                    print('raw default template %s :'%tpl_name)
+                    print(defaultTemplates[tpl_name])
+                    #for k,v in defaultTemplates[tpl_name].items() :
+                    #    if k != 'members' :
+                    #        print('  %s : %s'%(k,v))
+                    #    else :
+                    #        for member in v :
+                    #            print('  %s'%str(member)[1:-1].replace(',',' ').replace("'",''))
+                else :
+                    print('MATCHES BUILTIN TEMPLATE')
+    
+            
+    ##  read any kind of token data block
+    # by default the block is cleaned from inline comment space etc to allow data parsing
+    # useclean = False (retrieve all bytes) if you need to compute a file byte pointer
+    # to mimic the file.tell() function and use it with file.seek()
+    def readBlock(data,token, clean=True) :
+        ptr = token['pointer']
+        data.seek(ptr)
+        block = ''
+        #lvl = 0
+        trunkated = False
+        go = True
+        while go :
+            lines, trunkated = nextFileChunk(data,trunkated,chunksize)
+            if lines == None : break
+            for l in lines :
+                #eol = len(l) + 1
+                l = l.strip()
+                #c += 1
+                block += l+'\n'
+                if re.match(r_endsection,l) :
+                    go = False
+                    break
+        s = block.index('{') + 1
+        e = block.index('}')
+        block = block[s:e]
+        if clean : block = cleanBlock(block)
+        return block
+    
+    def getChilds(tokenname) :
+        childs = []
+        # '*' in childname means it's a reference. always perform this test
+        # when using the childs field
+        for childname in tokens[tokenname]['childs'] :
+            if childname[0] == '*' : childname = childname[1:]
+            childs.append( childname )
+        return childs
+    
+       # the input nested list of [bonename, matrix, [child0,child1..]] is given by import_dXtree()
+    def buildArm(armdata, child,lvl=0,parent_matrix=False) :
+        
+        bonename, bonemat, bonechilds = child
+        
+        if lvl == 0 :
+            armname = armdata
+            armdata = bpy.data.armatures.new(name=armname)
+            arm = bpy.data.objects.new(armname,armdata)
+            bpy.context.scene.objects.link(arm)
+            arm.select = True
+            bpy.context.scene.objects.active = arm
+            bpy.ops.object.mode_set(mode='EDIT')
+            parent_matrix = Matrix()
+        
+        bone = armdata.edit_bones.new(name=bonename)
+        bonematW = parent_matrix * bonemat
+        bone.head = bonematW.to_translation()
+        #bone.roll.. ?
+        bone_length = bone_maxlength
+        for bonechild in bonechilds :
+            bonechild = buildArm(armdata,bonechild,lvl+1,bonematW)
+            bonechild.parent = bone
+            bone_length = min((bonechild.head - bone.head).length, bone_length)
+        bone.tail = bonematW * Vector((0,bone_length,0))
+        if lvl == 0 :
+            bpy.ops.object.mode_set(mode='OBJECT')
+            return arm
+        return bone
+    
+    def import_dXtree(field,lvl=0) :
+        tab = ' '*lvl*2
+        if field == [] : 
+            if show_geninfo : print('%s>> no childs, return False'%(tab))
+            return False
+        ob = False
+        mat = False
+        is_root = False
+        frames = []
+        obs = []
+        
+        parentname = tokens[field[0]]['parent']
+        if show_geninfo : print('%s>>childs in frame %s :'%(tab,parentname))
+        
+        for tokenname in field :
+
+            tokentype = tokens[tokenname]['type']
+            
+            # frames can contain more than one mesh
+            if tokentype  == 'mesh' :
+                # object and mesh naming :
+                # if parent frame has several meshes : obname = meshname = mesh token name,
+                # if parent frame has only one mesh  : obname = parent frame name, meshname =  mesh token name.
+                if parentname :
+                    meshcount = 0
+                    for child in getChilds(parentname) :
+                        if tokens[child]['type'] == 'mesh' : 
+                            meshcount += 1
+                            if meshcount == 2 :
+                                parentname = tokenname
+                                break
+                else : parentname = tokenname
+                
+                ob = getMesh(parentname,tokenname)
+                obs.append(ob)
+
+                if show_geninfo : print('%smesh : %s'%(tab,tokenname))
+            
+            # frames contain one matrix (empty or bone)
+            elif tokentype  == 'frametransformmatrix' :
+                [mat] = readToken(tokenname)
+                if show_geninfo : print('%smatrix : %s'%(tab,tokenname))
+            
+            # frames can contain 0 or more frames
+            elif tokentype  == 'frame' :
+                frames.append(tokenname)
+                if show_geninfo : print('%sframe : %s'%(tab,tokenname))
+        
+        # matrix is used for mesh transform if some mesh(es) exist(s)      
+        if ob :
+            is_root = True
+            if mat == False :
+                mat = Matrix()
+                if show_geninfo : print('%smesh token without matrix, set it to default\n%splease report in bug tracker if you read this !'%(tab,tab))
+            if parentname == '' : 
+                mat = mat * global_matrix
+            if len(obs) == 1 :
+                ob.matrix_world = mat
+            else :
+                ob = bel.ob.new(parentname, None, naming_method)
+                ob.matrix_world = mat
+                for child in obs :
+                    child.parent = ob
+        
+        # matrix only, store it as a list as we don't know if
+        # it's a bone or an empty yet
+        elif mat :
+            ob = [parentname, mat,[]]
+
+        # nothing case ?
+        else :
+            ob = [parentname, Matrix() * global_matrix,[]]
+            if show_geninfo : print('%snothing here'%(tab))
+
+        childs = []
+        
+        for tokenname in frames :
+            if show_geninfo : print('%s<Begin %s :'%(tab,tokenname))
+            
+            # child is either False, empty, object, or a list or undefined name matrices hierarchy
+            child = import_dXtree(getChilds(tokenname),lvl+1)
+            if child and type(child) != list :
+                is_root = True
+            childs.append( [tokenname, child] )
+            if show_geninfo : print('%sEnd %s>'%(tab,tokenname))
+        
+        if is_root and parentname != '' :
+            
+            if show_geninfo : print('%send of tree a this point'%(tab))
+            if type(ob) == list :
+                mat = ob[1]
+                ob = bel.ob.new(parentname, None, naming_method)
+            ob.matrix_world = mat
+            
+        for tokenname, child in childs :
+            if show_geninfo : print('%sbegin2 %s>'%(tab,tokenname))
+            # returned a list of object(s) or matrice(s)
+            if child :
+
+                # current frame is an object or an empty, we parent this frame to it
+                #if eot or (ob and ( type(ob.data) == type(None) or type(ob.data) == bpy.types.Mesh ) ) :
+                if is_root :
+                    # this branch is an armature, convert it
+                    if type(child) == list :
+                        if show_geninfo : print('%sconvert to armature %s'%(tab,tokenname))
+                        child = buildArm(tokenname, child)
+                        
+                    # parent the obj/empty/arm to current
+                    # or apply the global user defined matrix to the object root
+                    if parentname != '' :
+                        child.parent = ob
+                    else :
+                        child.matrix_world = global_matrix
+                        
+                # returned a list of parented matrices. append it in childs list
+                elif type(child[0]) == str :
+                    ob[2].append(child)
+
+                # child is an empty or a mesh, so current frame is an empty, not an armature
+                elif ob and ( type(child.data) == type(None) or type(child.data) == bpy.types.Mesh ) :
+                    #print('  child data type: %s'%type(child.data))
+                    child.parent = ob
+                    #print('%s parented to %s'%(child.name,ob.name))
+                
+            # returned False
+            else :
+                 if show_geninfo : print('%sreturned %s, nothing'%(tab,child))
+
+        #print('>> %s return %s'%(field,ob))
+        return ob# if ob else False
+
+    # build from mesh token type
+    def getMesh(obname,tokenname,debug = False):
+    
+        if debug : print('\nmesh name : %s'%tokenname)
+        
+        verts = []
+        edges = []
+        faces = []
+        matslots = []
+        facemats = []
+        uvs = []
+        groupnames = []
+        groupindices = []
+        groupweights = []
+
+        nVerts, verts, nFaces, faces = readToken(tokenname) 
+
+        if debug :
+            print('verts    : %s %s\nfaces    : %s %s'%(nVerts, len(verts),nFaces, len(faces)))
+        
+        #for childname in token['childs'] :
+        for childname in getChilds(tokenname) :
+            
+            tokentype = tokens[childname]['type']
+            
+            # UV
+            if tokentype == 'meshtexturecoords' :
+                uv = readToken(childname)
+                uv = bel.uv.asVertsLocation(uv, faces)
+                uvs.append(uv)
+                
+                if debug : print('uv       : %s'%(len(uv)))
+            
+            # MATERIALS
+            elif tokentype == 'meshmateriallist' :
+                nbslots, facemats = readToken(childname)
+                
+                if debug : print('facemats : %s'%(len(facemats)))
+                
+                # mat can exist but with no datas so we prepare the mat slot
+                # with dummy ones
+                for slot in range(nbslots) :
+                    matslots.append('dXnoname%s'%slot )
+        
+                # length does not match (could be tuned more, need more cases)
+                if len(facemats) != len(faces) :
+                    facemats = [ facemats[0] for i in faces ]
+
+                # seek for materials then textures if any mapped in this mesh.
+                # no type test, only one option type in token meshmateriallist : 'Material'
+                for slotid, matname in enumerate(getChilds(childname)) :
+                    
+                    # rename dummy mats with the right name
+                    matslots[slotid] = matname
+
+                    # blender material creation (need tuning)
+                    mat = bel.material.new(matname,naming_method)
+                    matslots[slotid] = mat.name
+                    
+                    if naming_method != 1 :
+                        #print('matname : %s'%matname)
+                        (diffuse_color,alpha), power, specCol, emitCol = readToken(matname)
+                        #if debug : print(diffuse_color,alpha, power, specCol, emitCol)
+                        mat.diffuse_color = diffuse_color
+                        mat.diffuse_intensity = power
+                        mat.specular_color = specCol
+                        # dX emit don't use diffuse color but is a color itself
+                        # convert it to a kind of intensity 
+                        mat.emit = (emitCol[0] + emitCol[1] + emitCol[2] ) / 3
+                        
+                        if alpha != 1.0 :
+                            mat.use_transparency = True
+                            mat.transparency_method = 'Z_TRANSPARENCY'
+                            mat.alpha = alpha
+                            mat.specular_alpha = 0
+                            transp = True
+                        else : transp = False
+            
+                        # texture
+                        # only 'TextureFilename' can be here, no type test
+                        # textures have no name in .x so we build 
+                        # image and texture names from the image file name
+                        # bdata texture slot name = bdata image name
+                        btexnames = []
+                        for texname in getChilds(matname) :
+                            
+                            # create/rename/reuse etc corresponding data image
+                            # (returns False if not found)
+                            [filename] = readToken(texname)
+                            img = bel.image.new(path+'/'+filename)
+                            
+                            if img == False :
+                                imgname = 'not_found'
+                            else :
+                                imgname = img.name
+                                
+                            #print('texname : %s'%texname)
+                            #print('filename : %s'%filename)
+                            #print('btex/img name : %s'%imgname)
+                            
+                            # associated texture (no naming check.. maybe tune more)
+                            # tex and texslot are created even if img not found
+                            if imgname in bpy.data.textures and ( img == False or bpy.data.textures[imgname].image == img ) :
+                                tex = bpy.data.textures[imgname]
+                            else :
+                                tex = bpy.data.textures.new(name=imgname,type='IMAGE')
+                                if img : tex.image = img
+                                
+                            tex.use_alpha = transp
+                            tex.use_preview_alpha = transp
+                                
+                            # then create texture slot
+                            texslot = mat.texture_slots.create(index=0)
+                            texslot.texture = tex
+                            texslot.texture_coords = 'UV'
+                            texslot.uv_layer = 'UV0'
+                            texslot.use_map_alpha = transp
+                            texslot.alpha_factor = alpha
+
+                # create remaining dummy mat
+                for slotid, matname in enumerate(matslots) :
+                    if matname not in bpy.data.materials :
+                        mat = bel.material.new(matname,naming_method)
+                        matslots[slotid] = mat.name
+                        
+                if debug : print('matslots : %s'%matslots)
+                
+            # VERTICES GROUPS/WEIGHTS
+            elif tokentype == 'skinweights' :
+                groupname, nverts, vindices, vweights, mat = readToken(childname)
+                groupname = namelookup[groupname]
+                if debug : 
+                    print('vgroup    : %s (%s/%s verts) %s'%(groupname,len(vindices),len(vweights),'bone' if groupname in tokens else ''))
+
+                #if debug : print('matrix : %s\n%s'%(type(mat),mat))
+                
+                groupnames.append(groupname)
+                groupindices.append(vindices)
+                groupweights.append(vweights)
+                
+        ob = bel.mesh.write(obname,tokenname, 
+                            verts, edges, faces, 
+                            matslots, facemats, uvs, 
+                            groupnames, groupindices, groupweights,
+                            use_smooth_groups,
+                            naming_method)
+        
+        return ob
+                           
+    ## here we go
+     
+    file = os.path.basename(filepath)
+    
+    print('\nimporting %s...'%file)
+    start = time.clock()
+    path = os.path.dirname(filepath)
+    filepath = os.fsencode(filepath)
+    data = open(filepath,'rb')
+    header = dXheader(data)
+
+    if global_matrix is None:
+        global_matrix = mathutils.Matrix()
+
+    if header :
+        minor, major, format, accuracy = header
+        
+        if show_geninfo :
+            print('\n%s directX header'%file)
+            print('  minor  : %s'%(minor))
+            print('  major  : %s'%(major))
+            print('  format : %s'%(format))
+            print('  floats are %s bits'%(accuracy))
+
+        if format in [ 'txt' ] : #, 'bin' ] :
+
+            ## FILE READ : STEP 1 : STRUCTURE
+            if show_geninfo : print('\nBuilding internal .x tree')
+            t = time.clock()
+            tokens, templates, tokentypes = dXtree(data,quickmode)
+            readstruct_time = time.clock()-t
+            if show_geninfo : print('builded tree in %.2f\''%(readstruct_time)) # ,end='\r')
+
+            ## populate templates with datas
+            for tplname in templates :
+                readTemplate(data,tplname,show_templates)
+
+            ## DATA TREE CHECK
+            if show_tree :
+                print('\nDirectX Data Tree :\n')
+                walk_dXtree(tokens.keys())
+            
+            ## DATA IMPORTATION
+            if show_geninfo : 
+                print(tokens)
+                print('Root frames :\n %s'%rootTokens)
+            if parented :
+                import_dXtree(rootTokens)
+            else :
+                for tokenname in tokentypes['mesh'] :
+                    obname = tokens[tokenname]['parent']
+                    # object and mesh naming :
+                    # if parent frame has several meshes : obname = meshname = mesh token name,
+                    # if parent frame has only one mesh  : obname = parent frame name, meshname =  mesh token name.
+                    if obname :
+                        meshcount = 0
+                        for child in getChilds(obname) :
+                            if tokens[child]['type'] == 'mesh' : 
+                                meshcount += 1
+                                if meshcount == 2 :
+                                    obname = tokenname
+                                    break
+                    else : obname = tokenname
+
+                    ob = getMesh(obname,tokenname,show_geninfo)
+                    ob.matrix_world = global_matrix
+                    
+            print('done in %.2f\''%(time.clock()-start)) # ,end='\r')
+            
+        else :
+            print('only .x files in text format are currently supported')
+            print('please share your file to make the importer evolve')
+
+
+        return {'FINISHED'}
+        
\ No newline at end of file
diff --git a/io_directx_bel/templates_x.py b/io_directx_bel/templates_x.py
new file mode 100644 (file)
index 0000000..7b51e0c
--- /dev/null
@@ -0,0 +1,162 @@
+# dx 'strict' templates
+# lower() since some apps does not respect it,
+# and to keep the 'as it should be' syntax
+
+defaultTemplates={}
+templatesConvert={}
+
+# mesh template
+defaultTemplates['Mesh'.lower()] = {
+    'uuid' : '<3d82ab44-62da-11cf-ab39-0020af71e433>',
+    'restriction' : '[...]',
+    'members' : (
+        ('dword', 'nVertices'),
+        ('array', 'vector', 'vertices[nVertices]'),
+        ('dword', 'nFaces'),
+        ('array', 'MeshFace', 'faces[nFaces]'),
+    )
+}
+
+defaultTemplates['FrameTransformMatrix'.lower()] = {
+    'uuid' : '<f6f23f41-7686-11cf-8f52-0040333594a3>',
+    'restriction' : 'closed',
+    'members' : (
+        ('matrix4x4', 'frameMatrix'),
+    )
+}
+
+templatesConvert['matrix4x4'] = 'Matrix( [fields[0][0:4], fields[0][4:8] , fields[0][8:12] , fields[0][12:16]] )'
+defaultTemplates['matrix4x4'.lower()] = {
+    'uuid' : '<f6f23f45-7686-11cf-8f52-0040333594a3>',
+    'restriction' : 'closed',
+    'members' : (
+        ('array', 'float', 'matrix[16]'),
+    )
+}
+
+#returns [ [vid0,vid1,vid2], [vid1,vid2,vid3,vid4] .. ]
+templatesConvert['meshface'] = 'fields[1]'
+defaultTemplates['MeshFace'.lower()] = {
+    'uuid' : '<3d82ab5f-62da-11cf-ab39-0020af71e433>',
+    'restriction' : 'closed',
+    'members' : (
+        ('dword', 'nFaceVertexIndices'),
+        ('array', 'dword', 'faceVertexIndices[nFaceVertexIndices]')
+    )
+}
+
+defaultTemplates['vector'.lower()] = {
+    'uuid' : '<3d82ab5e-62da-11cf-ab39-0020af71e433>',
+    'restriction' : 'closed',
+    'members' : (
+        ('float', 'x'),
+        ('float', 'y'),
+        ('float', 'z')
+    )
+}
+
+defaultTemplates['Coords2d'.lower()] = {
+    'uuid' : '<f6f23f44-7686-11cf-8f52-0040333594a3>',
+    'restriction' : 'closed',
+    'members' : (
+        ('float', 'u'),
+        ('float', 'v')
+    )
+}
+
+# returns [ uvAsVertsLocation ]
+templatesConvert['meshtexturecoords'] = 'fields[1]'
+defaultTemplates['MeshTextureCoords'.lower()] = {
+    'uuid' : '<f6f23f40-7686-11cf-8f52-0040333594a3>',
+    'restriction' : 'closed',
+    'members' : (
+        ('dword', 'nTextureCoords'),
+        ('array', 'Coords2d', 'textureCoords[nTextureCoords]')
+    )
+}
+
+defaultTemplates['meshnormals'.lower()] = {
+    'uuid' : '<f6f23f43-7686-11cf-8f52-0040333594a3>',
+    'restriction' : 'closed',
+    'members' : (
+        ('dword', 'nNormals'),
+        ('array', 'vector', 'normals[nNormals]'),
+        ('dword', 'nFaceNormals'),
+        ('array', 'MeshFace', 'faceNormals[nFaceNormals]')
+    )
+}
+
+# returns [ nMaterials, [ materialindex of each face ] ]
+templatesConvert['meshmateriallist'] = '[fields[0],fields[2]]'
+defaultTemplates['MeshMaterialList'.lower()] = {
+    'uuid' : '<f6f23f42-7686-11cf-8f52-0040333594a3>',
+    'restriction' : '[Material]',
+    'members' : (
+        ('dword', 'nMaterials'),
+        ('dword', 'nFaceIndexes'),
+        ('array', 'dword', 'faceIndexes[nFaceIndexes]')
+    )
+}
+
+defaultTemplates['Material'.lower()] = {
+    'uuid' : '<3d82ab4d-62da-11cf-ab39-0020af71e433>',
+    'restriction' : '[...]',
+    'members' : (
+        ('colorrgba', 'faceColor'),
+        ('float', 'power'),
+        ('colorrgb', 'specularColor'),
+        ('colorrgb', 'emissiveColor')
+    )
+}
+
+templatesConvert['colorrgba'] = 'fields[:3],fields[3]'
+defaultTemplates['colorrgba'.lower()] = {
+    'uuid' : '<35ff44e0-6c7c-11cf-8f52-0040333594a3>',
+    'restriction' : 'closed',
+    'members' : (
+        ('float', 'red'),
+        ('float', 'green'),
+        ('float', 'blue'),
+        ('float', 'alpha')
+    )
+}
+
+defaultTemplates['colorrgb'.lower()] = {
+    'uuid' : '<d3e16e81-7835-11cf-8f52-0040333594a3>',
+    'restriction' : 'closed',
+    'members' : (
+        ('float', 'red'),
+        ('float', 'green'),
+        ('float', 'blue')
+    )
+}
+
+defaultTemplates['TextureFilename'.lower()] = {
+    'uuid' : '<a42790e1-7810-11cf-8f52-0040333594a3>',
+    'restriction' : 'closed',
+    'members' : (
+        ('string', 'filename'),
+    )
+}
+
+defaultTemplates['SkinWeights'.lower()] = {
+    'uuid' : '<6f0d123b-bad2-4167-a0d0-80224f25fabb>',
+    'restriction' : 'closed',
+    'members' : (
+        ('string', 'transformNodeName'),
+        ('dword', 'nWeights'),
+        ('array', 'dword', 'vertexIndices[nWeights]'),
+        ('array', 'float', 'weights[nWeights]'),
+        ('matrix4x4', 'matrixOffset')
+    )
+}
+
+defaultTemplates['XSkinMeshHeader'.lower()] = {
+    'uuid' : '3cf169ce-ff7c-44ab-93c0-f78f62d172e2',
+    'restriction' : 'closed',
+    'members' : (
+        ('word', 'nMaxSkinWeightsPerVertex'),
+        ('word', 'nMaxSkinWeightsPerFace'),
+        ('word', 'nBones')
+    )
+}