8680890b0ced49a5bd3175b24b8b745d9ee7424c
[blender.git] / release / scripts / lightwave_import.py
1 #!BPY
2 """
3 Name: 'LightWave + Materials (.lwo)...'
4 Blender: 237
5 Group: 'Import'
6 Tooltip: 'Import LightWave Object File Format (.lwo)'
7 """
8
9 __author__ = "Alessandro Pirovano, Anthony D'Agostino (Scorpius)"
10 __url__ = ("blender", "elysiun",
11 "Author's homepage, http://www.redrival.com/scorpius", "Author's homepage, http://uaraus.altervista.org")
12
13 importername = "lwo_import 0.1.16"
14 # +---------------------------------------------------------+
15 # | Save your work before and after use.                    |
16 # | Please report any useful comment to:                    |
17 # | uaraus-dem@yahoo.it                                     |
18 # | Thanks                                                  |
19 # +---------------------------------------------------------+
20 # +---------------------------------------------------------+
21 # | Copyright (c) 2002 Anthony D'Agostino                   |
22 # | http://www.redrival.com/scorpius                        |
23 # | scorpius@netzero.com                                    |
24 # | April 21, 2002                                          |
25 # | Released under the Blender Artistic Licence (BAL)       |
26 # | Import Export Suite v0.5                                |
27 # +---------------------------------------------------------+
28 # | Read and write LightWave Object File Format (*.lwo)     |
29 # +---------------------------------------------------------+
30 # +---------------------------------------------------------+
31 # | Alessandro Pirovano tweaked starting on March 2005      |
32 # | http://uaraus.altervista.org                            |
33 # +---------------------------------------------------------+
34 # +---------------------------------------------------------+
35 # | Release log:                                            |
36 # | 0.1.16: fixed (try 2) texture offset calculations       |
37 # |         added hint on axis mapping                      |
38 # |         added hint on texture blending mode             |
39 # |         added hint on texture transparency setting      |
40 # |         search images in original directory first       |
41 # |         fixed texture order application                 |
42 # | 0.1.15: added release log                               |
43 # |         fixed texture offset calculations (non-UV)      |
44 # |         fixed reverting vertex order in face generation |
45 # |         associate texture on game-engine settings       |
46 # |         vector math definitely based on mathutils       |
47 # |         search images in "Images" and "../Images" dir   |
48 # |         revised logging facility                        |
49 # |         fixed subsurf texture and material mappings     |
50 # | 0.1.14: patched missing mod_vector (not definitive)     |
51 # | 0.1.13: first public release                            |
52 # +---------------------------------------------------------+
53
54 #blender related import
55 import Blender
56
57 #iosuite related import
58 try: #new naming
59     import meshtools as my_meshtools
60 except ImportError: #fallback to the old one
61     print "using old mod_meshtools"
62     import mod_meshtools as my_meshtools
63
64 #python specific modules import
65 import struct, chunk, os, cStringIO, time, operator, copy
66
67 # ===========================================================
68 # === Utility Preamble ======================================
69 # ===========================================================
70
71 textname = "lwo_log"
72 #uncomment the following line to disable logging facility
73 #textname = None                      1
74
75 # ===========================================================
76
77 class dotext:
78
79     _NO = 0    #use internal to class only
80     LOG = 1    #write only to LOG
81     CON = 2    #write to both LOG and CONSOLE
82
83     def __init__(self, tname, where=LOG):
84         self.dwhere = where #defaults on console only
85         if (tname==None):
86             print "*** not using text object to log script"
87             self.txtobj = None
88             return
89         tlist = Blender.Text.get()
90         for i in range(len(tlist)):
91             if (tlist[i].getName()==tname):
92                 tlist[i].clear()
93                 #print tname, " text object found and cleared!"
94                 self.txtobj = tlist[i]
95                 return
96         #print tname, " text object not found and created!"
97         self.txtobj = Blender.Text.New(tname)
98     # end def __init__
99
100     def write(self, wstring, maxlen=100):
101         if (self.txtobj==None): return
102         while (1):
103             ll = len(wstring)
104             if (ll>maxlen):
105                 self.txtobj.write((wstring[:maxlen]))
106                 self.txtobj.write("\n")
107                 wstring = (wstring[maxlen:])
108             else:
109                 self.txtobj.write(wstring)
110                 break
111     # end def write
112
113     def pstring(self, ppstring, where = _NO):
114         if where == dotext._NO: where = self.dwhere
115         if where == dotext.CON:
116             print ppstring
117         self.write(ppstring)
118         self.write("\n")
119     # end def pstring
120
121     def plist(self, pplist, where = _NO):
122         self.pprint ("list:[")
123         for pp in range(len(pplist)):
124             self.pprint ("[%d] -> %s" % (pp, pplist[pp]), where)
125         self.pprint ("]")
126     # end def plist
127
128     def pdict(self, pdict, where = _NO):
129         self.pprint ("dict:{", where)
130         for pp in pdict.keys():
131             self.pprint ("[%s] -> %s" % (pp, pdict[pp]), where)
132         self.pprint ("}")
133     # end def pdict
134
135     def pprint(self, parg, where = _NO):
136         if parg == None:
137             self.pstring("_None_", where)
138         elif type(parg) == type ([]):
139             self.plist(parg, where)
140         elif type(parg) == type ({}):
141             self.pdict(parg, where)
142         else:
143             self.pstring(parg, where)
144     # end def pprint
145
146     def logcon(self, parg):
147         self.pprint(parg, dotext.CON)
148     # end def logcon
149 # endclass dotext
150
151 tobj=dotext(textname)
152 #uncomment the following line to log all messages on both console and logfile
153 #tobj=dotext(textname,dotext.CON)
154
155
156 # ===========================================================
157 # === Main read functions ===================================
158 # ===========================================================
159
160 # =============================
161 # === Read LightWave Format ===
162 # =============================
163 def read(filename):
164     global tobj
165
166     tobj.logcon ("#####################################################################")
167     tobj.logcon ("This is: %s" % importername)
168     tobj.logcon ("Importing file:")
169     tobj.logcon (filename)
170     tobj.pprint ("#####################################################################")
171
172     start = time.clock()
173     file = open(filename, "rb")
174
175     # === LWO header ===
176     form_id, form_size, form_type = struct.unpack(">4s1L4s",  file.read(12))
177     if (form_type == "LWOB"):
178         read_lwob(file, filename)
179     elif (form_type == "LWO2"):
180         read_lwo2(file, filename)
181     else:
182         tobj.logcon ("Can't read a file with the form_type: %s" %form_type)
183         return
184
185     Blender.Window.DrawProgressBar(1.0, "")    # clear progressbar
186     file.close()
187     end = time.clock()
188     seconds = " in %.2f %s" % (end-start, "seconds")
189     if form_type == "LWO2": fmt = " (v6.0 Format)"
190     if form_type == "LWOB": fmt = " (v5.5 Format)"
191     message = "Successfully imported " + os.path.basename(filename) + fmt + seconds
192     #my_meshtools.print_boxed(message)
193     tobj.pprint ("#####################################################################")
194     tobj.logcon (message)
195     tobj.logcon ("#####################################################################")
196
197 # enddef read
198
199
200 # =================================
201 # === Read LightWave 5.5 format ===
202 # =================================
203 def read_lwob(file, filename):
204     global tobj
205
206     tobj.logcon("LightWave 5.5 format")
207     objname = os.path.splitext(os.path.basename(filename))[0]
208
209     while 1:
210         try:
211             lwochunk = chunk.Chunk(file)
212         except EOFError:
213             break
214         if lwochunk.chunkname == "LAYR":
215             objname = read_layr(lwochunk)
216         elif lwochunk.chunkname == "PNTS":                         # Verts
217             verts = read_verts(lwochunk)
218         elif lwochunk.chunkname == "POLS": # Faces v5.5
219             faces = read_faces_5(lwochunk)
220             my_meshtools.create_mesh(verts, faces, objname)
221         else:                                                       # Misc Chunks
222             lwochunk.skip()
223     return
224 # enddef read_lwob
225
226
227 # =============================
228 # === Read LightWave Format ===
229 # =============================
230 def read_lwo2(file, filename, typ="LWO2"):
231     global tobj
232
233     tobj.logcon("LightWave 6 (and above) format")
234
235     dir_part = Blender.sys.dirname(filename)
236     fname_part = Blender.sys.basename(filename)
237
238     #first initialization of data structures
239     defaultname = os.path.splitext(fname_part)[0]
240     tag_list = []              #tag list: global for the whole file?
241     surf_list = []             #surf list: global for the whole file?
242     clip_list = []             #clip list: global for the whole file?
243     object_index = 0
244     object_list = None
245     # init value is: object_list = [[None, {}, [], [], {}, {}, 0, {}, {}]]
246     #0 - objname                    #original name
247     #1 - obj_dict = {TAG}           #objects created
248     #2 - verts = []                 #object vertexes
249     #3 - faces = []                 #object faces (associations poly -> vertexes)
250     #4 - obj_dim_dict = {TAG}       #tuples size and pos in local object coords - used for NON-UV mappings
251     #5 - polytag_dict = {TAG}       #tag to polygon mapping
252     #6 - patch_flag                 #0 = surf; 1 = patch (subdivision surface) - it was the image list
253     #7 - uvcoords_dict = {name}     #uvmap coordinates (mixed mode per face/per vertex)
254     #8 - facesuv_dict = {name}      #uvmap coordinates associations poly -> uv tuples
255
256     while 1:
257         try:
258             lwochunk = chunk.Chunk(file)
259         except EOFError:
260             break
261         tobj.pprint(" ")
262         if lwochunk.chunkname == "LAYR":
263             tobj.pprint("---- LAYR")
264             objname = read_layr(lwochunk)
265             tobj.pprint(objname)
266             if object_list == None:
267                 object_list = [[objname, {}, [], [], {}, {}, 0, {}, {}]]
268             else:
269                 object_list.append([objname, {}, [], [], {}, {}, 0, {}, {}])
270                 object_index += 1
271         elif lwochunk.chunkname == "PNTS":                         # Verts
272             tobj.pprint("---- PNTS")
273             verts = read_verts(lwochunk)
274             object_list[object_index][2] = verts
275         elif lwochunk.chunkname == "VMAP":                         # MAPS (UV)
276             tobj.pprint("---- VMAP")
277             object_list[object_index][7], object_list[object_index][8] = read_vmap(object_list[object_index][7], object_list[object_index][8], object_list[object_index][3], len(object_list[object_index][2]), lwochunk)
278         elif lwochunk.chunkname == "VMAD":                         # MAPS (UV) per-face
279             tobj.pprint("---- VMAD")
280             object_list[object_index][7], object_list[object_index][8] = read_vmad(object_list[object_index][7], object_list[object_index][8], object_list[object_index][3], len(object_list[object_index][2]), lwochunk)
281         elif lwochunk.chunkname == "POLS": # Faces v6.0
282             tobj.pprint("-------- POLS(6)")
283             faces, flag = read_faces_6(lwochunk)
284             #flag is 0 for regular polygon, 1 for patches (= subsurf), 2 for anything else to be ignored
285             if flag<2:
286                 if object_list[object_index][3] != []:
287                     object_list.append([object_list[object_index][0],                  #update name
288                                         {},                                            #init
289                                         copy.deepcopy(object_list[object_index][2]),   #same vertexes
290                                         [],                                            #no faces
291                                         {},                                            #no need to copy - filled at runtime
292                                         {},                                            #polygon tagging will follow
293                                         flag,                                          #patch flag
294                                         copy.deepcopy(object_list[object_index][7]),   #same uvcoords
295                                         {}])                                           #no uv mapping
296                     object_index += 1
297                 #end if already has a face list
298                 #update uv coords mapping if VMAP already encountered
299                 for uvname in object_list[object_index][7]:
300                     tobj.pprint("updating uv to face mapping for %s" % uvname)
301                     object_list[object_index][8][uvname] = copy.deepcopy(faces)
302                 object_list[object_index][3] = faces
303                 objname = object_list[object_index][0]
304                 if objname == None:
305                     objname = defaultname
306             #end if processing a valid poly type
307         elif lwochunk.chunkname == "TAGS":                         # Tags
308             tobj.pprint("---- TAGS")
309             tag_list.extend(read_tags(lwochunk))
310         elif lwochunk.chunkname == "PTAG":                         # PTags
311             tobj.pprint("---- PTAG")
312             polytag_dict = read_ptags(lwochunk, tag_list)
313             for kk in polytag_dict.keys(): object_list[object_index][5][kk] = polytag_dict[kk]
314         elif lwochunk.chunkname == "SURF":                         # surfaces
315             tobj.pprint("---- SURF")
316             surf_list.append(read_surfs(lwochunk, surf_list, tag_list))
317         elif lwochunk.chunkname == "CLIP":                         # texture images
318             tobj.pprint("---- CLIP")
319             clip_list.append(read_clip(lwochunk))
320             tobj.pprint("read total %s clips" % len(clip_list))
321         else:                                                       # Misc Chunks
322             tobj.pprint("---- %s: skipping" % lwochunk.chunkname)
323             lwochunk.skip()
324         #uncomment here to log data structure as it is built
325         #tobj.pprint(object_list)
326
327     tobj.pprint ("\n#####################################################################")
328     tobj.pprint("Found %d objects:" % len(object_list))
329     tobj.pprint ("#####################################################################")
330     for objspec_list in object_list:
331         tobj.pprint ("\n#===================================================================#")
332         tobj.pprint("Processing Object: %s" % objspec_list[0])
333         tobj.pprint ("#===================================================================#")
334         objspec_list[3], objspec_list[5], objspec_list[8] = recalc_faces(objspec_list[2], objspec_list[3], objspec_list[5], objspec_list[8]) #recalculate faces, polytag_dict and uv_mapping get rid of faces fanning
335
336         create_objects(objspec_list)
337
338         if surf_list != []:
339             create_material(clip_list, surf_list, objspec_list, dir_part) #give it all the object
340     return
341 # enddef read_lwo2
342
343
344
345
346
347
348 # ===========================================================
349 # === File reading routines =================================
350 # ===========================================================
351 # ==================
352 # === Read Verts ===
353 # ==================
354 def read_verts(lwochunk):
355     global tobj
356
357     data = cStringIO.StringIO(lwochunk.read())
358     numverts = lwochunk.chunksize/12
359     #$verts = []
360     verts = [None] * numverts
361     for i in range(numverts):
362         if not i%100 and my_meshtools.show_progress:
363             Blender.Window.DrawProgressBar(float(i)/numverts, "Reading Verts")
364         x, y, z = struct.unpack(">fff", data.read(12))
365         verts[i] = (x, z, y)
366     tobj.pprint("read %d vertexes" % (i+1))
367     return verts
368 # enddef read_verts
369
370
371 # =================
372 # === Read Name ===
373 # =================
374 # modified to deal with odd lenght strings
375 def read_name(file):
376     name = ""
377     while 1:
378         char = file.read(1)
379         if char == "\0": break
380         else: name += char
381     len_name = len(name) + 1 #count the trailing zero
382     if len_name%2==1:
383         char = file.read(1) #remove zero padding to even lenght
384         len_name += 1
385     return name, len_name
386
387
388 # ==================
389 # === Read Layer ===
390 # ==================
391 def read_layr(lwochunk):
392     data = cStringIO.StringIO(lwochunk.read())
393     idx, flags = struct.unpack(">hh", data.read(4))
394     pivot = struct.unpack(">fff", data.read(12))
395     layer_name, discard = read_name(data)
396     if not layer_name: layer_name = "NoName"
397     return layer_name
398 # enddef read_layr
399
400
401 # ======================
402 # === Read Faces 5.5 ===
403 # ======================
404 def read_faces_5(lwochunk):
405     data = cStringIO.StringIO(lwochunk.read())
406     faces = []
407     i = 0
408     while i < lwochunk.chunksize:
409         if not i%100 and my_meshtools.show_progress:
410            Blender.Window.DrawProgressBar(float(i)/lwochunk.chunksize, "Reading Faces")
411         facev = []
412         numfaceverts, = struct.unpack(">H", data.read(2))
413         for j in range(numfaceverts):
414             index, = struct.unpack(">H", data.read(2))
415             facev.append(index)
416         facev.reverse()
417         faces.append(facev)
418         surfaceindex, = struct.unpack(">H", data.read(2))
419         if surfaceindex < 0:
420             tobj.logcon ("***Error. Referencing uncorrect surface index")
421             return
422         i += (4+numfaceverts*2)
423     return faces
424
425
426 # ==================================
427 # === Read Variable-Length Index ===
428 # ==================================
429 def read_vx(data):
430     byte1, = struct.unpack(">B", data.read(1))
431     if byte1 != 0xFF:    # 2-byte index
432         byte2, = struct.unpack(">B", data.read(1))
433         index = byte1*256 + byte2
434         index_size = 2
435     else:                # 4-byte index
436         byte2, byte3, byte4 = struct.unpack(">3B", data.read(3))
437         index = byte2*65536 + byte3*256 + byte4
438         index_size = 4
439     return index, index_size
440
441
442 # ======================
443 # === Read uvmapping ===
444 # ======================
445 def read_vmap(uvcoords_dict, facesuv_dict, faces, maxvertnum, lwochunk):
446     if maxvertnum == 0:
447         tobj.pprint ("Found VMAP but no vertexes to map!")
448         return uvcoords_dict, facesuv_dict
449     data = cStringIO.StringIO(lwochunk.read())
450     map_type = data.read(4)
451     if map_type != "TXUV":
452         tobj.pprint ("Reading VMAP: No Texture UV map Were Found. Map Type: %s" % map_type)
453         return uvcoords_dict, facesuv_dict
454     dimension, = struct.unpack(">H", data.read(2))
455     name, i = read_name(data) #i initialized with string lenght + zeros
456     tobj.pprint ("TXUV %d %s" % (dimension, name))
457     #my_uv_list = [None] * maxvertnum
458     my_uv_list = [(0.0, 0.0)] * maxvertnum         #more safe to have some default coordinates to associate in any case?
459     while (i < lwochunk.chunksize - 6):            #4+2 header bytes already read
460         vertnum, vnum_size = read_vx(data)
461         u, v = struct.unpack(">ff", data.read(8))
462         if vertnum >= maxvertnum:
463             tobj.pprint ("Hem: more uvmap than vertexes? ignoring uv data for vertex %d" % vertnum)
464         else:
465             my_uv_list[vertnum] = (u, v)
466         i += 8 + vnum_size
467     #end loop on uv pairs
468     uvcoords_dict[name] = my_uv_list
469     #this is a per-vertex mapping AND the uv tuple is vertex-ordered, so faces_uv is the same as faces
470     if faces == []:
471         tobj.pprint ("no faces read yet! delaying uv to face assignments")
472         facesuv_dict[name] = []
473     else:
474         #deepcopy so we could modify it without actually modify faces
475         tobj.pprint ("faces already present: proceeding with assignments")
476         facesuv_dict[name] = copy.deepcopy(faces)
477     return uvcoords_dict, facesuv_dict
478
479
480 # ========================
481 # === Read uvmapping 2 ===
482 # ========================
483 def read_vmad(uvcoords_dict, facesuv_dict, faces, maxvertnum, lwochunk):
484     maxfacenum = len(faces)
485     if maxvertnum == 0 or maxfacenum == 0:
486         tobj.pprint ("Found VMAD but no vertexes to map!")
487         return uvcoords_dict, facesuv_dict
488     data = cStringIO.StringIO(lwochunk.read())
489     map_type = data.read(4)
490     if map_type != "TXUV":
491         tobj.pprint ("Reading VMAD: No Texture UV map Were Found. Map Type: %s" % map_type)
492         return uvcoords_dict, facesuv_dict
493     dimension, = struct.unpack(">H", data.read(2))
494     name, i = read_name(data) #i initialized with string lenght + zeros
495     tobj.pprint ("TXUV %d %s" % (dimension, name))
496     if uvcoords_dict.has_key(name):
497         my_uv_list = uvcoords_dict[name]          #update existing
498         my_facesuv_list = facesuv_dict[name]
499     else:
500         my_uv_list = [(0.0, 0.0)] * maxvertnum    #start a brand new: this could be made more smart
501         my_facesuv_list = copy.deepcopy(faces)
502     #end variable initialization
503     lastindex = len(my_uv_list) - 1
504     while (i < lwochunk.chunksize - 6):  #4+2 header bytes already read
505         vertnum, vnum_size = read_vx(data)
506         i += vnum_size
507         polynum, vnum_size = read_vx(data)
508         i += vnum_size
509         u, v = struct.unpack(">ff", data.read(8))
510         if polynum >= maxfacenum or vertnum >= maxvertnum:
511             tobj.pprint ("Hem: more uvmap than vertexes? ignorig uv data for vertex %d" % vertnum)
512         else:
513             my_uv_list.append( (u,v) )
514             newindex = len(my_uv_list) - 1
515             for vi in range(len(my_facesuv_list[polynum])): #polynum starting from 1 or from 0?
516                 if my_facesuv_list[polynum][vi] == vertnum:
517                     my_facesuv_list[polynum][vi] = newindex
518             #end loop on current face vertexes
519         i += 8
520     #end loop on uv pairs
521     uvcoords_dict[name] = my_uv_list
522     facesuv_dict[name] = my_facesuv_list
523     tobj.pprint ("updated %d vertexes data" % (newindex-lastindex))
524     return uvcoords_dict, facesuv_dict
525
526
527 # =================
528 # === Read tags ===
529 # =================
530 def read_tags(lwochunk):
531     data = cStringIO.StringIO(lwochunk.read())
532     tag_list = []
533     current_tag = ""
534     i = 0
535     while i < lwochunk.chunksize:
536         char = data.read(1)
537         if char == "\0":
538             tag_list.append(current_tag)
539             if (len(current_tag) % 2 == 0): char = data.read(1)
540             current_tag = ""
541         else:
542             current_tag += char
543         i += 1
544     tobj.pprint("read %d tags, list follows:" % len(tag_list))
545     tobj.pprint( tag_list)
546     return tag_list
547
548
549 # ==================
550 # === Read Ptags ===
551 # ==================
552 def read_ptags(lwochunk, tag_list):
553     data = cStringIO.StringIO(lwochunk.read())
554     polygon_type = data.read(4)
555     if polygon_type != "SURF":
556         tobj.pprint ("No Surf Were Found. Polygon Type: %s" % polygon_type)
557         return {}
558     ptag_dict = {}
559     i = 0
560     while(i < lwochunk.chunksize-4): #4 bytes polygon type already read
561         if not i%100 and my_meshtools.show_progress:
562            Blender.Window.DrawProgressBar(float(i)/lwochunk.chunksize, "Reading PTAGS")
563         poln, poln_size = read_vx(data)
564         i += poln_size
565         tag_index, = struct.unpack(">H", data.read(2))
566         if tag_index > (len(tag_list)):
567             tobj.pprint ("Reading PTAG: Surf belonging to undefined TAG: %d. Skipping" % tag_index)
568             return {}
569         i += 2
570         tag_key = tag_list[tag_index]
571         if not(ptag_dict.has_key(tag_key)):
572             ptag_dict[tag_list[tag_index]] = [poln]
573         else:
574             ptag_dict[tag_list[tag_index]].append(poln)
575     for i in ptag_dict.keys():
576         tobj.pprint ("read %d polygons belonging to TAG %s" % (len(ptag_dict[i]), i))
577     return ptag_dict
578
579
580
581 # ==================
582 # === Read Clips ===
583 # ==================
584 def read_clip(lwochunk):
585     clip_dict = {}
586     data = cStringIO.StringIO(lwochunk.read())
587     image_index, = struct.unpack(">L", data.read(4))
588     clip_dict['ID'] = image_index
589     i = 4
590     while(i < lwochunk.chunksize):
591         subchunkname, = struct.unpack("4s", data.read(4))
592         subchunklen, = struct.unpack(">H", data.read(2))
593         if subchunkname == "STIL":
594             tobj.pprint("-------- STIL")
595             clip_name, k = read_name(data)
596             #now split text independently from platform
597             #depend on the system where image was saved. NOT the one where the script is run
598             no_sep = "\\"
599             if Blender.sys.sep == no_sep: no_sep ="/"
600             if (no_sep in clip_name):
601                 clip_name = clip_name.replace(no_sep, Blender.sys.sep)
602             short_name = Blender.sys.basename(clip_name)
603             if (clip_name == "") or (short_name == ""):
604                 tobj.pprint ("Reading CLIP: Empty clip name not allowed. Skipping")
605                 discard = data.read(subchunklen-k)
606             clip_dict['NAME'] = clip_name
607             clip_dict['BASENAME'] = short_name
608         elif subchunkname == "XREF":                           #cross reference another image
609             tobj.pprint("-------- XREF")
610             image_index, = struct.unpack(">L", data.read(4))
611             clip_name, k = read_name(data)
612             clip_dict['NAME'] = clip_name
613             clip_dict['XREF'] = image_index
614         elif subchunkname == "NEGA":                           #negate texture effect
615             tobj.pprint("-------- NEGA")
616             n, = struct.unpack(">H", data.read(2))
617             clip_dict['NEGA'] = n
618         else:                                                       # Misc Chunks
619             tobj.pprint("-------- SURF:%s: skipping" % subchunkname)
620             discard = data.read(subchunklen)
621         i = i + 6 + subchunklen
622     #end loop on surf chunks
623     tobj.pprint("read image:%s" % clip_dict)
624     return clip_dict
625
626
627 # ===========================
628 # === Read Surfaces Block ===
629 # ===========================
630 def read_surfblok(subchunkdata):
631     lenght = len(subchunkdata)
632     my_dict = {}
633     my_uvname = ""
634     data = cStringIO.StringIO(subchunkdata)
635     ##############################################################
636     # blok header sub-chunk
637     ##############################################################
638     subchunkname, = struct.unpack("4s", data.read(4))
639     subchunklen, = struct.unpack(">h", data.read(2))
640     accumulate_i = subchunklen + 6
641     if subchunkname != 'IMAP':
642         tobj.pprint("---------- SURF: BLOK: %s: block aborting" % subchunkname)
643         return {}, ""
644     tobj.pprint ("---------- IMAP")
645     ordinal, i = read_name(data)
646     my_dict['ORD'] = ordinal
647     my_dict['g_ORD'] = -1
648     my_dict['ENAB'] = True
649     while(i < subchunklen): # ---------left 6------------------------- loop on header parameters
650         sub2chunkname, = struct.unpack("4s", data.read(4))
651         sub2chunklen, = struct.unpack(">h", data.read(2))
652         i = i + 6 + sub2chunklen
653         if sub2chunkname == "CHAN":
654             tobj.pprint("------------ CHAN")
655             sub2chunkname, = struct.unpack("4s", data.read(4))
656             my_dict['CHAN'] = sub2chunkname
657             sub2chunklen -= 4
658         elif sub2chunkname == "ENAB":                             #only present if is to be disabled
659             tobj.pprint("------------ ENAB")
660             ena, = struct.unpack(">h", data.read(2))
661             my_dict['ENAB'] = ena
662             sub2chunklen -= 2
663         elif sub2chunkname == "NEGA":                             #only present if is to be enabled
664             tobj.pprint("------------ NEGA")
665             ena, = struct.unpack(">h", data.read(2))
666             if ena == 1:
667                 my_dict['NEGA'] = ena
668             sub2chunklen -= 2
669         elif sub2chunkname == "OPAC":                             #only present if is to be disabled
670             tobj.pprint("------------ OPAC")
671             opa, = struct.unpack(">h", data.read(2))
672             s, = struct.unpack(">f", data.read(4))
673             envelope, env_size = read_vx(data)
674             my_dict['OPAC'] = opa
675             my_dict['OPACVAL'] = s
676             sub2chunklen -= 6
677         elif sub2chunkname == "AXIS":
678             tobj.pprint("------------ AXIS")
679             ena, = struct.unpack(">h", data.read(2))
680             my_dict['DISPLAXIS'] = ena
681             sub2chunklen -= 2
682         else:                                                       # Misc Chunks
683             tobj.pprint("------------ SURF: BLOK: IMAP: %s: skipping" % sub2chunkname)
684             discard = data.read(sub2chunklen)
685     #end loop on blok header subchunks
686     ##############################################################
687     # blok attributes sub-chunk
688     ##############################################################
689     subchunkname, = struct.unpack("4s", data.read(4))
690     subchunklen, = struct.unpack(">h", data.read(2))
691     accumulate_i += subchunklen + 6
692     if subchunkname != 'TMAP':
693         tobj.pprint("---------- SURF: BLOK: %s: block aborting" % subchunkname)
694         return {}, ""
695     tobj.pprint ("---------- TMAP")
696     i = 0
697     while(i < subchunklen): # -----------left 6----------------------- loop on header parameters
698         sub2chunkname, = struct.unpack("4s", data.read(4))
699         sub2chunklen, = struct.unpack(">h", data.read(2))
700         i = i + 6 + sub2chunklen
701         if sub2chunkname == "CNTR":
702             tobj.pprint("------------ CNTR")
703             x, y, z = struct.unpack(">fff", data.read(12))
704             envelope, env_size = read_vx(data)
705             my_dict['CNTR'] = [x, y, z]
706             sub2chunklen -= (12+env_size)
707         elif sub2chunkname == "SIZE":
708             tobj.pprint("------------ SIZE")
709             x, y, z = struct.unpack(">fff", data.read(12))
710             envelope, env_size = read_vx(data)
711             my_dict['SIZE'] = [x, y, z]
712             sub2chunklen -= (12+env_size)
713         elif sub2chunkname == "ROTA":
714             tobj.pprint("------------ ROTA")
715             x, y, z = struct.unpack(">fff", data.read(12))
716             envelope, env_size = read_vx(data)
717             my_dict['ROTA'] = [x, y, z]
718             sub2chunklen -= (12+env_size)
719         elif sub2chunkname == "CSYS":
720             tobj.pprint("------------ CSYS")
721             ena, = struct.unpack(">h", data.read(2))
722             my_dict['CSYS'] = ena
723             sub2chunklen -= 2
724         else:                                                       # Misc Chunks
725             tobj.pprint("------------ SURF: BLOK: TMAP: %s: skipping" % sub2chunkname)
726         if  sub2chunklen > 0:
727             discard = data.read(sub2chunklen)
728     #end loop on blok attributes subchunks
729     ##############################################################
730     # ok, now other attributes without sub_chunks
731     ##############################################################
732     while(accumulate_i < lenght): # ---------------------------------- loop on header parameters: lenght has already stripped the 6 bypes header
733         subchunkname, = struct.unpack("4s", data.read(4))
734         subchunklen, = struct.unpack(">H", data.read(2))
735         accumulate_i = accumulate_i + 6 + subchunklen
736         if subchunkname == "PROJ":
737             tobj.pprint("---------- PROJ")
738             p, = struct.unpack(">h", data.read(2))
739             my_dict['PROJ'] = p
740             subchunklen -= 2
741         elif subchunkname == "AXIS":
742             tobj.pprint("---------- AXIS")
743             a, = struct.unpack(">h", data.read(2))
744             my_dict['MAJAXIS'] = a
745             subchunklen -= 2
746         elif subchunkname == "IMAG":
747             tobj.pprint("---------- IMAG")
748             i, i_size = read_vx(data)
749             my_dict['IMAG'] = i
750             subchunklen -= i_size
751         elif subchunkname == "WRAP":
752             tobj.pprint("---------- WRAP")
753             ww, wh = struct.unpack(">hh", data.read(4))
754             #reduce width and height to just 1 parameter for both
755             my_dict['WRAP'] = max([ww,wh])
756             #my_dict['WRAPWIDTH'] = ww
757             #my_dict['WRAPHEIGHT'] = wh
758             subchunklen -= 4
759         elif subchunkname == "WRPW":
760             tobj.pprint("---------- WRPW")
761             w, = struct.unpack(">f", data.read(4))
762             my_dict['WRPW'] = w
763             envelope, env_size = read_vx(data)
764             subchunklen -= (env_size+4)
765         elif subchunkname == "WRPH":
766             tobj.pprint("---------- WRPH")
767             w, = struct.unpack(">f", data.read(4))
768             my_dict['WRPH'] = w
769             envelope, env_size = read_vx(data)
770             subchunklen -= (env_size+4)
771         elif subchunkname == "VMAP":
772             tobj.pprint("---------- VMAP")
773             vmp, i = read_name(data)
774             my_dict['VMAP'] = vmp
775             my_uvname = vmp
776             subchunklen -= i
777         else:                                                    # Misc Chunks
778             tobj.pprint("---------- SURF: BLOK: %s: skipping" % subchunkname)
779         if  subchunklen > 0:
780             discard = data.read(subchunklen)
781     #end loop on blok subchunks
782     return my_dict, my_uvname
783
784
785 # =====================
786 # === Read Surfaces ===
787 # =====================
788 def read_surfs(lwochunk, surf_list, tag_list):
789     my_dict = {}
790     data = cStringIO.StringIO(lwochunk.read())
791     surf_name, i = read_name(data)
792     parent_name, j = read_name(data)
793     i += j
794     if (surf_name == "") or not(surf_name in tag_list):
795         tobj.pprint ("Reading SURF: Actually empty surf name not allowed. Skipping")
796         return {}
797     if (parent_name != ""):
798         parent_index = [x['NAME'] for x in surf_list].count(parent_name)
799         if parent_index >0:
800             my_dict = surf_list[parent_index-1]
801     my_dict['NAME'] = surf_name
802     tobj.pprint ("Surface data for TAG %s" % surf_name)
803     while(i < lwochunk.chunksize):
804         subchunkname, = struct.unpack("4s", data.read(4))
805         subchunklen, = struct.unpack(">H", data.read(2))
806         i = i + 6 + subchunklen #6 bytes subchunk header
807         if subchunkname == "COLR":                             #color: mapped on color
808             tobj.pprint("-------- COLR")
809             r, g, b = struct.unpack(">fff", data.read(12))
810             envelope, env_size = read_vx(data)
811             my_dict['COLR'] = [r, g, b]
812             subchunklen -= (12+env_size)
813         elif subchunkname == "DIFF":                           #diffusion: mapped on reflection (diffuse shader)
814             tobj.pprint("-------- DIFF")
815             s, = struct.unpack(">f", data.read(4))
816             envelope, env_size = read_vx(data)
817             my_dict['DIFF'] = s
818             subchunklen -= (4+env_size)
819         elif subchunkname == "SPEC":                           #specularity: mapped to specularity (spec shader)
820             tobj.pprint("-------- SPEC")
821             s, = struct.unpack(">f", data.read(4))
822             envelope, env_size = read_vx(data)
823             my_dict['SPEC'] = s
824             subchunklen -= (4+env_size)
825         elif subchunkname == "REFL":                           #reflection: mapped on raymirror
826             tobj.pprint("-------- REFL")
827             s, = struct.unpack(">f", data.read(4))
828             envelope, env_size = read_vx(data)
829             my_dict['REFL'] = s
830             subchunklen -= (4+env_size)
831         elif subchunkname == "TRNL":                           #translucency: mapped on same param
832             tobj.pprint("-------- TRNL")
833             s, = struct.unpack(">f", data.read(4))
834             envelope, env_size = read_vx(data)
835             my_dict['TRNL'] = s
836             subchunklen -= (4+env_size)
837         elif subchunkname == "GLOS":                           #glossiness: mapped on specularity hardness (spec shader)
838             tobj.pprint("-------- GLOS")
839             s, = struct.unpack(">f", data.read(4))
840             envelope, env_size = read_vx(data)
841             my_dict['GLOS'] = s
842             subchunklen -= (4+env_size)
843         elif subchunkname == "TRAN":                           #transparency: inverted and mapped on alpha channel
844             tobj.pprint("-------- TRAN")
845             s, = struct.unpack(">f", data.read(4))
846             envelope, env_size = read_vx(data)
847             my_dict['TRAN'] = s
848             subchunklen -= (4+env_size)
849         elif subchunkname == "LUMI":                           #luminosity: mapped on emit channel
850             tobj.pprint("-------- LUMI")
851             s, = struct.unpack(">f", data.read(4))
852             envelope, env_size = read_vx(data)
853             my_dict['LUMI'] = s
854             subchunklen -= (4+env_size)
855         elif subchunkname == "GVAL":                           #glow: mapped on add channel
856             tobj.pprint("-------- GVAL")
857             s, = struct.unpack(">f", data.read(4))
858             envelope, env_size = read_vx(data)
859             my_dict['GVAL'] = s
860             subchunklen -= (4+env_size)
861         elif subchunkname == "SMAN":                           #smoothing angle
862             tobj.pprint("-------- SMAN")
863             s, = struct.unpack(">f", data.read(4))
864             my_dict['SMAN'] = s
865             subchunklen -= 4
866         elif subchunkname == "SIDE":                           #double sided?
867             tobj.pprint("-------- SIDE")                             #if 1 side do not define key
868             s, = struct.unpack(">H", data.read(2))
869             if s == 3:
870                 my_dict['SIDE'] = s
871             subchunklen -= 2
872         elif subchunkname == "RIND":                           #Refraction: mapped on IOR
873             tobj.pprint("-------- RIND")
874             s, = struct.unpack(">f", data.read(4))
875             envelope, env_size = read_vx(data)
876             my_dict['RIND'] = s
877             subchunklen -= (4+env_size)
878         elif subchunkname == "BLOK":                           #blocks
879             tobj.pprint("-------- BLOK")
880             rr, uvname = read_surfblok(data.read(subchunklen))
881             #paranoia setting: preventing adding an empty dict
882             if rr != {}:
883                 if not(my_dict.has_key('BLOK')):
884                     my_dict['BLOK'] = [rr]
885                 else:
886                     my_dict['BLOK'].append(rr)
887             if uvname != "":
888                 my_dict['UVNAME'] = uvname                            #theoretically there could be a number of them: only one used per surf
889             subchunklen = 0 #force ending
890         else:                                                       # Misc Chunks
891             tobj.pprint("-------- SURF:%s: skipping" % subchunkname)
892         if  subchunklen > 0:
893             discard = data.read(subchunklen)
894     #end loop on surf chunks
895     if my_dict.has_key('BLOK'):
896        my_dict['BLOK'].reverse()
897     return my_dict
898
899
900
901
902
903
904 # ===========================================================
905 # === Generation Routines ===================================
906 # ===========================================================
907 # ==================================================
908 # === Compute vector distance between two points ===
909 # ==================================================
910 def dist_vector (head, tail): #vector from head to tail
911     return Blender.Mathutils.Vector([head[0] - tail[0], head[1] - tail[1], head[2] - tail[2]])
912
913
914 # ================
915 # === Find Ear ===
916 # ================
917 def find_ear(normal, list_dict, verts, face):
918     nv = len(list_dict['MF'])
919     #looping through vertexes trying to find an ear
920     #most likely in case of panic
921     mlc = 0
922     mla = 1
923     mlb = 2
924
925     for c in range(nv):
926         a = (c+1) % nv; b = (a+1) % nv
927
928         if list_dict['P'][a] > 0.0: #we have to start from a convex vertex
929         #if (list_dict['P'][a] > 0.0) and (list_dict['P'][b] <= 0.0): #we have to start from a convex vertex
930             mlc = c
931             mla = a
932             mlb = b
933             #tobj.pprint ("## mmindex: %s, %s, %s  'P': %s, %s, %s" % (c, a, b, list_dict['P'][c],list_dict['P'][a],list_dict['P'][b]))
934             #tobj.pprint ("   ok, this one passed")
935             concave = 0
936             concave_inside = 0
937             for xx in range(nv): #looking for concave vertex
938                 if (list_dict['P'][xx] <= 0.0) and (xx != b) and (xx != c): #cannot be a: it's convex
939                     #ok, found concave vertex
940                     concave = 1
941                     #a, b, c, xx are all meta-meta vertex indexes
942                     mva = list_dict['MF'][a] #meta-vertex-index
943                     mvb = list_dict['MF'][b]
944                     mvc = list_dict['MF'][c]
945                     mvxx = list_dict['MF'][xx]
946                     va = face[mva] #vertex
947                     vb = face[mvb]
948                     vc = face[mvc]
949                     vxx = face[mvxx]
950
951                     #Distances
952                     d_ac_v = list_dict['D'][c]
953                     d_ba_v = list_dict['D'][a]
954                     d_cb_v = dist_vector(verts[vc], verts[vb])
955
956                     #distance from triangle points
957                     d_xxa_v = dist_vector(verts[vxx], verts[va])
958                     d_xxb_v = dist_vector(verts[vxx], verts[vb])
959                     d_xxc_v = dist_vector(verts[vxx], verts[vc])
960
961                     #normals
962                     n_xxa_v = Blender.Mathutils.CrossVecs(d_ba_v, d_xxa_v)
963                     n_xxb_v = Blender.Mathutils.CrossVecs(d_cb_v, d_xxb_v)
964                     n_xxc_v = Blender.Mathutils.CrossVecs(d_ac_v, d_xxc_v)
965
966                     #how are oriented the normals?
967                     p_xxa_v = Blender.Mathutils.DotVecs(normal, n_xxa_v)
968                     p_xxb_v = Blender.Mathutils.DotVecs(normal, n_xxb_v)
969                     p_xxc_v = Blender.Mathutils.DotVecs(normal, n_xxc_v)
970
971                     #if normals are oriented all to same directions - so it is insida
972                     if ((p_xxa_v > 0.0) and (p_xxb_v > 0.0) and (p_xxc_v > 0.0)) or ((p_xxa_v <= 0.0) and (p_xxb_v <= 0.0) and (p_xxc_v <= 0.0)):
973                         #print "vertex %d: concave inside" % xx
974                         concave_inside = 1
975                         break
976                 #endif found a concave vertex
977             #end loop looking for concave vertexes
978             if (concave == 0) or (concave_inside == 0):
979                 #no concave vertexes in polygon (should not be): return immediately
980                 #looped all concave vertexes and no one inside found
981                 return [c, a, b]
982         #no convex vertex, try another one
983     #end loop to find a suitable base vertex for ear
984     #looped all candidate ears and find no-one suitable
985     tobj.pprint ("Reducing face: no valid ear found to reduce!")
986     return [mlc, mla, mlb] #uses most likely
987
988
989
990
991 # ====================
992 # === Reduce Faces ===
993 # ====================
994 # http://www-cgrl.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/cutting_ears.html per l'import
995 def reduce_face(verts, face):
996     nv = len (face)
997     if nv == 3: return [[0,1,2]] #trivial decomposition list
998     list_dict = {}
999     #meta-vertex indexes
1000     list_dict['MF'] = range(nv) # these are meta-vertex-indexes
1001     list_dict['D'] = [None] * nv
1002     list_dict['X'] = [None] * nv
1003     list_dict['P'] = [None] * nv
1004     #list of distances
1005     for mvi in list_dict['MF']:
1006         #vector between two vertexes
1007         mvi_hiend = (mvi+1) % nv      #last-to-first
1008         vi_hiend = face[mvi_hiend] #vertex
1009         vi = face[mvi]
1010         list_dict['D'][mvi] = dist_vector(verts[vi_hiend], verts[vi])
1011     #list of cross products - normals evaluated into vertexes
1012     for vi in range(nv):
1013         list_dict['X'][vi] = Blender.Mathutils.CrossVecs(list_dict['D'][vi], list_dict['D'][vi-1])
1014     my_face_normal = Blender.Mathutils.Vector([list_dict['X'][0][0], list_dict['X'][0][1], list_dict['X'][0][2]])
1015     #list of dot products
1016     list_dict['P'][0] = 1.0
1017     for vi in range(1, nv):
1018         list_dict['P'][vi] = Blender.Mathutils.DotVecs(my_face_normal, list_dict['X'][vi])
1019     #is there at least one concave vertex?
1020     #one_concave = reduce(lambda x, y: (x) or (y<=0.0), list_dict['P'], 0)
1021     one_concave = reduce(lambda x, y: (x) + (y<0.0), list_dict['P'], 0)
1022     decomposition_list = []
1023
1024     while 1:
1025         if nv == 3: break
1026         if one_concave:
1027             #look for triangle
1028             ct = find_ear(my_face_normal, list_dict, verts, face)
1029             mv0 = list_dict['MF'][ct[0]] #meta-vertex-index
1030             mv1 = list_dict['MF'][ct[1]]
1031             mv2 = list_dict['MF'][ct[2]]
1032             #add the triangle to output list
1033             decomposition_list.append([mv0, mv1, mv2])
1034             #update data structures removing remove middle vertex from list
1035             #distances
1036             v0 = face[mv0] #vertex
1037             v1 = face[mv1]
1038             v2 = face[mv2]
1039             list_dict['D'][ct[0]] = dist_vector(verts[v2], verts[v0])
1040             #cross products
1041             list_dict['X'][ct[0]] = Blender.Mathutils.CrossVecs(list_dict['D'][ct[0]], list_dict['D'][ct[0]-1])
1042             list_dict['X'][ct[2]] = Blender.Mathutils.CrossVecs(list_dict['D'][ct[2]], list_dict['D'][ct[0]])
1043             #list of dot products
1044             list_dict['P'][ct[0]] = Blender.Mathutils.DotVecs(my_face_normal, list_dict['X'][ct[0]])
1045             list_dict['P'][ct[2]] = Blender.Mathutils.DotVecs(my_face_normal, list_dict['X'][ct[2]])
1046             #physical removal
1047             list_dict['MF'].pop(ct[1])
1048             list_dict['D'].pop(ct[1])
1049             list_dict['X'].pop(ct[1])
1050             list_dict['P'].pop(ct[1])
1051             one_concave = reduce(lambda x, y: (x) or (y<0.0), list_dict['P'], 0)
1052             nv -=1
1053         else: #here if no more concave vertexes
1054             if nv == 4: break  #quads only if no concave vertexes
1055             decomposition_list.append([list_dict['MF'][0], list_dict['MF'][1], list_dict['MF'][2]])
1056             #physical removal
1057             list_dict['MF'].pop(1)
1058             nv -=1
1059     #end while there are more my_face to triangulate
1060     decomposition_list.append(list_dict['MF'])
1061     return decomposition_list
1062
1063
1064 # =========================
1065 # === Recalculate Faces ===
1066 # =========================
1067 # --------- this do the standard face + ptag_dict + uv-map recalc
1068 def recalc_faces(verts, faces, polytag_dict, facesuv_dict):
1069     # init local face list
1070     my_faces = []
1071     # init local uvface dict
1072     my_facesuv = {}
1073     for uvname in facesuv_dict:
1074         my_facesuv[uvname] = []
1075     replaced_faces_dict = {}
1076     j = 0
1077     if len(faces)==0:
1078         return faces, polytag_dict, facesuv_dict
1079     for i in range(len(faces)):
1080         # i = index that spans on original faces
1081         # j = index that spans on new faces
1082         if not i%100 and my_meshtools.show_progress: Blender.Window.DrawProgressBar(float(i)/len(faces), "Recalculating faces")
1083         numfaceverts=len(faces[i])
1084         if numfaceverts < 4:                #This face is a triangle or quad: more strict - it has to be a triangle
1085             my_faces.append(faces[i])       #ok, leave it alone ....
1086             for uvname in facesuv_dict:
1087                 my_facesuv[uvname].append(facesuv_dict[uvname][i])
1088             replaced_faces_dict[i] = [j]     #.... but change the nuber order of the face
1089             j += 1
1090         else:                                                # Reduce n-sided convex polygon.
1091             meta_faces = reduce_face(verts, faces[i])   # Indices of triangles.
1092             this_faces = []                                  # list of triangles poly replacing original face
1093             this_faces_index = []
1094             for mf in meta_faces:
1095                 ll = len(mf)
1096                 if ll == 3: #triangle
1097                     this_faces.append([faces[i][mf[0]], faces[i][mf[1]], faces[i][mf[2]]])
1098                 else:        #quads
1099                     this_faces.append([faces[i][mf[0]], faces[i][mf[1]], faces[i][mf[2]], faces[i][mf[3]]])
1100                 for uvname in facesuv_dict:
1101                     if ll == 3:  #triangle
1102                         my_facesuv[uvname].append([facesuv_dict[uvname][i][mf[0]], facesuv_dict[uvname][i][mf[1]], facesuv_dict[uvname][i][mf[2]]])
1103                     else:        #quads
1104                         my_facesuv[uvname].append([facesuv_dict[uvname][i][mf[0]], facesuv_dict[uvname][i][mf[1]], facesuv_dict[uvname][i][mf[2]], facesuv_dict[uvname][i][mf[3]]])
1105                 this_faces_index.append(j)
1106                 j +=1
1107             my_faces.extend(this_faces)
1108             replaced_faces_dict[i] = this_faces_index   #face i substituted by this list of faces
1109         #endif on face vertex number
1110     #end loop on every face
1111     #now we have the new faces list and a dictionary replacement.
1112     #going for polygon tagging
1113     my_ptag_dict = {}
1114     for tag in polytag_dict:                                      #for every tag group
1115         my_ptag_dict[tag] = []                                    #rebuild a new entry
1116         for poly in polytag_dict[tag]:                            #take every element of old face list
1117             my_ptag_dict[tag].extend(replaced_faces_dict[poly])   #substitutes the element of new face list
1118     return my_faces, my_ptag_dict, my_facesuv
1119
1120
1121 # ========================================
1122 # === Revert list keeping first vertex ===
1123 # ========================================
1124 def revert (llist):
1125     #different flavors: the reverse one is the one that works better
1126     #rhead = [llist[0]]
1127     #rtail = llist[1:]
1128     #rhead.extend(rtail)
1129     #return rhead
1130     #--------------
1131     rhead=copy.deepcopy(llist)
1132     rhead.reverse()
1133     return rhead
1134     #--------------
1135     #return llist
1136
1137 # ====================================
1138 # === Modified Create Blender Mesh ===
1139 # ====================================
1140 def my_create_mesh(complete_vertlist, complete_facelist, current_facelist, objname, not_used_faces):
1141     #take the needed faces and update the not-used face list
1142     vertex_map = [-1] * len(complete_vertlist)
1143     cur_ptag_faces = []
1144     for ff in current_facelist:
1145         cur_face = complete_facelist[ff]
1146         cur_ptag_faces.append(cur_face)
1147         if not_used_faces != []: not_used_faces[ff] = -1
1148         for vv in cur_face:
1149             vertex_map[vv] = 1
1150         #end loop on vertex on this face
1151     #end loop on faces
1152
1153     mesh = Blender.NMesh.GetRaw()
1154
1155     #append vertexes
1156     jj = 0
1157     for i in range(len(complete_vertlist)):
1158         if vertex_map[i] == 1:
1159             if not i%100 and my_meshtools.show_progress: Blender.Window.DrawProgressBar(float(i)/len(complete_vertlist), "Generating Verts")
1160             x, y, z = complete_vertlist[i]
1161             mesh.verts.append(Blender.NMesh.Vert(x, y, z))
1162             vertex_map[i] = jj
1163             jj += 1
1164     #end sweep over vertexes
1165
1166     #append faces
1167     for i in range(len(cur_ptag_faces)):
1168         if not i%100 and my_meshtools.show_progress: Blender.Window.DrawProgressBar(float(i)/len(cur_ptag_faces), "Generating Faces")
1169         face = Blender.NMesh.Face()
1170         rev_face = revert(cur_ptag_faces[i])
1171         for vi in rev_face:
1172         #for vi in cur_ptag_faces[i]:
1173             index = vertex_map[vi]
1174             face.v.append(mesh.verts[index])
1175         #end sweep over vertexes
1176         mesh.faces.append(face)
1177     #end sweep over faces
1178
1179     if not my_meshtools.overwrite_mesh_name:
1180         objname = my_meshtools.versioned_name(objname)
1181     Blender.NMesh.PutRaw(mesh, objname)    # Name the Mesh
1182     obj = Blender.Object.GetSelected()[0]
1183     obj.name=objname        # Name the Object
1184     Blender.Redraw()
1185     return obj, not_used_faces              #return the created object
1186
1187
1188 # ============================================
1189 # === Set Subsurf attributes on given mesh ===
1190 # ============================================
1191 def set_subsurf(obj):
1192     msh = obj.getData()
1193     msh.setSubDivLevels([2, 2])
1194     msh.mode |= Blender.NMesh.Modes.SUBSURF
1195     msh.update(1)
1196     obj.makeDisplayList()
1197     return
1198
1199
1200 # =================================
1201 # === object size and dimension ===
1202 # =================================
1203 def obj_size_pos(obj):
1204     bbox = obj.getBoundBox()
1205     bbox_min = map(lambda *row: min(row), *bbox) #transpose & get min
1206     bbox_max = map(lambda *row: max(row), *bbox) #transpose & get max
1207     obj_size = (bbox_max[0]-bbox_min[0], bbox_max[1]-bbox_min[1], bbox_max[2]-bbox_min[2])
1208     obj_pos = ( (bbox_max[0]+bbox_min[0]) / 2, (bbox_max[1]+bbox_min[1]) / 2, (bbox_max[2]+bbox_min[2]) / 2)
1209     return (obj_size, obj_pos)
1210
1211
1212 # =========================
1213 # === Create the object ===
1214 # =========================
1215 def create_objects(objspec_list):
1216     nf = len(objspec_list[3])
1217     not_used_faces = range(nf)
1218     ptag_dict = objspec_list[5]
1219     obj_dict = {}  #links tag names to object, used for material assignments
1220     obj_dim_dict = {}
1221     obj_list = []  #have it handy for parent association
1222     middlechar = "+"
1223     endchar = ""
1224     if (objspec_list[6] == 1):
1225         middlechar = endchar = "#"
1226     for cur_tag in ptag_dict.keys():
1227         if ptag_dict[cur_tag] != []:
1228             cur_obj, not_used_faces= my_create_mesh(objspec_list[2], objspec_list[3], ptag_dict[cur_tag], objspec_list[0][:9]+middlechar+cur_tag[:9], not_used_faces)
1229             if objspec_list[6] == 1:
1230                 set_subsurf(cur_obj)
1231             obj_dict[cur_tag] = cur_obj
1232             obj_dim_dict[cur_tag] = obj_size_pos(cur_obj)
1233             obj_list.append(cur_obj)
1234     #end loop on current group
1235     #and what if some faces not used in any named PTAG? get rid of unused faces
1236     for ff in range(nf):
1237         tt = nf-1-ff #reverse order
1238         if not_used_faces[tt] == -1:
1239             not_used_faces.pop(tt)
1240     #end sweep on unused face list
1241     if not_used_faces != []:
1242         cur_obj, not_used_faces = my_create_mesh(objspec_list[2], objspec_list[3], not_used_faces, objspec_list[0][:9]+middlechar+"lone", [])
1243         #my_meshtools.create_mesh(objspec_list[2], not_used_faces, "_unk") #vert, faces, name
1244         #cur_obj = Blender.Object.GetSelected()[0]
1245         if objspec_list[6] == 1:
1246             set_subsurf(cur_obj)
1247         obj_dict["lone"] = cur_obj
1248         obj_dim_dict["lone"] = obj_size_pos(cur_obj)
1249         obj_list.append(cur_obj)
1250     objspec_list[1] = obj_dict
1251     objspec_list[4] = obj_dim_dict
1252     scene = Blender.Scene.getCurrent ()                   # get the current scene
1253     ob = Blender.Object.New ('Empty', objspec_list[0]+endchar)    # make empty object
1254     scene.link (ob)                                       # link the object into the scene
1255     ob.makeParent(obj_list, 1, 0)                         # set the root for created objects (no inverse, update scene hyerarchy (slow))
1256     Blender.Redraw()
1257     return
1258
1259
1260 # =====================
1261 # === Load an image ===
1262 # =====================
1263 #extensively search for image name
1264 def load_image(dir_part, name):
1265     img = None
1266     nname = Blender.sys.splitext(name)
1267     lname = [c.lower() for c in nname]
1268     ext_list = []
1269     if lname[1] != nname[1]:
1270         ext_list.append(lname[1])
1271     ext_list.extend(['.tga', '.png', '.jpg', '.gif', '.bmp'])  #order from best to worst (personal judgement) bmp last cause of nasty bug
1272     #first round: original "case"
1273     current = Blender.sys.join(dir_part, name)
1274     name_list = [current]
1275     name_list.extend([Blender.sys.makename(current, ext) for ext in ext_list])
1276     #second round: lower "case"
1277     if lname[0] != nname[0]:
1278         current = Blender.sys.join(dir_part, lname[0])
1279         name_list.extend([Blender.sys.makename(current, ext) for ext in ext_list])
1280     for nn in name_list:
1281         if Blender.sys.exists(nn) == 1:
1282             break
1283     try:
1284         img = Blender.Image.Load(nn)
1285         return img
1286     except IOError:
1287         return None
1288
1289
1290 # ===========================================
1291 # === Lookup for image index in clip_list ===
1292 # ===========================================
1293 def lookup_imag(clip_list,ima_id):
1294     for ii in clip_list:
1295         if ii['ID'] == ima_id:
1296             if ii.has_key('XREF'):
1297                 #cross reference - recursively look for images
1298                 return lookup_imag(clip_list, ii['XREF'])
1299             else:
1300                 return ii
1301     return None
1302
1303
1304 # ===================================================
1305 # === Create and assign image mapping to material ===
1306 # ===================================================
1307 def create_blok(surf, mat, clip_list, dir_part, obj_size, obj_pos):
1308
1309     def output_size_ofs(size, pos, blok):
1310         #just automate repetitive task
1311         c_map = [0,1,2]
1312         c_map_txt = ["    X--", "    -Y-", "    --Z"]
1313         if blok['MAJAXIS'] == 0:
1314             c_map = [1,2,0]
1315         if blok['MAJAXIS'] == 2:
1316             c_map = [0,2,1]
1317         tobj.pprint ("!!!axis mapping:")
1318         for mp in c_map: tobj.pprint (c_map_txt[mp])
1319
1320         s = ["1.0 (Forced)"] * 3
1321         o = ["0.0 (Forced)"] * 3
1322         if blok['SIZE'][0] > 0.0:          #paranoia controls
1323             s[0] = "%.5f" % (size[0]/blok['SIZE'][0])
1324             o[0] = "%.5f" % ((blok['CNTR'][0]-pos[0])/blok['SIZE'][0])
1325         if blok['SIZE'][1] > 0.0:
1326             s[2] = "%.5f" % (size[2]/blok['SIZE'][1])
1327             o[2] = "%.5f" % ((blok['CNTR'][1]-pos[2])/blok['SIZE'][1])
1328         if blok['SIZE'][2] > 0.0:
1329             s[1] = "%.5f" % (size[1]/blok['SIZE'][2])
1330             o[1] = "%.5f" % ((blok['CNTR'][2]-pos[1])/blok['SIZE'][2])
1331         tobj.pprint ("!!!texture size and offsets:")
1332         tobj.pprint ("    sizeX = %s; sizeY = %s; sizeZ = %s" % (s[c_map[0]], s[c_map[1]], s[c_map[2]]))
1333         tobj.pprint ("    ofsX = %s; ofsY = %s; ofsZ = %s" % (o[c_map[0]], o[c_map[1]], o[c_map[2]]))
1334         return
1335
1336     ti = 0
1337     for blok in surf['BLOK']:
1338         tobj.pprint ("#...................................................................#")
1339         tobj.pprint ("# Processing texture block no.%s for surf %s" % (ti,surf['NAME']))
1340         tobj.pprint ("#...................................................................#")
1341         tobj.pdict (blok)
1342         if ti > 9: break                                    #only 8 channels 0..7 allowed for texture mapping
1343         if not blok['ENAB']:
1344             tobj.pprint (  "***Image is not ENABled! Quitting this block")
1345             break
1346         if not(blok.has_key('IMAG')):
1347             tobj.pprint (  "***No IMAGe for this block? Quitting")
1348             break                 #extract out the image index within the clip_list
1349         tobj.pprint ("looking for image number %d" % blok['IMAG'])
1350         ima = lookup_imag(clip_list, blok['IMAG'])
1351         if ima == None:
1352             tobj.pprint (  "***Block index image not within CLIP list? Quitting Block")
1353             break                              #safety check (paranoia setting)
1354         #look for images
1355         img = load_image("",ima['NAME'])
1356         if img == None:
1357             tobj.pprint (  "***No image %s found: trying LWO file subdir" % ima['NAME'])
1358             img = load_image(dir_part,ima['BASENAME'])
1359         if img == None:
1360             tobj.pprint (  "***No image %s found in directory %s: trying Images subdir" % (ima['BASENAME'], dir_part))
1361             img = load_image(dir_part+Blender.sys.sep+"Images",ima['BASENAME'])
1362         if img == None:
1363             tobj.pprint (  "***No image %s found: trying alternate Images subdir" % ima['BASENAME'])
1364             img = load_image(dir_part+Blender.sys.sep+".."+Blender.sys.sep+"Images",ima['BASENAME'])
1365         if img == None:
1366             tobj.pprint (  "***No image %s found: giving up" % ima['BASENAME'])
1367             break
1368         #lucky we are: we have an image
1369         tname = str(ima['ID'])
1370         if blok.has_key('CHAN'):
1371             tname = tname + "+" + blok['CHAN']
1372         newtex = Blender.Texture.New(tname)
1373         newtex.setType('Image')                 # make it an image texture
1374         newtex.image = img
1375         #how does it extends beyond borders
1376         if blok.has_key('WRAP'):
1377             if (blok['WRAP'] == 3) or (blok['WRAP'] == 2):
1378                 newtex.setExtend('Extend')
1379             elif (blok['WRAP'] == 1):
1380                 newtex.setExtend('Repeat')
1381             elif (blok['WRAP'] == 0):
1382                 newtex.setExtend('Clip')
1383         tobj.pprint ("generated texture %s" % tname)
1384
1385         blendmode_list = ['Mix',
1386                          'Subtractive',
1387                          'Difference',
1388                          'Multiply',
1389                          'Divide',
1390                          'Mix (CalcAlpha already set; try setting Stencil!',
1391                          'Texture Displacement',
1392                          'Additive']
1393         set_blendmode = 7 #default additive
1394         if blok.has_key('OPAC'):
1395             set_blendmode = blok['OPAC']
1396         if set_blendmode == 5: #transparency
1397             newtex.imageFlags |= Blender.Texture.ImageFlags.CALCALPHA
1398         tobj.pprint ("!!!Set Texture -> MapTo -> Blending Mode = %s" % blendmode_list[set_blendmode])
1399
1400         set_dvar = 1.0
1401         if blok.has_key('OPACVAL'):
1402             set_dvar = blok['OPACVAL']
1403
1404         #MapTo is determined by CHAN parameter
1405         mapflag = Blender.Texture.MapTo.COL  #default to color
1406         if blok.has_key('CHAN'):
1407             if blok['CHAN'] == 'COLR':
1408                 tobj.pprint ("!!!Set Texture -> MapTo -> Col = %.3f" % set_dvar)
1409                 if set_blendmode == 0:
1410                     surf['g_IM'] = img                 #do not set anything, just save image object for later assignment
1411             if blok['CHAN'] == 'BUMP':
1412                 mapflag = Blender.Texture.MapTo.NOR
1413                 tobj.pprint ("!!!Set Texture -> MapTo -> Nor = %.3f" % set_dvar)
1414             if blok['CHAN'] == 'LUMI':
1415                 mapflag = Blender.Texture.MapTo.EMIT
1416                 tobj.pprint ("!!!Set Texture -> MapTo -> DVar = %.3f" % set_dvar)
1417             if blok['CHAN'] == 'DIFF':
1418                 mapflag = Blender.Texture.MapTo.REF
1419                 tobj.pprint ("!!!Set Texture -> MapTo -> DVar = %.3f" % set_dvar)
1420             if blok['CHAN'] == 'SPEC':
1421                 mapflag = Blender.Texture.MapTo.SPEC
1422                 tobj.pprint ("!!!Set Texture -> MapTo -> DVar = %.3f" % set_dvar)
1423             if blok['CHAN'] == 'TRAN':
1424                 mapflag = Blender.Texture.MapTo.ALPHA
1425                 tobj.pprint ("!!!Set Texture -> MapTo -> DVar = %.3f" % set_dvar)
1426         if blok.has_key('NEGA'):
1427             tobj.pprint ("!!!Watch-out: effect of this texture channel must be INVERTED!")
1428
1429         #the TexCo flag is determined by PROJ parameter
1430         if blok.has_key('PROJ'):
1431             if blok['PROJ'] == 0: #0 - Planar
1432                tobj.pprint ("!!!Flat projection")
1433                coordflag = Blender.Texture.TexCo.ORCO
1434                output_size_ofs(obj_size, obj_pos, blok)
1435             elif blok['PROJ'] == 1: #1 - Cylindrical
1436                tobj.pprint ("!!!Cylindrical projection")
1437                coordflag = Blender.Texture.TexCo.ORCO
1438                output_size_ofs(obj_size, obj_pos, blok)
1439             elif blok['PROJ'] == 2: #2 - Spherical
1440                tobj.pprint ("!!!Spherical projection")
1441                coordflag = Blender.Texture.TexCo.ORCO
1442                output_size_ofs(obj_size, obj_pos, blok)
1443             elif blok['PROJ'] == 3: #3 - Cubic
1444                tobj.pprint ("!!!Cubic projection")
1445                coordflag = Blender.Texture.TexCo.ORCO
1446                output_size_ofs(obj_size, obj_pos, blok)
1447             elif blok['PROJ'] == 4: #4 - Front Projection
1448                tobj.pprint ("!!!Front projection")
1449                coordflag = Blender.Texture.TexCo.ORCO
1450                output_size_ofs(obj_size, obj_pos, blok)
1451             elif blok['PROJ'] == 5: #5 - UV
1452                tobj.pprint ("UVMapped")
1453                coordflag = Blender.Texture.TexCo.UV
1454         mat.setTexture(ti, newtex, coordflag, mapflag)
1455         ti += 1
1456     #end loop over bloks
1457     return
1458
1459
1460
1461
1462 # ========================================
1463 # === Create and assign a new material ===
1464 # ========================================
1465 #def create_material(surf_list, ptag_dict, obj, clip_list, uv_dict, dir_part):
1466 def create_material(clip_list, surf_list, objspec, dir_part):
1467     if (surf_list == []) or (objspec[5] == {}) or (objspec[1] == {}):
1468         tobj.pprint( surf_list)
1469         tobj.pprint( objspec[5])
1470         tobj.pprint( objspec[1])
1471         tobj.pprint( "something getting wrong in create_material ...")
1472         return
1473     obj_dict = objspec[1]
1474     obj_dim_dict = objspec[4]
1475     ptag_dict = objspec[5]
1476     uvcoords_dict = objspec[7]
1477     facesuv_dict = objspec[8]
1478     for surf in surf_list:
1479         if (surf['NAME'] in ptag_dict.keys()):
1480             tobj.pprint ("#-------------------------------------------------------------------#")
1481             tobj.pprint ("Processing surface (material): %s" % surf['NAME'])
1482             tobj.pprint ("#-------------------------------------------------------------------#")
1483             #material set up
1484             facelist = ptag_dict[surf['NAME']]
1485             #bounding box and position
1486             cur_obj = obj_dict[surf['NAME']]
1487             obj_size = obj_dim_dict[surf['NAME']][0]
1488             obj_pos = obj_dim_dict[surf['NAME']][1]
1489             tobj.pprint(surf)
1490             mat = Blender.Material.New(surf['NAME'])
1491             if surf.has_key('COLR'):
1492                 mat.rgbCol = surf['COLR']
1493             if surf.has_key('LUMI'):
1494                 mat.setEmit(surf['LUMI'])
1495             if surf.has_key('GVAL'):
1496                 mat.setAdd(surf['GVAL'])
1497             if surf.has_key('SPEC'):
1498                 mat.setSpec(surf['SPEC'])                        #it should be * 2 but seems to be a bit higher lwo [0.0, 1.0] - blender [0.0, 2.0]
1499             if surf.has_key('DIFF'):
1500                 mat.setRef(surf['DIFF'])                         #lwo [0.0, 1.0] - blender [0.0, 1.0]
1501             if surf.has_key('REFL'):
1502                 mat.setRayMirr(surf['REFL'])                     #lwo [0.0, 1.0] - blender [0.0, 1.0]
1503                 #mat.setMode('RAYMIRROR')
1504                 mat.mode |= Blender.Material.Modes.RAYMIRROR
1505             #WARNING translucency not implemented yet check 2.36 API
1506             #if surf.has_key('TRNL'):
1507             #
1508             if surf.has_key('GLOS'):                             #lwo [0.0, 1.0] - blender [0, 255]
1509                 glo = int(371.67 * surf['GLOS'] - 42.334)        #linear mapping - seems to work better than exp mapping
1510                 if glo <32:  glo = 32                            #clamped to 32-255
1511                 if glo >255: glo = 255
1512                 mat.setHardness(glo)
1513             if surf.has_key('TRAN'):
1514                 mat.setAlpha(1.0-surf['TRAN'])                                        #lwo [0.0, 1.0] - blender [1.0, 0.0]
1515                 mat.mode |= Blender.Material.Modes.RAYTRANSP
1516             if surf.has_key('RIND'):
1517                 s = surf['RIND']
1518                 if s < 1.0: s = 1.0
1519                 if s > 3.0: s = 3.0
1520                 mat.setIOR(s)                                                         #clipped to blender [1.0, 3.0]
1521                 mat.mode |= Blender.Material.Modes.RAYTRANSP
1522             if surf.has_key('BLOK') and surf['BLOK'] != []:
1523                 #update the material according to texture.
1524                 create_blok(surf, mat, clip_list, dir_part, obj_size, obj_pos)
1525             #finished setting up the material
1526             #associate material to mesh
1527             msh = cur_obj.getData()
1528             mat_index = len(msh.getMaterials(1))
1529             msh.addMaterial(mat)
1530             msh.mode |= Blender.NMesh.Modes.AUTOSMOOTH                                #smooth it anyway
1531             msh.update(1)
1532             for f in range(len(msh.faces)):
1533                 msh.faces[f].materialIndex = mat_index
1534                 msh.faces[f].smooth = 1 #smooth it anyway
1535                 msh.faces[f].mode |= Blender.NMesh.FaceModes.TWOSIDE                  #set it anyway
1536                 msh.faces[f].transp = Blender.NMesh.FaceTranspModes['SOLID']
1537                 msh.faces[f].flag = Blender.NMesh.FaceTranspModes['SOLID']
1538                 if surf.has_key('SMAN'):
1539                     #not allowed mixed mode mesh (all the mesh is smoothed and all with the same angle)
1540                     #only one smoothing angle will be active! => take the max one
1541                     s = int(surf['SMAN']/3.1415926535897932384626433832795*180.0)     #lwo in radians - blender in degrees
1542                     if msh.getMaxSmoothAngle() < s: msh.setMaxSmoothAngle(s)
1543                 #if surf.has_key('SIDE'):
1544                 #    msh.faces[f].mode |= Blender.NMesh.FaceModes.TWOSIDE             #set it anyway
1545                 if surf.has_key('TRAN') and mat.getAlpha()<1.0:
1546                     msh.faces[f].transp = Blender.NMesh.FaceTranspModes['ALPHA']
1547                 if surf.has_key('UVNAME') and facesuv_dict.has_key(surf['UVNAME']):
1548                     #assign uv-data
1549                     msh.hasFaceUV(1)
1550                     #WARNING every block could have its own uvmap set of coordinate. take only the first one
1551                     facesuv_list = facesuv_dict[surf['UVNAME']]
1552                     #print "facesuv_list: ",f , facelist[f]
1553                     rev_face = revert(facesuv_list[facelist[f]])
1554                     for vi in rev_face:
1555                         msh.faces[f].uv.append(uvcoords_dict[surf['UVNAME']][vi])
1556                     if surf.has_key('g_IM'):
1557                         msh.faces[f].mode |= Blender.NMesh.FaceModes['TEX']
1558                         msh.faces[f].image = surf['g_IM']
1559             #end loop over faces
1560             msh.update(1)
1561             mat_index += 1
1562         #end if exist faces ib this object belonging to surf
1563     #end loop on surfaces
1564     return
1565
1566
1567 # ======================
1568 # === Read Faces 6.0 ===
1569 # ======================
1570 def read_faces_6(lwochunk):
1571     data = cStringIO.StringIO(lwochunk.read())
1572     faces = []
1573     polygon_type = data.read(4)
1574     subsurf = 0
1575     if polygon_type != "FACE" and polygon_type != "PTCH":
1576         tobj.pprint("No FACE/PATCH Were Found. Polygon Type: %s" % polygon_type)
1577         return "", 2
1578     if polygon_type == 'PTCH': subsurf = 1
1579     i = 0
1580     while(i < lwochunk.chunksize-4):
1581         if not i%100 and my_meshtools.show_progress:
1582             Blender.Window.DrawProgressBar(float(i)/lwochunk.chunksize, "Reading Faces")
1583         facev = []
1584         numfaceverts, = struct.unpack(">H", data.read(2))
1585         i += 2
1586
1587         for j in range(numfaceverts):
1588             index, index_size = read_vx(data)
1589             i += index_size
1590             facev.append(index)
1591         faces.append(facev)
1592     tobj.pprint("read %s faces; type of block %d (0=FACE; 1=PATCH)" % (len(faces), subsurf))
1593     return faces, subsurf
1594
1595
1596
1597 # ===========================================================
1598 # === Start the show and main callback ======================
1599 # ===========================================================
1600
1601 def fs_callback(filename):
1602     read(filename)
1603
1604 Blender.Window.FileSelector(fs_callback, "Import LWO")