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