19e60b29ebcab8c9535b8b560090b99ccef32225
[blender-addons-contrib.git] / io_directx_bel / import_x.py
1 # Blender directX importer
2 # version baby
3
4 # litterature explaining the parser directions :
5
6 # I don't want to load the whole file as it can be huge : go chunks
7 # also I want random access to 3d datas to import pieces, not always everything
8 # so step1 is a whole file fast parsing, retrieving tokens name and building the internal dict
9 # with no 3d datas inside.
10 # step 2 is to call any token by their names and retrieve the 3d datas thanks to a pointer stored in dicts
11 # between stp1 and step 2 a script ui should be provided to select, transform etc before import.
12 # > I need to know the pointer position of tokens but data.tell() is slow
13 # a += pointer computed from line length is way faster. so I need eol -> rb mode
14 # and readline() is ok in binary mode 'rb' with \r\n (win) \n (unix) but not \r mac..
15 # 2chrs for windows, 1 for mac and lunix > win eol \r\n becomes \n\n (add a line)
16 # mac eol \r becomes \n so win lines info are wrong
17 # this also allows support for wrong files format (mixed \r and \r\n)
18 # for now it only works for text format, but the used methods will be independant of the container type.
19
20 # TEST FILES
21 # http://assimp.svn.sourceforge.net/viewvc/assimp/trunk/test/models/X/
22
23
24 import os
25 import re
26 import struct, binascii
27 import time
28
29 import bpy
30 import mathutils as bmat
31 from mathutils import Vector, Matrix
32
33 import bel
34 import bel.mesh
35 import bel.image
36 import bel.uv
37 import bel.material
38 import bel.ob
39 import bel.fs
40
41 from .templates_x import *
42
43 '''
44 # just a temp hack to reload bel everytime
45 import imp
46 imp.reload(bel)
47 imp.reload(bel.fs)
48 imp.reload(bel.image)
49 imp.reload(bel.material)
50 imp.reload(bel.mesh)
51 imp.reload(bel.ob)
52 imp.reload(bel.uv)
53 '''
54
55 ###################################################
56
57 def load(operator, context, filepath,
58          global_clamp_size=0.0,
59          show_tree=False,
60          show_templates=False,
61          show_geninfo=False,
62          quickmode=False,
63          parented=False,
64          bone_maxlength=1.0,
65          chunksize=False,
66          naming_method=0,
67          use_ngons=True,
68          use_edges=True,
69          use_smooth_groups=True,
70          use_split_objects=True,
71          use_split_groups=True,
72          use_groups_as_vgroups=False,
73          use_image_search=True,
74          global_matrix=None,
75          ):
76     
77     
78     if quickmode :
79         parented = False
80     
81     bone_minlength = bone_maxlength / 100.0
82     
83     #global templates, tokens
84     rootTokens = []
85     namelookup = {}
86     imgnamelookup = {}
87     chunksize = int(chunksize)
88     reserved_type = (
89         'dword',
90         'float',
91         'string'
92     )
93
94     '''
95         'array',
96         'Matrix4x4',
97         'Vector',
98     '''
99     '''
100     with * : defined in dXdata
101     
102     WORD     16 bits
103     * DWORD     32 bits
104     * FLOAT     IEEE float
105     DOUBLE     64 bits
106     CHAR     8 bits
107     UCHAR     8 bits
108     BYTE     8 bits
109     * STRING     NULL-terminated string
110     CSTRING     Formatted C-string (currently unsupported)
111     UNICODE     UNICODE string (currently unsupported)
112
113 BINARY FORMAT
114 # TOKENS in little-endian WORDs
115 #define TOKEN_NAME         1
116 #define TOKEN_STRING       2
117 #define TOKEN_INTEGER      3
118 #define TOKEN_GUID         5
119 #define TOKEN_INTEGER_LIST 6
120 #define TOKEN_FLOAT_LIST   7
121 #define TOKEN_OBRACE      10
122 #define TOKEN_CBRACE      11
123 #define TOKEN_OPAREN      12
124 #define TOKEN_CPAREN      13
125 #define TOKEN_OBRACKET    14
126 #define TOKEN_CBRACKET    15
127 #define TOKEN_OANGLE      16
128 #define TOKEN_CANGLE      17
129 #define TOKEN_DOT         18
130 #define TOKEN_COMMA       19
131 #define TOKEN_SEMICOLON   20
132 #define TOKEN_TEMPLATE    31
133 #define TOKEN_WORD        40
134 #define TOKEN_DWORD       41
135 #define TOKEN_FLOAT       42
136 #define TOKEN_DOUBLE      43
137 #define TOKEN_CHAR        44
138 #define TOKEN_UCHAR       45
139 #define TOKEN_SWORD       46
140 #define TOKEN_SDWORD      47
141 #define TOKEN_VOID        48
142 #define TOKEN_LPSTR       49
143 #define TOKEN_UNICODE     50
144 #define TOKEN_CSTRING     51
145 #define TOKEN_ARRAY       52
146     
147     '''
148     
149     # COMMON REGEX
150     space = '[\ \t]{1,}' # at least one space / tab
151     space0 = '[\ \t]{0,}' # zero or more space / tab
152     
153     # DIRECTX REGEX TOKENS
154     r_template = r'template' + space + '[\w]*' + space0 + '\{'
155     if quickmode :
156         r_sectionname = r'Mesh' + space + '[\W-]*'
157     else :
158         r_sectionname = r'[\w]*' + space + '[\w-]*' + space0 + '\{'
159     r_refsectionname = r'\{' + space0 + '[\w-]*' + space0 + '\}'
160     r_endsection = r'\{|\}'
161     
162     # dX comments
163     r_ignore = r'#|//'
164     
165     #r_frame = r'Frame' + space + '[\w]*'
166     #r_matrix = r'FrameTransformMatrix' + space + '\{[\s\d.,-]*'
167     #r_mesh = r'Mesh' + space + '[\W]*'
168
169     ###################
170     ## STEP 1 FUNCTIONS
171     ###################
172     
173     ## HEADER
174     # returns header values or False if directx reco tag is missing
175     # assuming there's never comment header and that xof if the 1st
176     # string of the file
177     '''
178      they look like xof 0303txt 0032
179      4       Magic Number (required) "xof "
180      2       Minor Version 03
181      2       Major Version 02
182      4       Format Type (required) 
183         "txt " Text File
184         "bin " Binary File  
185         "tzip" MSZip Compressed Text File
186         "bzip" MSZip Compressed Binary File
187      4       Float Accuracy "0032" 32 bit or "0064" 64 bit
188     '''
189     def dXheader(data) :
190         l = data.read(4)
191         if l != b'xof ' :
192             print ('no header found !')
193             data.seek(0)
194             return False
195         minor = data.read(2).decode()
196         major = data.read(2).decode()
197         format = data.read(4).decode().strip()
198         accuracy = int(data.read(4).decode())
199         data.seek(0)
200         return ( minor, major, format, accuracy )
201         
202     
203     ##
204     def dXtree(data,quickmode = False) :
205         tokens = {}
206         templates = {}
207         tokentypes = {}
208         c = 0
209         lvl = 0
210         tree = ['']
211         ptr = 0
212         eol = 0
213         trunkated = False
214         previouslvl = False
215         while True :
216         #for l in data.readlines() :
217             lines, trunkated = nextFileChunk(data,trunkated)
218             if lines == None : break
219             for l in lines :
220                 
221                 # compute pointer position
222                 ptr += eol
223                 c += 1
224                 eol = len(l) + 1
225                 #print(c,data.tell(),ptr+eol)
226                 #if l != '' : print('***',l)
227                 #if l == ''  : break
228                 l = l.strip()
229                 
230                 # remove blank and comment lines
231                 if l == '' or re.match(r_ignore,l) :
232                     continue
233                 
234                 # one line token cases level switch
235                 if previouslvl :
236                     lvl -= 1
237                     previouslvl = False
238                 
239                 #print('%s lines in %.2f\''%(c,time.clock()-t),end='\r')
240                 #print(c,len(l)+1,ptr,data.tell())
241                 if '{' in l :
242                     lvl += 1
243                     if '}' in l : previouslvl = True #; print('got one line token : \n%s'%l)
244                 elif '}' in l :
245                     lvl -= 1
246                 #print(c,lvl,tree)
247                 
248                 if quickmode == False :
249                     ## look for templates
250                     if re.match(r_template,l) :
251                         tname = l.split(' ')[1]
252                         templates[tname] = {'pointer' : ptr, 'line' : c}
253                         continue
254     
255                     ## look for {references}
256                     if re.match(r_refsectionname,l) :
257                         refname = namelookup[ l[1:-1].strip() ]
258                         #print('FOUND reference to %s in %s at line %s (level %s)'%(refname,tree[lvl-1],c,lvl))
259                         #tree = tree[0:lvl]
260                         parent = tree[lvl-1]
261                         # tag it as a reference, since it's not exactly a child.
262                         # put it in childs since order can matter in sub tokens declaration
263                         tokens[parent]['childs'].append('*'+refname)
264                         if refname not in tokens :
265                             print('reference to %s done before its declaration (line %s)\ncreated dummy'%(refname,c))
266                             tokens[refname] = {}
267                         if 'user' not in tokens[refname] : tokens[refname]['users'] = [parent]
268                         else : tokens[refname]['users'].append(parent)
269                         continue
270     
271                 ## look for any token or only Mesh token in quickmode
272                 if re.match(r_sectionname,l) :
273                     tokenname = getName(l,tokens)
274                     #print('FOUND %s %s %s %s'%(tokenname,c,lvl,tree))
275                     #print('pointer %s %s'%(data.tell(),ptr))
276                     if lvl == 1 : rootTokens.append(tokenname)
277                     typ = l.split(' ')[0].strip().lower()
278                     tree = tree[0:lvl]
279                     if typ not in tokentypes : tokentypes[typ] = [tokenname]
280                     else : tokentypes[typ].append(tokenname)
281                     parent = tree[-1]
282                     if tokenname in tokens :
283                         tokens[tokenname]['pointer'] = ptr
284                         tokens[tokenname]['line'] = c
285                         tokens[tokenname]['parent'] = parent
286                         tokens[tokenname]['childs'] = []
287                         tokens[tokenname]['type'] = typ
288                         
289                     else : tokens[tokenname] = {'pointer': ptr,
290                                                 'line'   : c,
291                                                 'parent' : parent,
292                                                 'childs' : [],
293                                                 'users'  : [],
294                                                 'type'   : typ
295                                                 }
296                     tree.append(tokenname)
297                     if lvl > 1 and quickmode == False :
298                         tokens[parent]['childs'].append(tokenname)
299                     
300         return tokens, templates, tokentypes
301         
302     ## returns file binary chunks
303     def nextFileChunk(data,trunkated=False,chunksize=1024) :
304         if chunksize == 0 : chunk = data.read()
305         else : chunk = data.read(chunksize)
306         if format == 'txt' :
307             lines = chunk.decode('utf-8', errors='ignore')
308             #if stream : return lines.replace('\r','').replace('\n','')
309             lines = lines.replace('\r','\n').split('\n')
310             if trunkated : lines[0] = trunkated + lines[0]
311             if len(lines) == 1 : 
312                 if lines[0] == '' : return None, None
313                 return lines, False
314             return lines, lines.pop()
315         # wip, todo for binaries
316         else :
317             print(chunk)
318             for word in range(0,len(chunk)) :
319                 w = chunk[word:word+4]
320                 print(word,w,struct.unpack("<l", w),binascii.unhexlify(w))
321
322     
323     # name unnamed tokens, watchout for x duplicate
324     # for blender, referenced token in x should be named and unique..
325     def getName(l,tokens) :
326         xnam = l.split(' ')[1].strip()
327         
328         #if xnam[0] == '{' : xnam = ''
329         if xnam and xnam[-1] == '{' : xnam = xnam[:-1]
330         
331         name = xnam
332         if len(name) == 0 : name = l.split(' ')[0].strip()
333         
334         namelookup[xnam] = bel.bpyname(name,tokens,4)
335
336         return namelookup[xnam]
337     
338     
339     ###################
340     ## STEP 2 FUNCTIONS
341     ###################
342     # once the internal dict is populated the functions below can be used
343     
344     ## from a list of tokens, displays every child, users and references
345     '''
346       walk_dxtree( [ 'Mesh01', 'Mesh02' ] ) # for particular pieces
347       walk_dxtree(tokens.keys()) for the whole tree
348     '''
349     def walk_dXtree(field,lvl=0,tab='') :
350         for fi, tokenname in enumerate(field) :
351             if lvl > 0 or tokens[tokenname]['parent'] == '' :
352                 if tokenname not in tokens :
353                     tokenname = tokenname[1:]
354                     ref = 'ref: '
355                 else : ref = False
356                 
357                 frame_type = tokens[tokenname]['type']
358                 line = ('{:7}'.format(tokens[tokenname]['line']))
359                 log = ' %s%s (%s)'%( ref if ref else '', tokenname, frame_type )
360                 print('%s.%s%s'%(line, tab, log))
361                 if fi == len(field) - 1 : tab = tab[:-3] + '   '
362     
363                 if ref == False :
364                     for user in tokens[tokenname]['users'] :
365                          print('%s.%s |__ user: %s'%(line, tab.replace('_',' '), user))
366                     walk_dXtree(tokens[tokenname]['childs'],lvl+1,tab.replace('_',' ')+' |__')
367                 
368                 if fi == len(field) - 1 and len(tokens[tokenname]['childs']) == 0 :
369                     print('%s.%s'%(line,tab))
370     
371     ## remove eol, comments, spaces from a raw block of datas
372     def cleanBlock(block) :
373         while '//' in block :
374             s = block.index('//')
375             e = block.index('\n',s+1)
376             block = block[0:s] + block[e:]
377         while '#' in block :
378             s = block.index('#')
379             e = block.index('\n',s+1)
380             block = block[0:s] + block[e:]
381         block = block.replace('\n','').replace(' ','').replace('\t ','')
382         return block
383         
384     def readToken(tokenname) :
385         token = tokens[tokenname]
386         datatype = token['type'].lower()
387         if datatype in templates : tpl = templates[datatype]
388         elif datatype in defaultTemplates : tpl = defaultTemplates[datatype]
389         else :
390             print("can't find any template to read %s (type : %s)"%(tokenname,datatype))
391             return False
392         #print('> use template %s'%datatype)
393         block = readBlock(data,token)
394         ptr = 0
395         #return dXtemplateData(tpl,block)
396         fields, ptr = dXtemplateData(tpl,block)
397         if datatype in templatesConvert :
398             fields = eval( templatesConvert[datatype] )
399         return fields
400     
401     def dXtemplateData(tpl,block,ptr=0) :
402         #print('dxTPL',block[ptr])
403         pack = []
404         for member in tpl['members'] :
405             #print(member)
406             dataname = member[-1]
407             datatype = member[0].lower()
408             if datatype ==  'array' :
409                 datatype = member[1].lower()
410                 s = dataname.index('[') + 1
411                 e = dataname.index(']')
412                 #print(dataname[s:e])
413                 length = eval(dataname[s:e])
414                 #print("array %s type %s length defined by '%s' : %s"%(dataname[:s-1],datatype,dataname[s:e],length))
415                 dataname = dataname[:s-1]
416                 datavalue, ptr = dXarray(block, datatype, length, ptr)
417                 #print('back to %s'%(dataname))
418             else :
419                 length = 1
420                 datavalue, ptr = dXdata(block, datatype, length, ptr)
421     
422             #if len(str(datavalue)) > 50 : dispvalue = str(datavalue[0:25]) + ' [...] ' + str(datavalue[-25:])
423             #else : dispvalue = str(datavalue)
424             #print('%s :  %s %s'%(dataname,dispvalue,type(datavalue)))
425             exec('%s = datavalue'%(dataname))
426             pack.append( datavalue )
427         return pack, ptr + 1
428     
429     def dXdata(block,datatype,length,s=0,eof=';') :
430         #print('dxDTA',block[s])
431         # at last, the data we need
432         # should be a ';' but one meet ',' often, like in meshface
433         if datatype == 'dword' :
434             e = block.index(';',s+1)
435             try : field = int(block[s:e])
436             except :
437                 e = block.index(',',s+1)
438                 field = int(block[s:e])
439             return field, e+1
440         elif datatype == 'float' :
441             e = block.index(eof,s+1)
442             return float(block[s:e]), e+1
443         elif datatype == 'string' :
444             e = block.index(eof,s+1)
445             return str(block[s+1:e-1]) , e+1
446         else :
447             if datatype in templates : tpl = templates[datatype]
448             elif datatype in defaultTemplates : tpl = defaultTemplates[datatype]
449             else :
450                 print("can't find any template for type : %s"%(datatype))
451                 return False
452             #print('> use template %s'%datatype)
453             fields, ptr = dXtemplateData(tpl,block,s)
454             if datatype in templatesConvert :
455                 fields = eval( templatesConvert[datatype] )
456             return fields, ptr
457     
458     def dXarray(block, datatype, length, s=0) :
459         #print('dxARR',block[s])
460         lst = []
461         if datatype in reserved_type :
462             eoi=','
463             for i in range(length) :
464                 if i+1 == length : eoi = ';'
465                 datavalue, s = dXdata(block,datatype,1,s,eoi)
466                 lst.append( datavalue )
467             
468         else :
469             eoi = ';,'
470             for i in range(length) :
471                 if i+1 == length : eoi = ';;'
472                 #print(eoi)
473                 e = block.index(eoi,s)
474                 #except : print(block,s) ; popo()
475                 datavalue, na = dXdata(block[s:e+1],datatype,1)
476                 lst.append( datavalue )
477                 s = e + 2
478         return lst, s
479     
480     ###################################################
481
482     ## populate a template with its datas
483     # this make them available in the internal dict. should be used in step 2 for unknown data type at least
484     def readTemplate(data,tpl_name,display=False) :
485         ptr = templates[tpl_name]['pointer']
486         line = templates[tpl_name]['line']
487         #print('> %s at line %s (chr %s)'%(tpl_name,line,ptr))
488         data.seek(ptr)
489         block = ''
490         trunkated = False
491         go = True
492         while go :
493             lines, trunkated = nextFileChunk(data,trunkated,chunksize) # stream ?
494             if lines == None : 
495                 break
496             for l in lines :
497                 #l = data.readline().decode().strip()
498                 block += l.strip()
499                 if '}' in l :
500                     go = False
501                     break
502         
503         uuid = re.search(r'<.+>',block).group()
504         templates[tpl_name]['uuid'] = uuid.lower()
505         templates[tpl_name]['members'] = []
506         templates[tpl_name]['restriction'] = 'closed'
507         
508         members = re.search(r'>.+',block).group()[1:-1].split(';')
509         for member in members :
510             if member == '' : continue
511             if member[0] == '[' :
512                 templates[tpl_name]['restriction'] = member
513                 continue  
514             templates[tpl_name]['members'].append( member.split(' ') )
515     
516         if display : 
517             print('\ntemplate %s :'%tpl_name)
518             for k,v in templates[tpl_name].items() :
519                 if k != 'members' :
520                     print('  %s : %s'%(k,v))
521                 else :
522                     for member in v :
523                         print('  %s'%str(member)[1:-1].replace(',',' ').replace("'",''))
524                 
525             if tpl_name in defaultTemplates :
526                 defaultTemplates[tpl_name]['line'] = templates[tpl_name]['line']
527                 defaultTemplates[tpl_name]['pointer'] = templates[tpl_name]['pointer']
528                 if defaultTemplates[tpl_name] != templates[tpl_name] :
529                     print('! DIFFERS FROM BUILTIN TEMPLATE :')
530                     print('raw template %s :'%tpl_name)
531                     print(templates[tpl_name])
532                     print('raw default template %s :'%tpl_name)
533                     print(defaultTemplates[tpl_name])
534                     #for k,v in defaultTemplates[tpl_name].items() :
535                     #    if k != 'members' :
536                     #        print('  %s : %s'%(k,v))
537                     #    else :
538                     #        for member in v :
539                     #            print('  %s'%str(member)[1:-1].replace(',',' ').replace("'",''))
540                 else :
541                     print('MATCHES BUILTIN TEMPLATE')
542     
543             
544     ##  read any kind of token data block
545     # by default the block is cleaned from inline comment space etc to allow data parsing
546     # useclean = False (retrieve all bytes) if you need to compute a file byte pointer
547     # to mimic the file.tell() function and use it with file.seek()
548     def readBlock(data,token, clean=True) :
549         ptr = token['pointer']
550         data.seek(ptr)
551         block = ''
552         #lvl = 0
553         trunkated = False
554         go = True
555         while go :
556             lines, trunkated = nextFileChunk(data,trunkated,chunksize)
557             if lines == None : break
558             for l in lines :
559                 #eol = len(l) + 1
560                 l = l.strip()
561                 #c += 1
562                 block += l+'\n'
563                 if re.match(r_endsection,l) :
564                     go = False
565                     break
566         s = block.index('{') + 1
567         e = block.index('}')
568         block = block[s:e]
569         if clean : block = cleanBlock(block)
570         return block
571     
572     def getChilds(tokenname) :
573         childs = []
574         # '*' in childname means it's a reference. always perform this test
575         # when using the childs field
576         for childname in tokens[tokenname]['childs'] :
577             if childname[0] == '*' : childname = childname[1:]
578             childs.append( childname )
579         return childs
580     
581         # the input nested list of [bonename, matrix, [child0,child1..]] is given by import_dXtree()
582     def buildArm(armdata, child,lvl=0,parent_matrix=False) :
583         
584         bonename, bonemat, bonechilds = child
585         
586         if lvl == 0 :
587             armname = armdata
588             armdata = bpy.data.armatures.new(name=armname)
589             arm = bpy.data.objects.new(armname,armdata)
590             bpy.context.scene.objects.link(arm)
591             arm.select = True
592             bpy.context.scene.objects.active = arm
593             bpy.ops.object.mode_set(mode='EDIT')
594             parent_matrix = Matrix()
595         
596         bone = armdata.edit_bones.new(name=bonename)
597         bonematW = parent_matrix * bonemat
598         bone.head = bonematW.to_translation()
599         #bone.roll.. ?
600         bone_length = bone_maxlength
601         for bonechild in bonechilds :
602             bonechild = buildArm(armdata,bonechild,lvl+1,bonematW)
603             bonechild.parent = bone
604             bone_length = min((bonechild.head - bone.head).length, bone_length)
605         bone.tail = bonematW * Vector((0,bone_length,0))
606         if lvl == 0 :
607             bpy.ops.object.mode_set(mode='OBJECT')
608             return arm
609         return bone
610     
611     def import_dXtree(field,lvl=0) :
612         tab = ' '*lvl*2
613         if field == [] : 
614             if show_geninfo : print('%s>> no childs, return False'%(tab))
615             return False
616         ob = False
617         mat = False
618         is_root = False
619         frames = []
620         obs = []
621         
622         parentname = tokens[field[0]]['parent']
623         if show_geninfo : print('%s>>childs in frame %s :'%(tab,parentname))
624         
625         for tokenname in field :
626
627             tokentype = tokens[tokenname]['type']
628             
629             # frames can contain more than one mesh
630             if tokentype  == 'mesh' :
631                 # object and mesh naming :
632                 # if parent frame has several meshes : obname = meshname = mesh token name,
633                 # if parent frame has only one mesh  : obname = parent frame name, meshname =  mesh token name.
634                 if parentname :
635                     meshcount = 0
636                     for child in getChilds(parentname) :
637                         if tokens[child]['type'] == 'mesh' : 
638                             meshcount += 1
639                             if meshcount == 2 :
640                                 parentname = tokenname
641                                 break
642                 else : parentname = tokenname
643                 
644                 ob = getMesh(parentname,tokenname)
645                 obs.append(ob)
646
647                 if show_geninfo : print('%smesh : %s'%(tab,tokenname))
648             
649             # frames contain one matrix (empty or bone)
650             elif tokentype  == 'frametransformmatrix' :
651                 [mat] = readToken(tokenname)
652                 if show_geninfo : print('%smatrix : %s'%(tab,tokenname))
653             
654             # frames can contain 0 or more frames
655             elif tokentype  == 'frame' :
656                 frames.append(tokenname)
657                 if show_geninfo : print('%sframe : %s'%(tab,tokenname))
658         
659         # matrix is used for mesh transform if some mesh(es) exist(s)      
660         if ob :
661             is_root = True
662             if mat == False :
663                 mat = Matrix()
664                 if show_geninfo : print('%smesh token without matrix, set it to default\n%splease report in bug tracker if you read this !'%(tab,tab))
665             if parentname == '' : 
666                 mat = mat * global_matrix
667             if len(obs) == 1 :
668                 ob.matrix_world = mat
669             else :
670                 ob = bel.ob.new(parentname, None, naming_method)
671                 ob.matrix_world = mat
672                 for child in obs :
673                     child.parent = ob
674         
675         # matrix only, store it as a list as we don't know if
676         # it's a bone or an empty yet
677         elif mat :
678             ob = [parentname, mat,[]]
679
680         # nothing case ?
681         else :
682             ob = [parentname, Matrix() * global_matrix,[]]
683             if show_geninfo : print('%snothing here'%(tab))
684
685         childs = []
686         
687         for tokenname in frames :
688             if show_geninfo : print('%s<Begin %s :'%(tab,tokenname))
689             
690             # child is either False, empty, object, or a list or undefined name matrices hierarchy
691             child = import_dXtree(getChilds(tokenname),lvl+1)
692             if child and type(child) != list :
693                 is_root = True
694             childs.append( [tokenname, child] )
695             if show_geninfo : print('%sEnd %s>'%(tab,tokenname))
696         
697         if is_root and parentname != '' :
698             
699             if show_geninfo : print('%send of tree a this point'%(tab))
700             if type(ob) == list :
701                 mat = ob[1]
702                 ob = bel.ob.new(parentname, None, naming_method)
703             ob.matrix_world = mat
704             
705         for tokenname, child in childs :
706             if show_geninfo : print('%sbegin2 %s>'%(tab,tokenname))
707             # returned a list of object(s) or matrice(s)
708             if child :
709
710                 # current frame is an object or an empty, we parent this frame to it
711                 #if eot or (ob and ( type(ob.data) == type(None) or type(ob.data) == bpy.types.Mesh ) ) :
712                 if is_root :
713                     # this branch is an armature, convert it
714                     if type(child) == list :
715                         if show_geninfo : print('%sconvert to armature %s'%(tab,tokenname))
716                         child = buildArm(tokenname, child)
717                         
718                     # parent the obj/empty/arm to current
719                     # or apply the global user defined matrix to the object root
720                     if parentname != '' :
721                         child.parent = ob
722                     else :
723                         child.matrix_world = global_matrix
724                         
725                 # returned a list of parented matrices. append it in childs list
726                 elif type(child[0]) == str :
727                     ob[2].append(child)
728
729                 # child is an empty or a mesh, so current frame is an empty, not an armature
730                 elif ob and ( type(child.data) == type(None) or type(child.data) == bpy.types.Mesh ) :
731                     #print('  child data type: %s'%type(child.data))
732                     child.parent = ob
733                     #print('%s parented to %s'%(child.name,ob.name))
734                 
735             # returned False
736             else :
737                  if show_geninfo : print('%sreturned %s, nothing'%(tab,child))
738
739         #print('>> %s return %s'%(field,ob))
740         return ob# if ob else False
741
742     # build from mesh token type
743     def getMesh(obname,tokenname,debug = False):
744     
745         if debug : print('\nmesh name : %s'%tokenname)
746         
747         verts = []
748         edges = []
749         faces = []
750         matslots = []
751         facemats = []
752         uvs = []
753         groupnames = []
754         groupindices = []
755         groupweights = []
756
757         nVerts, verts, nFaces, faces = readToken(tokenname) 
758
759         if debug :
760             print('verts    : %s %s\nfaces    : %s %s'%(nVerts, len(verts),nFaces, len(faces)))
761         
762         #for childname in token['childs'] :
763         for childname in getChilds(tokenname) :
764             
765             tokentype = tokens[childname]['type']
766             
767             # UV
768             if tokentype == 'meshtexturecoords' :
769                 uv = readToken(childname)
770                 uv = bel.uv.asVertsLocation(uv, faces)
771                 uvs.append(uv)
772                 
773                 if debug : print('uv       : %s'%(len(uv)))
774             
775             # MATERIALS
776             elif tokentype == 'meshmateriallist' :
777                 nbslots, facemats = readToken(childname)
778                 
779                 if debug : print('facemats : %s'%(len(facemats)))
780                 
781                 # mat can exist but with no datas so we prepare the mat slot
782                 # with dummy ones
783                 for slot in range(nbslots) :
784                     matslots.append('dXnoname%s'%slot )
785         
786                 # length does not match (could be tuned more, need more cases)
787                 if len(facemats) != len(faces) :
788                     facemats = [ facemats[0] for i in faces ]
789
790                 # seek for materials then textures if any mapped in this mesh.
791                 # no type test, only one option type in token meshmateriallist : 'Material'
792                 for slotid, matname in enumerate(getChilds(childname)) :
793                     
794                     # rename dummy mats with the right name
795                     matslots[slotid] = matname
796
797                     # blender material creation (need tuning)
798                     mat = bel.material.new(matname,naming_method)
799                     matslots[slotid] = mat.name
800                     
801                     if naming_method != 1 :
802                         #print('matname : %s'%matname)
803                         (diffuse_color,alpha), power, specCol, emitCol = readToken(matname)
804                         #if debug : print(diffuse_color,alpha, power, specCol, emitCol)
805                         mat.diffuse_color = diffuse_color
806                         mat.diffuse_intensity = power
807                         mat.specular_color = specCol
808                         # dX emit don't use diffuse color but is a color itself
809                         # convert it to a kind of intensity 
810                         mat.emit = (emitCol[0] + emitCol[1] + emitCol[2] ) / 3
811                         
812                         if alpha != 1.0 :
813                             mat.use_transparency = True
814                             mat.transparency_method = 'Z_TRANSPARENCY'
815                             mat.alpha = alpha
816                             mat.specular_alpha = 0
817                             transp = True
818                         else : transp = False
819             
820                         # texture
821                         # only 'TextureFilename' can be here, no type test
822                         # textures have no name in .x so we build 
823                         # image and texture names from the image file name
824                         # bdata texture slot name = bdata image name
825                         btexnames = []
826                         for texname in getChilds(matname) :
827                             
828                             # create/rename/reuse etc corresponding data image
829                             # (returns False if not found)
830                             [filename] = readToken(texname)
831                             img = bel.image.new(path+'/'+filename)
832                             
833                             if img == False :
834                                 imgname = 'not_found'
835                             else :
836                                 imgname = img.name
837                                 
838                             #print('texname : %s'%texname)
839                             #print('filename : %s'%filename)
840                             #print('btex/img name : %s'%imgname)
841                             
842                             # associated texture (no naming check.. maybe tune more)
843                             # tex and texslot are created even if img not found
844                             if imgname in bpy.data.textures and ( img == False or bpy.data.textures[imgname].image == img ) :
845                                 tex = bpy.data.textures[imgname]
846                             else :
847                                 tex = bpy.data.textures.new(name=imgname,type='IMAGE')
848                                 if img : tex.image = img
849                                 
850                             tex.use_alpha = transp
851                             tex.use_preview_alpha = transp
852                                 
853                             # then create texture slot
854                             texslot = mat.texture_slots.create(index=0)
855                             texslot.texture = tex
856                             texslot.texture_coords = 'UV'
857                             texslot.uv_layer = 'UV0'
858                             texslot.use_map_alpha = transp
859                             texslot.alpha_factor = alpha
860
861                 # create remaining dummy mat
862                 for slotid, matname in enumerate(matslots) :
863                     if matname not in bpy.data.materials :
864                         mat = bel.material.new(matname,naming_method)
865                         matslots[slotid] = mat.name
866                         
867                 if debug : print('matslots : %s'%matslots)
868                 
869             # VERTICES GROUPS/WEIGHTS
870             elif tokentype == 'skinweights' :
871                 groupname, nverts, vindices, vweights, mat = readToken(childname)
872                 groupname = namelookup[groupname]
873                 if debug : 
874                     print('vgroup    : %s (%s/%s verts) %s'%(groupname,len(vindices),len(vweights),'bone' if groupname in tokens else ''))
875
876                 #if debug : print('matrix : %s\n%s'%(type(mat),mat))
877                 
878                 groupnames.append(groupname)
879                 groupindices.append(vindices)
880                 groupweights.append(vweights)
881                 
882         ob = bel.mesh.write(obname,tokenname, 
883                             verts, edges, faces, 
884                             matslots, facemats, uvs, 
885                             groupnames, groupindices, groupweights,
886                             use_smooth_groups,
887                             naming_method)
888         
889         return ob
890                            
891     ## here we go
892      
893     file = os.path.basename(filepath)
894     
895     print('\nimporting %s...'%file)
896     start = time.clock()
897     path = os.path.dirname(filepath)
898     filepath = os.fsencode(filepath)
899     data = open(filepath,'rb')
900     header = dXheader(data)
901
902     if global_matrix is None:
903         global_matrix = mathutils.Matrix()
904
905     if header :
906         minor, major, format, accuracy = header
907         
908         if show_geninfo :
909             print('\n%s directX header'%file)
910             print('  minor  : %s'%(minor))
911             print('  major  : %s'%(major))
912             print('  format : %s'%(format))
913             print('  floats are %s bits'%(accuracy))
914
915         if format in [ 'txt' ] : #, 'bin' ] :
916
917             ## FILE READ : STEP 1 : STRUCTURE
918             if show_geninfo : print('\nBuilding internal .x tree')
919             t = time.clock()
920             tokens, templates, tokentypes = dXtree(data,quickmode)
921             readstruct_time = time.clock()-t
922             if show_geninfo : print('builded tree in %.2f\''%(readstruct_time)) # ,end='\r')
923
924             ## populate templates with datas
925             for tplname in templates :
926                 readTemplate(data,tplname,show_templates)
927
928             ## DATA TREE CHECK
929             if show_tree :
930                 print('\nDirectX Data Tree :\n')
931                 walk_dXtree(tokens.keys())
932             
933             ## DATA IMPORTATION
934             if show_geninfo : 
935                 print(tokens)
936                 print('Root frames :\n %s'%rootTokens)
937             if parented :
938                 import_dXtree(rootTokens)
939             else :
940                 for tokenname in tokentypes['mesh'] :
941                     obname = tokens[tokenname]['parent']
942                     # object and mesh naming :
943                     # if parent frame has several meshes : obname = meshname = mesh token name,
944                     # if parent frame has only one mesh  : obname = parent frame name, meshname =  mesh token name.
945                     if obname :
946                         meshcount = 0
947                         for child in getChilds(obname) :
948                             if tokens[child]['type'] == 'mesh' : 
949                                 meshcount += 1
950                                 if meshcount == 2 :
951                                     obname = tokenname
952                                     break
953                     else : obname = tokenname
954
955                     ob = getMesh(obname,tokenname,show_geninfo)
956                     ob.matrix_world = global_matrix
957                     
958             print('done in %.2f\''%(time.clock()-start)) # ,end='\r')
959             
960         else :
961             print('only .x files in text format are currently supported')
962             print('please share your file to make the importer evolve')
963
964
965         return {'FINISHED'}
966