copy x3d/vrml importer from blender 2.4x.
[blender.git] / release / scripts / op / io_scene_x3d / import_x3d.py
1 #!BPY
2 """
3 Name: 'X3D & VRML97 (.x3d / wrl)...'
4 Blender: 248
5 Group: 'Import'
6 Tooltip: 'Load an X3D or VRML97 file'
7 """
8
9 # ***** BEGIN GPL LICENSE BLOCK *****
10 #
11 # (C) Copyright 2008 Paravizion
12 # Written by Campbell Barton aka Ideasman42
13 #
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software Foundation,
26 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
27 #
28 # ***** END GPL LICENCE BLOCK *****
29 # --------------------------------------------------------------------------
30
31 __author__ = "Campbell Barton"
32 __url__ = ['www.blender.org', 'blenderartists.org', 'http://wiki.blender.org/index.php/Scripts/Manual/Import/X3D_VRML97']
33 __version__ = "0.1"
34
35 __bpydoc__ = """\
36 This script is an importer for the X3D and VRML97 file formats.
37 """
38
39 DEBUG = False
40
41 # This should work without a blender at all
42 try:
43         from Blender.sys import exists
44 except:
45         from os.path import exists
46
47 def baseName(path):
48         return path.split('/')[-1].split('\\')[-1]
49
50 def dirName(path):
51         return path[:-len(baseName(path))]
52
53 def imageConvertCompat(path):
54         
55         try:             import os
56         except:          return path
57         if os.sep=='\\': return path # assime win32 has quicktime, dont convert
58         
59         if path.lower().endswith('.gif'):
60                 path_to = path[:-3] + 'png'
61                 
62                 '''
63                 if exists(path_to):
64                         return path_to
65                 '''
66                 # print '\n'+path+'\n'+path_to+'\n'
67                 os.system('convert "%s" "%s"' % (path, path_to)) # for now just hope we have image magick
68                 
69                 if exists(path_to):
70                         return path_to
71         
72         return path
73
74 # notes
75 # transform are relative 
76 # order dosnt matter for loc/size/rot
77 # right handed rotation
78 # angles are in radians
79 # rotation first defines axis then ammount in radians
80
81
82
83 # =============================== VRML Spesific
84
85
86 def vrmlFormat(data):
87         '''
88         Keep this as a valid vrml file, but format in a way we can predict.
89         '''
90         # Strip all commends - # not in strings - warning multiline strings are ignored.
91         def strip_comment(l):
92                 #l = ' '.join(l.split())
93                 l = l.strip()
94                 
95                 if l.startswith('#'):
96                         return ''
97                 
98                 i = l.find('#')
99                 
100                 if i==-1:
101                         return l
102                 
103                 # Most cases accounted for! if we have a comment at the end of the line do this...
104                 #j = l.find('url "')
105                 j = l.find('"')
106                 
107                 if j == -1: # simple no strings
108                         return l[:i].strip()
109                 
110                 q = False
111                 for i,c in enumerate(l):
112                         if c == '"':
113                                 q = not q # invert
114                         
115                         elif c == '#':
116                                 if q==False:
117                                         return l[:i-1]
118                 
119                 return l
120         
121         data = '\n'.join([strip_comment(l) for l in data.split('\n') ]) # remove all whitespace
122         
123         EXTRACT_STRINGS = True # only needed when strings or filesnames containe ,[]{} chars :/
124         
125         if EXTRACT_STRINGS:
126                 
127                 # We need this so we can detect URL's
128                 data = '\n'.join([' '.join(l.split()) for l in data.split('\n')]) # remove all whitespace
129                 
130                 string_ls = []
131                 
132                 #search = 'url "'
133                 search = '"'
134                 
135                 ok = True
136                 last_i = 0
137                 while ok:
138                         ok = False
139                         i = data.find(search, last_i)
140                         if i != -1:
141                                 
142                                 start = i + len(search) # first char after end of search
143                                 end = data.find('"', start)
144                                 if end != -1:
145                                         item = data[start:end]
146                                         string_ls.append( item )
147                                         data = data[:start] + data[end:]
148                                         ok = True # keep looking
149                                         
150                                         last_i = (end - len(item)) + 1
151                                         # print last_i, item, '|' + data[last_i] + '|'
152                 
153         # done with messy extracting strings part
154         
155         
156         # Bad, dont take strings into account
157         '''
158         data = data.replace('#', '\n#')
159         data = '\n'.join([ll for l in data.split('\n') for ll in (l.strip(),) if not ll.startswith('#')]) # remove all whitespace
160         '''
161         data = data.replace('{', '\n{\n')
162         data = data.replace('}', '\n}\n')
163         data = data.replace('[', '\n[\n')
164         data = data.replace(']', '\n]\n')
165         data = data.replace(',', ' , ') # make sure comma's seperate
166         
167         if EXTRACT_STRINGS:
168                 # add strings back in 
169                 
170                 search = '"' # fill in these empty strings
171                 
172                 ok = True
173                 last_i = 0
174                 while ok:
175                         ok = False
176                         i = data.find(search + '"', last_i)
177                         # print i
178                         if i != -1:
179                                 start = i + len(search) # first char after end of search
180                                 item = string_ls.pop(0)
181                                 # print item
182                                 data = data[:start] + item + data[start:]
183                                 
184                                 last_i = start + len(item) + 1
185                                 
186                                 ok = True
187         
188         
189         # More annoying obscure cases where USE or DEF are placed on a newline
190         # data = data.replace('\nDEF ', ' DEF ')
191         # data = data.replace('\nUSE ', ' USE ')
192         
193         data = '\n'.join([' '.join(l.split()) for l in data.split('\n')]) # remove all whitespace
194         
195         # Better to parse the file accounting for multiline arrays
196         '''
197         data = data.replace(',\n', ' , ') # remove line endings with commas
198         data = data.replace(']', '\n]\n') # very very annoying - but some comma's are at the end of the list, must run this again.
199         '''
200         
201         return [l for l in data.split('\n') if l]
202
203 NODE_NORMAL = 1 # {}
204 NODE_ARRAY = 2 # []
205 NODE_REFERENCE = 3 # USE foobar
206 # NODE_PROTO = 4 #
207
208 lines = []
209
210 def getNodePreText(i, words):
211         # print lines[i]
212         use_node = False
213         while len(words) < 5:
214                 
215                 if i>=len(lines):
216                         break
217                         '''
218                 elif lines[i].startswith('PROTO'):
219                         return NODE_PROTO, i+1
220                         '''
221                 elif lines[i]=='{':
222                         # words.append(lines[i]) # no need
223                         # print "OK"
224                         return NODE_NORMAL, i+1
225                 elif lines[i].count('"') % 2 != 0: # odd number of quotes? - part of a string.
226                         # print 'ISSTRING'
227                         break
228                 else:
229                         new_words = lines[i].split()
230                         if 'USE' in new_words:
231                                 use_node = True
232                         
233                         words.extend(new_words)
234                         i += 1
235                 
236                 # Check for USE node - no {
237                 # USE #id - should always be on the same line.
238                 if use_node:
239                         # print 'LINE', i, words[:words.index('USE')+2]
240                         words[:] = words[:words.index('USE')+2]
241                         if lines[i] == '{' and lines[i+1] == '}':
242                                 # USE sometimes has {} after it anyway
243                                 i+=2 
244                         return NODE_REFERENCE, i
245                 
246         # print "error value!!!", words
247         return 0, -1
248
249 def is_nodeline(i, words):
250         
251         if not lines[i][0].isalpha():
252                 return 0, 0
253         
254         #if lines[i].startswith('field'):
255         #       return 0, 0
256         
257         # Is this a prototype??
258         if lines[i].startswith('PROTO'):
259                 words[:] = lines[i].split()
260                 return NODE_NORMAL, i+1 # TODO - assumes the next line is a '[\n', skip that
261         if lines[i].startswith('EXTERNPROTO'):
262                 words[:] = lines[i].split()
263                 return NODE_ARRAY, i+1 # TODO - assumes the next line is a '[\n', skip that
264         
265         '''
266         proto_type, new_i = is_protoline(i, words, proto_field_defs)
267         if new_i != -1:
268                 return proto_type, new_i
269         '''
270         
271         # Simple "var [" type
272         if lines[i+1] == '[':
273                 if lines[i].count('"') % 2 == 0:
274                         words[:] = lines[i].split()
275                         return NODE_ARRAY, i+2
276         
277         node_type, new_i = getNodePreText(i, words)
278         
279         if not node_type:
280                 if DEBUG: print "not node_type", lines[i]
281                 return 0, 0
282         
283         # Ok, we have a { after some values
284         # Check the values are not fields
285         for i, val in enumerate(words):
286                 if i != 0 and words[i-1] in ('DEF', 'USE'):
287                         # ignore anything after DEF, it is a ID and can contain any chars.
288                         pass
289                 elif val[0].isalpha() and val not in ('TRUE', 'FALSE'):
290                         pass
291                 else:
292                         # There is a number in one of the values, therefor we are not a node.
293                         return 0, 0
294         
295         #if node_type==NODE_REFERENCE:
296         #       print words, "REF_!!!!!!!"
297         return node_type, new_i
298
299 def is_numline(i):
300         '''
301         Does this line start with a number?
302         '''
303         
304         # Works but too slow.
305         '''
306         l = lines[i]
307         for w in l.split():
308                 if w==',':
309                         pass
310                 else:
311                         try:
312                                 float(w)
313                                 return True
314                         
315                         except:
316                                 return False
317         
318         return False
319         '''
320         
321         l = lines[i]
322         
323         line_start = 0
324         
325         if l.startswith(', '):
326                 line_start += 2
327         
328         line_end = len(l)-1
329         line_end_new = l.find(' ', line_start) # comma's always have a space before them
330         
331         if line_end_new != -1:
332                 line_end = line_end_new
333         
334         try:
335                 float(l[line_start:line_end]) # works for a float or int
336                 return True
337         except:
338                 return False
339
340
341 class vrmlNode(object):
342         __slots__ = 'id', 'fields', 'proto_node', 'proto_field_defs', 'proto_fields', 'node_type', 'parent', 'children', 'parent', 'array_data', 'reference', 'lineno', 'filename', 'blendObject', 'DEF_NAMESPACE', 'ROUTE_IPO_NAMESPACE', 'PROTO_NAMESPACE', 'x3dNode'
343         def __init__(self, parent, node_type, lineno):
344                 self.id = None
345                 self.node_type = node_type
346                 self.parent = parent
347                 self.blendObject = None
348                 self.x3dNode = None # for x3d import only
349                 if parent:
350                         parent.children.append(self)
351                 
352                 self.lineno = lineno
353                 
354                 # This is only set from the root nodes.
355                 # Having a filename also denotes a root node
356                 self.filename = None
357                 self.proto_node = None # proto field definition eg: "field SFColor seatColor .6 .6 .1"
358                 
359                 # Store in the root node because each inline file needs its own root node and its own namespace
360                 self.DEF_NAMESPACE = None 
361                 self.ROUTE_IPO_NAMESPACE = None 
362                 '''
363                 self.FIELD_NAMESPACE = None
364                 '''
365                 
366                 
367                 self.PROTO_NAMESPACE = None
368                 
369                 self.reference = None
370                 
371                 if node_type==NODE_REFERENCE:
372                         # For references, only the parent and ID are needed
373                         # the reference its self is assigned on parsing
374                         return 
375                 
376                 self.fields = [] # fields have no order, in some cases rool level values are not unique so dont use a dict
377                 
378                 self.proto_field_defs = [] # proto field definition eg: "field SFColor seatColor .6 .6 .1"
379                 self.proto_fields = [] # proto field usage "diffuseColor IS seatColor"
380                 self.children = []
381                 self.array_data = [] # use for arrays of data - should only be for NODE_ARRAY types
382                 
383         
384         # Only available from the root node
385         '''
386         def getFieldDict(self):
387                 if self.FIELD_NAMESPACE != None:
388                         return self.FIELD_NAMESPACE
389                 else:
390                         return self.parent.getFieldDict()
391         '''
392         def getProtoDict(self):
393                 if self.PROTO_NAMESPACE != None:
394                         return self.PROTO_NAMESPACE
395                 else:
396                         return self.parent.getProtoDict()
397         
398         def getDefDict(self):
399                 if self.DEF_NAMESPACE != None:
400                         return self.DEF_NAMESPACE
401                 else:
402                         return self.parent.getDefDict()
403                         
404         def getRouteIpoDict(self):
405                 if self.ROUTE_IPO_NAMESPACE != None:
406                         return self.ROUTE_IPO_NAMESPACE
407                 else:
408                         return self.parent.getRouteIpoDict()
409         
410         def setRoot(self, filename):
411                 self.filename = filename
412                 # self.FIELD_NAMESPACE =                {}
413                 self.DEF_NAMESPACE =            {}
414                 self.ROUTE_IPO_NAMESPACE =      {}
415                 self.PROTO_NAMESPACE =          {}
416         
417         def isRoot(self):
418                 if self.filename == None:
419                         return False
420                 else:
421                         return True
422         
423         def getFilename(self):
424                 if self.filename:
425                         return self.filename
426                 elif self.parent:
427                         return self.parent.getFilename()
428                 else:
429                         return None
430         
431         def getRealNode(self):
432                 if self.reference:
433                         return self.reference
434                 else:
435                         return self
436         
437         def getSpec(self):
438                 self_real = self.getRealNode()
439                 try:
440                         return self_real.id[-1] # its possible this node has no spec
441                 except:
442                         return None
443         
444         def findSpecRecursive(self, spec):
445                 self_real = self.getRealNode()
446                 if spec == self_real.getSpec():
447                         return self
448                 
449                 for child in self_real.children:
450                         if child.findSpecRecursive(spec):
451                                 return child
452                 
453                 return None
454         
455         def getPrefix(self):
456                 if self.id:
457                         return self.id[0]
458                 return None
459         
460         def getSpecialTypeName(self, typename):
461                 self_real = self.getRealNode()
462                 try:            return self_real.id[ list(self_real.id).index(typename)+1 ]
463                 except: return None
464         
465         
466         def getDefName(self):
467                 return self.getSpecialTypeName('DEF')
468         
469         def getProtoName(self):
470                 return self.getSpecialTypeName('PROTO')
471                 
472         def getExternprotoName(self):
473                 return self.getSpecialTypeName('EXTERNPROTO')
474         
475         def getChildrenBySpec(self, node_spec): # spec could be Transform, Shape, Appearance
476                 self_real = self.getRealNode()
477                 # using getSpec functions allows us to use the spec of USE children that dont have their spec in their ID
478                 if type(node_spec) == str:
479                         return [child for child in self_real.children if child.getSpec()==node_spec]
480                 else:
481                         # Check inside a list of optional types
482                         return [child for child in self_real.children if child.getSpec() in node_spec]
483         
484         def getChildBySpec(self, node_spec): # spec could be Transform, Shape, Appearance
485                 # Use in cases where there is only ever 1 child of this type
486                 ls = self.getChildrenBySpec(node_spec)
487                 if ls: return ls[0]
488                 else: return None
489         
490         def getChildrenByName(self, node_name): # type could be geometry, children, appearance
491                 self_real = self.getRealNode()
492                 return [child for child in self_real.children if child.id if child.id[0]==node_name]
493         
494         def getChildByName(self, node_name):
495                 self_real = self.getRealNode()
496                 for child in self_real.children:
497                         if child.id and child.id[0]==node_name: # and child.id[-1]==node_spec:
498                                 return child
499         
500         def getSerialized(self, results, ancestry):
501                 '''     Return this node and all its children in a flat list '''
502                 ancestry = ancestry[:] # always use a copy
503                 
504                 # self_real = self.getRealNode()
505                 
506                 results.append((self, tuple(ancestry)))
507                 ancestry.append(self)
508                 for child in self.getRealNode().children:
509                         if child not in ancestry:
510                                 # We dont want to load proto's, they are only references
511                                 # We could enforce this elsewhere
512                                 
513                                 # Only add this in a very special case
514                                 # where the parent of this object is not the real parent
515                                 # - In this case we have added the proto as a child to a node instancing it.
516                                 # This is a bit arbitary, but its how Proto's are done with this importer.
517                                 if child.getProtoName() == None and child.getExternprotoName() == None:
518                                         child.getSerialized(results, ancestry)
519                                 else:
520                                         
521                                         if DEBUG: print 'getSerialized() is proto:', child.getProtoName(), child.getExternprotoName(), self.getSpec()
522                                         
523                                         self_spec = self.getSpec()
524                                         
525                                         if child.getProtoName() == self_spec or child.getExternprotoName() == self_spec:
526                                                 if DEBUG: "FoundProto!"
527                                                 child.getSerialized(results, ancestry)
528                                         
529                                         
530                 
531                 return results
532                 
533         def searchNodeTypeID(self, node_spec, results):
534                 self_real = self.getRealNode()
535                 # print self.lineno, self.id
536                 if self_real.id and self_real.id[-1]==node_spec: # use last element, could also be only element
537                         results.append(self_real)
538                 for child in self_real.children:
539                         child.searchNodeTypeID(node_spec, results)
540                 return results
541         
542         def getFieldName(self, field, ancestry, AS_CHILD=False):
543                 self_real = self.getRealNode() # incase we're an instance
544                 
545                 for f in self_real.fields:
546                         # print f
547                         if f and f[0] == field:
548                                 # print '\tfound field', f
549                                 
550                                 if len(f)>=3 and f[1] == 'IS': # eg: 'diffuseColor IS legColor'
551                                         field_id = f[2]
552                                         
553                                         # print "\n\n\n\n\n\nFOND IS!!!"
554                                         f_proto_lookup = None
555                                         f_proto_child_lookup = None
556                                         i = len(ancestry)
557                                         while i:
558                                                 i -= 1
559                                                 node = ancestry[i]
560                                                 node = node.getRealNode()
561                                                 
562                                                 # proto settings are stored in "self.proto_node"
563                                                 if node.proto_node: 
564                                                         # Get the default value from the proto, this can be overwridden by the proto instace
565                                                         # 'field SFColor legColor .8 .4 .7'
566                                                         if AS_CHILD:
567                                                                 for child in node.proto_node.children:
568                                                                         #if child.id  and  len(child.id) >= 3  and child.id[2]==field_id:
569                                                                         if child.id and ('point' in child.id or 'points' in child.id):
570                                                                                 f_proto_child_lookup = child
571                                                         
572                                                         else:
573                                                                 for f_def in node.proto_node.proto_field_defs:
574                                                                         if len(f_def) >= 4:
575                                                                                 if f_def[0]=='field' and f_def[2]==field_id:
576                                                                                         f_proto_lookup = f_def[3:]
577                                                 
578                                                 # Node instance, Will be 1 up from the proto-node in the ancestry list. but NOT its parent.
579                                                 # This is the setting as defined by the instance, including this setting is optional,
580                                                 # and will override the default PROTO value 
581                                                 # eg: 'legColor 1 0 0'
582                                                 if AS_CHILD:
583                                                         for child in node.children:
584                                                                 if child.id and child.id[0]==field_id:
585                                                                         f_proto_child_lookup = child
586                                                 else:
587                                                         for f_def in node.fields:
588                                                                 if len(f_def) >= 2:
589                                                                         if f_def[0]==field_id:
590                                                                                 if DEBUG: print "getFieldName(), found proto", f_def
591                                                                                 f_proto_lookup = f_def[1:]
592                                                 
593                                         
594                                         if AS_CHILD:
595                                                 if f_proto_child_lookup:
596                                                         if DEBUG:
597                                                                 print "getFieldName() - AS_CHILD=True, child found"
598                                                                 print f_proto_child_lookup
599                                                 return f_proto_child_lookup
600                                         else:
601                                                 return f_proto_lookup
602                                 else:
603                                         if AS_CHILD:
604                                                 return None
605                                         else:
606                                                 # Not using a proto
607                                                 return f[1:]
608                                                 
609                 # print '\tfield not found', field
610                 
611                 
612                 # See if this is a proto name
613                 if AS_CHILD:
614                         child_array = None
615                         for child in self_real.children:
616                                 if child.id and len(child.id) == 1 and child.id[0] == field:
617                                         return child
618                 
619                 return None
620         
621         def getFieldAsInt(self, field, default, ancestry):
622                 self_real = self.getRealNode() # incase we're an instance
623                 
624                 f = self_real.getFieldName(field, ancestry)
625                 if f==None:     return default
626                 if ',' in f: f = f[:f.index(',')] # strip after the comma
627                 
628                 if len(f) != 1:
629                         print '\t"%s" wrong length for int conversion for field "%s"' % (f, field)
630                         return default
631                 
632                 try:
633                         return int(f[0])
634                 except:
635                         print '\tvalue "%s" could not be used as an int for field "%s"' % (f[0], field)
636                         return default
637         
638         def getFieldAsFloat(self, field, default, ancestry):
639                 self_real = self.getRealNode() # incase we're an instance
640                 
641                 f = self_real.getFieldName(field, ancestry)
642                 if f==None:     return default
643                 if ',' in f: f = f[:f.index(',')] # strip after the comma
644                 
645                 if len(f) != 1:
646                         print '\t"%s" wrong length for float conversion for field "%s"' % (f, field)
647                         return default
648                 
649                 try:
650                         return float(f[0])
651                 except:
652                         print '\tvalue "%s" could not be used as a float for field "%s"' % (f[0], field)
653                         return default
654         
655         def getFieldAsFloatTuple(self, field, default, ancestry):
656                 self_real = self.getRealNode() # incase we're an instance
657                 
658                 f = self_real.getFieldName(field, ancestry)
659                 if f==None:     return default
660                 # if ',' in f: f = f[:f.index(',')] # strip after the comma
661                 
662                 if len(f) < 1:
663                         print '"%s" wrong length for float tuple conversion for field "%s"' % (f, field)
664                         return default
665                 
666                 ret = []
667                 for v in f:
668                         if v != ',':
669                                 try:            ret.append(float(v))
670                                 except:         break # quit of first non float, perhaps its a new field name on the same line? - if so we are going to ignore it :/ TODO
671                 # print ret
672                 
673                 if ret:
674                         return ret
675                 if not ret:
676                         print '\tvalue "%s" could not be used as a float tuple for field "%s"' % (f, field)
677                         return default
678         
679         def getFieldAsBool(self, field, default, ancestry):
680                 self_real = self.getRealNode() # incase we're an instance
681                 
682                 f = self_real.getFieldName(field, ancestry)
683                 if f==None:     return default
684                 if ',' in f: f = f[:f.index(',')] # strip after the comma
685                 
686                 if len(f) != 1:
687                         print '\t"%s" wrong length for bool conversion for field "%s"' % (f, field)
688                         return default
689                 
690                 if f[0].upper()=='"TRUE"' or f[0].upper()=='TRUE':
691                         return True
692                 elif f[0].upper()=='"FALSE"' or f[0].upper()=='FALSE':
693                         return False
694                 else:
695                         print '\t"%s" could not be used as a bool for field "%s"' % (f[1], field)
696                         return default
697         
698         def getFieldAsString(self, field, default, ancestry):
699                 self_real = self.getRealNode() # incase we're an instance
700                 
701                 f = self_real.getFieldName(field, ancestry)
702                 if f==None:     return default
703                 if len(f) < 1:
704                         print '\t"%s" wrong length for string conversion for field "%s"' % (f, field)
705                         return default
706                 
707                 if len(f) > 1:
708                         # String may contain spaces
709                         st = ' '.join(f)
710                 else:
711                         st = f[0]
712                 
713                 # X3D HACK 
714                 if self.x3dNode: 
715                         return st
716                         
717                 if st[0]=='"' and st[-1]=='"':
718                         return st[1:-1]
719                 else:
720                         print '\tvalue "%s" could not be used as a string for field "%s"' % (f[0], field)
721                         return default
722         
723         def getFieldAsArray(self, field, group, ancestry):
724                 '''
725                 For this parser arrays are children
726                 '''
727                 self_real = self.getRealNode() # incase we're an instance
728                 
729                 child_array = self_real.getFieldName(field, ancestry, True)
730                 
731                 #if type(child_array)==list: # happens occasionaly
732                 #       array_data = child_array 
733                         
734                 if child_array==None:
735                         
736                         # For x3d, should work ok with vrml too
737                         # for x3d arrays are fields, vrml they are nodes, annoying but not tooo bad.
738                         data_split = self.getFieldName(field, ancestry)
739                         if not data_split:
740                                 return []
741                         array_data = ' '.join(data_split)
742                         if array_data == None:
743                                 return []
744                         
745                         array_data = array_data.replace(',', ' ')
746                         data_split = array_data.split()
747                         try:
748                                 array_data = [int(val) for val in data_split]
749                         except:
750                                 try:
751                                         array_data = [float(val) for val in data_split]
752                                 except:
753                                         print '\tWarning, could not parse array data from field'
754                                         array_data = []
755                 else:
756                         # print child_array
757                         # Normal vrml
758                         array_data = child_array.array_data
759                         
760                 
761                 # print 'array_data', array_data
762                 
763                 if group==-1 or len(array_data)==0:
764                         return array_data
765                 
766                 # We want a flat list
767                 flat = True
768                 for item in array_data:
769                         if type(item) == list:
770                                 flat = False
771                                 break
772                 
773                 # make a flat array
774                 if flat:
775                         flat_array = array_data # we are alredy flat.
776                 else:
777                         flat_array = []
778                         
779                         def extend_flat(ls):
780                                 for item in ls:
781                                         if type(item)==list:    extend_flat(item)
782                                         else:                                   flat_array.append(item)
783                         
784                         extend_flat(array_data)
785                 
786                 
787                 # We requested a flat array
788                 if group == 0:
789                         return flat_array
790                 
791                 new_array = []
792                 sub_array = []
793                 
794                 for item in flat_array:
795                         sub_array.append(item)
796                         if len(sub_array)==group:
797                                 new_array.append(sub_array)
798                                 sub_array = []
799                 
800                 if sub_array:
801                         print '\twarning, array was not aligned to requested grouping', group, 'remaining value', sub_array
802                 
803                 return new_array
804         
805         def getFieldAsStringArray(self, field, ancestry):
806                 '''
807                 Get a list of strings
808                 '''
809                 self_real = self.getRealNode() # incase we're an instance
810                 
811                 child_array = None
812                 for child in self_real.children:
813                         if child.id and len(child.id) == 1 and child.id[0] == field:
814                                 child_array = child
815                                 break
816                 if not child_array:
817                         return []
818                 
819                 # each string gets its own list, remove ""'s
820                 try:
821                         new_array = [f[0][1:-1] for f in child_array.fields]
822                 except:
823                         print '\twarning, string array could not be made'
824                         new_array = []
825                 
826                 return new_array
827         
828         
829         def getLevel(self):
830                 # Ignore self_real
831                 level = 0
832                 p = self.parent
833                 while p:
834                         level +=1
835                         p = p.parent
836                         if not p: break
837                         
838                 return level
839         
840         def __repr__(self):
841                 level = self.getLevel()
842                 ind = '  ' * level
843                 if self.node_type==NODE_REFERENCE:
844                         brackets = ''
845                 elif self.node_type==NODE_NORMAL:
846                         brackets = '{}'
847                 else:
848                         brackets = '[]'
849                 
850                 if brackets:
851                         text = ind + brackets[0] + '\n'
852                 else:
853                         text = ''
854                 
855                 text += ind + 'ID: ' + str(self.id) + ' ' + str(level) + (' lineno %d\n' % self.lineno)
856                 
857                 if self.node_type==NODE_REFERENCE:
858                         text += ind + "(reference node)\n"
859                         return text
860                 
861                 if self.proto_node:
862                         text += ind + 'PROTO NODE...\n'
863                         text += str(self.proto_node)
864                         text += ind + 'PROTO NODE_DONE\n'
865                 
866                 text += ind + 'FIELDS:' + str(len(self.fields)) + '\n'
867                 
868                 for i,item in enumerate(self.fields):
869                         text += ind + 'FIELD:\n'
870                         text += ind + str(item) +'\n'
871
872                 text += ind + 'PROTO_FIELD_DEFS:' + str(len(self.proto_field_defs)) + '\n'
873                 
874                 for i,item in enumerate(self.proto_field_defs):
875                         text += ind + 'PROTO_FIELD:\n'
876                         text += ind + str(item) +'\n'
877
878                 text += ind + 'ARRAY: ' + str(len(self.array_data)) + ' ' + str(self.array_data) + '\n'
879                 #text += ind + 'ARRAY: ' + str(len(self.array_data)) + '[...] \n'
880                 
881                 text += ind + 'CHILDREN: ' + str(len(self.children)) + '\n'
882                 for i, child in enumerate(self.children):
883                         text += ind + ('CHILD%d:\n' % i)
884                         text += str(child)
885                 
886                 text += '\n' + ind + brackets[1]
887                 
888                 return text
889         
890         def parse(self, i, IS_PROTO_DATA=False):
891                 new_i = self.__parse(i, IS_PROTO_DATA)
892                 
893                 # print self.id, self.getFilename()
894                 
895                 # Check if this node was an inline or externproto
896                 
897                 url_ls = []
898                 
899                 if self.node_type == NODE_NORMAL and self.getSpec() == 'Inline':
900                         ancestry = [] # Warning! - PROTO's using this wont work at all.
901                         url = self.getFieldAsString('url', None, ancestry)
902                         if url:
903                                 url_ls = [(url, None)]
904                         del ancestry
905                 
906                 elif self.getExternprotoName():
907                         # externproto
908                         url_ls = []
909                         for f in self.fields:
910                                 
911                                 if type(f)==str:
912                                         f = [f]
913                                 
914                                 for ff in f:
915                                         for f_split in ff.split('"'):
916                                                 # print f_split
917                                                 # "someextern.vrml#SomeID"
918                                                 if '#' in f_split:
919                                                         
920                                                         f_split, f_split_id = f_split.split('#') # there should only be 1 # anyway
921                                                         
922                                                         url_ls.append( (f_split, f_split_id) )
923                                                 else:
924                                                         url_ls.append( (f_split, None) )
925                 
926                 
927                 # Was either an Inline or an EXTERNPROTO
928                 if url_ls:
929                         
930                         # print url_ls
931                         
932                         for url, extern_key in url_ls:
933                                 print url
934                                 urls = []
935                                 urls.append( url )
936                                 urls.append( BPySys.caseInsensitivePath(urls[-1]) )
937                                 
938                                 urls.append( dirName(self.getFilename()) + url )
939                                 urls.append( BPySys.caseInsensitivePath(urls[-1]) )
940                                 
941                                 urls.append( dirName(self.getFilename()) + baseName(url) )
942                                 urls.append( BPySys.caseInsensitivePath(urls[-1]) )
943                                 
944                                 try:
945                                         url = [url for url in urls if exists(url)][0]
946                                         url_found = True
947                                 except:
948                                         url_found = False
949                                 
950                                 if not url_found:
951                                         print '\tWarning: Inline URL could not be found:', url
952                                 else:
953                                         if url==self.getFilename(): 
954                                                 print '\tWarning: cant Inline yourself recursively:', url
955                                         else:
956                                                 
957                                                 try:
958                                                         data = gzipOpen(url)
959                                                 except:
960                                                         print '\tWarning: cant open the file:', url
961                                                         data = None
962                                                 
963                                                 if data:
964                                                         # Tricky - inline another VRML
965                                                         print '\tLoading Inline:"%s"...' % url
966                                                         
967                                                         # Watch it! - backup lines
968                                                         lines_old = lines[:]
969                                                         
970                                                         
971                                                         lines[:] = vrmlFormat( data )
972                                                         
973                                                         lines.insert(0, '{')
974                                                         lines.insert(0, 'root_node____')
975                                                         lines.append('}')
976                                                         '''
977                                                         ff = open('/tmp/test.txt', 'w')
978                                                         ff.writelines([l+'\n' for l in lines])
979                                                         '''
980                                                         
981                                                         child = vrmlNode(self, NODE_NORMAL, -1)
982                                                         child.setRoot(url) # initialized dicts
983                                                         child.parse(0)
984                                                         
985                                                         # if self.getExternprotoName():
986                                                         if self.getExternprotoName():
987                                                                 if not extern_key: # if none is spesified - use the name
988                                                                         extern_key = self.getSpec()
989                                                                 
990                                                                 if extern_key:
991                                                                         
992                                                                         self.children.remove(child)
993                                                                         child.parent = None
994                                                                         
995                                                                         extern_child = child.findSpecRecursive(extern_key)
996                                                                         
997                                                                         if extern_child:
998                                                                                 self.children.append(extern_child)
999                                                                                 extern_child.parent = self
1000                                                                                 
1001                                                                                 if DEBUG: print "\tEXTERNPROTO ID found!:", extern_key
1002                                                                         else:
1003                                                                                 print "\tEXTERNPROTO ID not found!:", extern_key
1004                                                                         
1005                                                         # Watch it! - restore lines
1006                                                         lines[:] = lines_old
1007                 
1008                 return new_i
1009         
1010         def __parse(self, i, IS_PROTO_DATA=False):
1011                 '''
1012                 print 'parsing at', i,
1013                 print i, self.id, self.lineno
1014                 '''
1015                 l = lines[i]
1016                 
1017                 if l=='[':
1018                         # An anonymous list
1019                         self.id = None
1020                         i+=1
1021                 else:
1022                         words = []
1023                         
1024                         node_type, new_i = is_nodeline(i, words)
1025                         if not node_type: # fail for parsing new node.
1026                                 print "Failed to parse new node"
1027                                 raise ValueError
1028                         
1029                         if self.node_type==NODE_REFERENCE:
1030                                 # Only assign the reference and quit
1031                                 key = words[words.index('USE')+1]
1032                                 self.id = (words[0],)
1033                                 
1034                                 self.reference = self.getDefDict()[key]
1035                                 return new_i
1036                         
1037                         self.id = tuple(words)
1038                         
1039                         # fill in DEF/USE
1040                         key = self.getDefName()
1041                         if key != None:
1042                                 self.getDefDict()[ key ] = self
1043                         
1044                         key = self.getProtoName()
1045                         if not key:     key = self.getExternprotoName()
1046                         
1047                         proto_dict = self.getProtoDict()
1048                         if key != None:
1049                                 proto_dict[ key ] = self
1050                                 
1051                                 # Parse the proto nodes fields
1052                                 self.proto_node = vrmlNode(self, NODE_ARRAY, new_i)
1053                                 new_i = self.proto_node.parse(new_i)
1054                                 
1055                                 self.children.remove(self.proto_node)
1056                                 
1057                                 # print self.proto_node
1058                                 
1059                                 new_i += 1 # skip past the {
1060                                 
1061                         
1062                         else: # If we're a proto instance, add the proto node as our child.
1063                                 spec = self.getSpec()
1064                                 try:
1065                                         self.children.append( proto_dict[spec] )
1066                                         #pass
1067                                 except:
1068                                         pass
1069                                 
1070                                 del spec
1071                         
1072                         del proto_dict, key
1073                         
1074                         i = new_i
1075                 
1076                 # print self.id
1077                 ok = True
1078                 while ok:
1079                         if i>=len(lines):
1080                                 return len(lines)-1
1081                         
1082                         l = lines[i]
1083                         # print '\tDEBUG:', i, self.node_type, l
1084                         if l=='':
1085                                 i+=1
1086                                 continue 
1087                         
1088                         if l=='}':
1089                                 if self.node_type != NODE_NORMAL: # also ends proto nodes, we may want a type for these too.
1090                                         print 'wrong node ending, expected an } ' + str(i) + ' ' + str(self.node_type)
1091                                         if DEBUG:
1092                                                 raise ValueError
1093                                 ### print "returning", i
1094                                 return i+1
1095                         if l==']':
1096                                 if self.node_type != NODE_ARRAY:
1097                                         print 'wrong node ending, expected a ] ' + str(i) + ' ' + str(self.node_type)
1098                                         if DEBUG:
1099                                                 raise ValueError
1100                                 ### print "returning", i
1101                                 return i+1
1102                                 
1103                         node_type, new_i = is_nodeline(i, [])
1104                         if node_type: # check text\n{
1105                                 child = vrmlNode(self, node_type, i)
1106                                 i = child.parse(i)
1107                                 
1108                         elif l=='[': # some files have these anonymous lists
1109                                 child = vrmlNode(self, NODE_ARRAY, i)
1110                                 i = child.parse(i)
1111                                 
1112                         elif is_numline(i):
1113                                 l_split = l.split(',')
1114                                 
1115                                 values = None
1116                                 # See if each item is a float?
1117                                 
1118                                 for num_type in (int, float):
1119                                         try:
1120                                                 values = [num_type(v) for v in l_split ]
1121                                                 break
1122                                         except:
1123                                                 pass
1124                                         
1125                                         
1126                                         try:
1127                                                 values = [[num_type(v) for v in segment.split()] for segment in l_split ]
1128                                                 break
1129                                         except:
1130                                                 pass
1131                                 
1132                                 if values == None: # dont parse
1133                                         values = l_split
1134                                 
1135                                 # This should not extend over multiple lines however it is possible
1136                                 # print self.array_data
1137                                 if values:
1138                                         self.array_data.extend( values )
1139                                 i+=1
1140                         else:
1141                                 words = l.split()
1142                                 if len(words) > 2 and words[1] == 'USE':
1143                                         vrmlNode(self, NODE_REFERENCE, i)
1144                                 else:
1145                                         
1146                                         # print "FIELD", i, l
1147                                         # 
1148                                         #words = l.split()
1149                                         ### print '\t\ttag', i
1150                                         # this is a tag/
1151                                         # print words, i, l
1152                                         value = l
1153                                         # print i
1154                                         # javastrips can exist as values.
1155                                         quote_count = l.count('"')
1156                                         if quote_count % 2: # odd number?
1157                                                 # print 'MULTILINE'
1158                                                 while 1:
1159                                                         i+=1
1160                                                         l = lines[i]
1161                                                         quote_count = l.count('"')
1162                                                         if quote_count % 2: # odd number?
1163                                                                 value += '\n'+ l[:l.rfind('"')]
1164                                                                 break # assume
1165                                                         else:
1166                                                                 value += '\n'+ l
1167                                         
1168                                         value_all = value.split()
1169                                         
1170                                         def iskey(k):
1171                                                 if k[0] != '"' and k[0].isalpha() and k.upper() not in ('TRUE', 'FALSE'):
1172                                                         return True
1173                                                 return False
1174                                         
1175                                         def split_fields(value):
1176                                                 '''
1177                                                 key 0.0 otherkey 1,2,3 opt1 opt1 0.0
1178                                                         -> [key 0.0], [otherkey 1,2,3], [opt1 opt1 0.0]
1179                                                 '''
1180                                                 field_list = []
1181                                                 field_context = []
1182                                                 
1183                                                 for j in xrange(len(value)):
1184                                                         if iskey(value[j]):
1185                                                                 if field_context:
1186                                                                         # this IS a key but the previous value was not a key, ot it was a defined field.
1187                                                                         if (not iskey(field_context[-1])) or ((len(field_context)==3 and field_context[1]=='IS')):
1188                                                                                 field_list.append(field_context)
1189                                                                                 
1190                                                                                 field_context = [value[j]]
1191                                                                         else:
1192                                                                                 # The last item was not a value, multiple keys are needed in some cases.
1193                                                                                 field_context.append(value[j])
1194                                                                 else:
1195                                                                         # Is empty, just add this on
1196                                                                         field_context.append(value[j])
1197                                                         else:
1198                                                                 # Add a value to the list
1199                                                                 field_context.append(value[j])
1200                                                 
1201                                                 if field_context:
1202                                                         field_list.append(field_context)
1203                                                 
1204                                                 return field_list
1205                                         
1206                                         
1207                                         for value in split_fields(value_all):
1208                                                 # Split 
1209                                                 
1210                                                 if value[0]=='field':
1211                                                         # field SFFloat creaseAngle 4
1212                                                         self.proto_field_defs.append(value)
1213                                                 else:
1214                                                         self.fields.append(value)
1215                                 i+=1
1216
1217 def gzipOpen(path):
1218         try: import gzip
1219         except: gzip = None
1220         
1221         data = None
1222         if gzip:
1223                 try: data = gzip.open(path, 'r').read()
1224                 except: pass
1225         else:
1226                 print '\tNote, gzip module could not be imported, compressed files will fail to load'
1227         
1228         if data==None:
1229                 try:    data = open(path, 'rU').read()
1230                 except: pass
1231         
1232         return data
1233
1234 def vrml_parse(path):
1235         '''
1236         Sets up the root node and returns it so load_web3d() can deal with the blender side of things.
1237         Return root (vrmlNode, '') or (None, 'Error String')
1238         '''
1239         data = gzipOpen(path)
1240         
1241         if data==None:
1242                 return None, 'Failed to open file: ' + path
1243         
1244         # Stripped above
1245         lines[:] = vrmlFormat( data )
1246         
1247         lines.insert(0, '{')
1248         lines.insert(0, 'dymmy_node')
1249         lines.append('}')
1250         # Use for testing our parsed output, so we can check on line numbers.
1251         
1252         '''
1253         ff = open('/tmp/test.txt', 'w')
1254         ff.writelines([l+'\n' for l in lines])
1255         ff.close()
1256         '''
1257         
1258         # Now evaluate it
1259         node_type, new_i = is_nodeline(0, [])
1260         if not node_type:
1261                 return None, 'Error: VRML file has no starting Node'
1262         
1263         # Trick to make sure we get all root nodes.
1264         lines.insert(0, '{')
1265         lines.insert(0, 'root_node____') # important the name starts with an ascii char
1266         lines.append('}')
1267         
1268         root = vrmlNode(None, NODE_NORMAL, -1)
1269         root.setRoot(path) # we need to set the root so we have a namespace and know the path incase of inlineing
1270         
1271         # Parse recursively
1272         root.parse(0)
1273         
1274         # This prints a load of text
1275         if DEBUG:
1276                 print root
1277         
1278         return root, ''
1279
1280
1281 # ====================== END VRML 
1282
1283
1284
1285 # ====================== X3d Support
1286
1287 # Sane as vrml but replace the parser
1288 class x3dNode(vrmlNode):
1289         def __init__(self, parent, node_type, x3dNode):
1290                 vrmlNode.__init__(self, parent, node_type, -1)
1291                 self.x3dNode = x3dNode
1292                 
1293         def parse(self, IS_PROTO_DATA=False):
1294                 # print self.x3dNode.tagName
1295                 
1296                 define = self.x3dNode.getAttributeNode('DEF')
1297                 if define:
1298                         self.getDefDict()[define.value] = self
1299                 else:
1300                         use = self.x3dNode.getAttributeNode('USE')
1301                         if use:
1302                                 try:
1303                                         self.reference = self.getDefDict()[use.value]
1304                                         self.node_type = NODE_REFERENCE
1305                                 except:
1306                                         print '\tWarning: reference', use.value, 'not found'
1307                                         self.parent.children.remove(self)
1308                                 
1309                                 return
1310                 
1311                 for x3dChildNode in self.x3dNode.childNodes:
1312                         if x3dChildNode.nodeType in (x3dChildNode.TEXT_NODE, x3dChildNode.COMMENT_NODE, x3dChildNode.CDATA_SECTION_NODE):
1313                                 continue
1314                         
1315                         node_type = NODE_NORMAL
1316                         # print x3dChildNode, dir(x3dChildNode)
1317                         if x3dChildNode.getAttributeNode('USE'):
1318                                 node_type = NODE_REFERENCE
1319                         
1320                         child = x3dNode(self, node_type, x3dChildNode)
1321                         child.parse()
1322                 
1323                 # TODO - x3d Inline
1324                 
1325         def getSpec(self):
1326                 return self.x3dNode.tagName # should match vrml spec
1327         
1328         def getDefName(self):
1329                 data = self.x3dNode.getAttributeNode('DEF')
1330                 if data: data.value
1331                 return None
1332         
1333         # Other funcs operate from vrml, but this means we can wrap XML fields, still use nice utility funcs
1334         # getFieldAsArray getFieldAsBool etc
1335         def getFieldName(self, field, ancestry, AS_CHILD=False):
1336                 # ancestry and AS_CHILD are ignored, only used for VRML now
1337                 
1338                 self_real = self.getRealNode() # incase we're an instance
1339                 field_xml = self.x3dNode.getAttributeNode(field)
1340                 if field_xml:
1341                         value = field_xml.value
1342                         
1343                         # We may want to edit. for x3d spesific stuff
1344                         # Sucks a bit to return the field name in the list but vrml excepts this :/
1345                         return value.split()
1346                 else:
1347                         return None
1348
1349 def x3d_parse(path):
1350         '''
1351         Sets up the root node and returns it so load_web3d() can deal with the blender side of things.
1352         Return root (x3dNode, '') or (None, 'Error String')
1353         '''
1354         
1355         try:
1356                 import xml.dom.minidom
1357         except:
1358                 return None, 'Error, import XML parsing module (xml.dom.minidom) failed, install python'
1359         
1360         '''
1361         try:    doc = xml.dom.minidom.parse(path)
1362         except: return None, 'Could not parse this X3D file, XML error'
1363         '''
1364         
1365         # Could add a try/except here, but a console error is more useful.
1366         data = gzipOpen(path)
1367         
1368         if data==None:
1369                 return None, 'Failed to open file: ' + path
1370         
1371         doc = xml.dom.minidom.parseString(data)
1372         
1373         
1374         try:
1375                 x3dnode = doc.getElementsByTagName('X3D')[0]
1376         except:
1377                 return None, 'Not a valid x3d document, cannot import'
1378         
1379         root = x3dNode(None, NODE_NORMAL, x3dnode)
1380         root.setRoot(path) # so images and Inline's we load have a relative path
1381         root.parse()
1382         
1383         return root, ''
1384
1385
1386
1387 ## f = open('/_Cylinder.wrl', 'r')
1388 # f = open('/fe/wrl/Vrml/EGS/TOUCHSN.WRL', 'r')
1389 # vrml_parse('/fe/wrl/Vrml/EGS/TOUCHSN.WRL')
1390 #vrml_parse('/fe/wrl/Vrml/EGS/SCRIPT.WRL')
1391 '''
1392
1393 import os
1394 files = os.popen('find /fe/wrl -iname "*.wrl"').readlines()
1395 files.sort()
1396 tot = len(files)
1397 for i, f in enumerate(files):
1398         #if i < 801:
1399         #       continue
1400         
1401         f = f.strip()
1402         print f, i, tot
1403         vrml_parse(f)
1404 '''
1405
1406 # NO BLENDER CODE ABOVE THIS LINE.
1407 # -----------------------------------------------------------------------------------
1408 import bpy
1409 import BPyImage
1410 import BPySys
1411 reload(BPySys)
1412 reload(BPyImage)
1413 import Blender
1414 from Blender import Texture, Material, Mathutils, Mesh, Types, Window
1415 from Blender.Mathutils import TranslationMatrix
1416 from Blender.Mathutils import RotationMatrix
1417 from Blender.Mathutils import Vector
1418 from Blender.Mathutils import Matrix
1419
1420 RAD_TO_DEG = 57.29578
1421
1422 GLOBALS = {'CIRCLE_DETAIL':16}
1423
1424 def translateRotation(rot):
1425         '''     axis, angle     '''
1426         return RotationMatrix(rot[3]*RAD_TO_DEG, 4, 'r', Vector(rot[:3]))
1427
1428 def translateScale(sca):
1429         mat = Matrix() # 4x4 default
1430         mat[0][0] = sca[0]
1431         mat[1][1] = sca[1]
1432         mat[2][2] = sca[2]
1433         return mat
1434
1435 def translateTransform(node, ancestry):
1436         cent =          node.getFieldAsFloatTuple('center', None, ancestry) # (0.0, 0.0, 0.0)
1437         rot =           node.getFieldAsFloatTuple('rotation', None, ancestry) # (0.0, 0.0, 1.0, 0.0)
1438         sca =           node.getFieldAsFloatTuple('scale', None, ancestry) # (1.0, 1.0, 1.0)
1439         scaori =        node.getFieldAsFloatTuple('scaleOrientation', None, ancestry) # (0.0, 0.0, 1.0, 0.0)
1440         tx =            node.getFieldAsFloatTuple('translation', None, ancestry) # (0.0, 0.0, 0.0)
1441         
1442         if cent:
1443                 cent_mat = TranslationMatrix(Vector(cent)).resize4x4()
1444                 cent_imat = cent_mat.copy().invert()
1445         else:
1446                 cent_mat = cent_imat = None
1447         
1448         if rot:         rot_mat = translateRotation(rot)
1449         else:           rot_mat = None
1450         
1451         if sca:         sca_mat = translateScale(sca)
1452         else:           sca_mat = None
1453         
1454         if scaori:
1455                 scaori_mat = translateRotation(scaori)
1456                 scaori_imat = scaori_mat.copy().invert()
1457         else:
1458                 scaori_mat = scaori_imat = None
1459         
1460         if tx:          tx_mat = TranslationMatrix(Vector(tx)).resize4x4()
1461         else:           tx_mat = None
1462         
1463         new_mat = Matrix()
1464         
1465         mats = [tx_mat, cent_mat, rot_mat, scaori_mat, sca_mat, scaori_imat, cent_imat]
1466         for mtx in mats:
1467                 if mtx:
1468                         new_mat = mtx * new_mat
1469         
1470         return new_mat
1471
1472 def translateTexTransform(node, ancestry):
1473         cent =          node.getFieldAsFloatTuple('center', None, ancestry) # (0.0, 0.0)
1474         rot =           node.getFieldAsFloat('rotation', None, ancestry) # 0.0
1475         sca =           node.getFieldAsFloatTuple('scale', None, ancestry) # (1.0, 1.0)
1476         tx =            node.getFieldAsFloatTuple('translation', None, ancestry) # (0.0, 0.0)
1477         
1478         
1479         if cent:
1480                 # cent is at a corner by default
1481                 cent_mat = TranslationMatrix(Vector(cent).resize3D()).resize4x4()
1482                 cent_imat = cent_mat.copy().invert()
1483         else:
1484                 cent_mat = cent_imat = None
1485         
1486         if rot:         rot_mat = RotationMatrix(rot*RAD_TO_DEG, 4, 'z') # translateRotation(rot)
1487         else:           rot_mat = None
1488         
1489         if sca:         sca_mat = translateScale((sca[0], sca[1], 0.0))
1490         else:           sca_mat = None
1491         
1492         if tx:          tx_mat = TranslationMatrix(Vector(tx).resize3D()).resize4x4()
1493         else:           tx_mat = None
1494         
1495         new_mat = Matrix()
1496         
1497         # as specified in VRML97 docs
1498         mats = [cent_imat, sca_mat, rot_mat, cent_mat, tx_mat]
1499
1500         for mtx in mats:
1501                 if mtx:
1502                         new_mat = mtx * new_mat
1503         
1504         return new_mat
1505
1506
1507
1508 def getFinalMatrix(node, mtx, ancestry):
1509         
1510         transform_nodes = [node_tx for node_tx in ancestry if node_tx.getSpec() == 'Transform']
1511         if node.getSpec()=='Transform':
1512                 transform_nodes.append(node)
1513         transform_nodes.reverse()
1514         
1515         if mtx==None:
1516                 mtx = Matrix()
1517         
1518         for node_tx in transform_nodes:
1519                 mat = translateTransform(node_tx, ancestry)
1520                 mtx = mtx * mat
1521         
1522         return mtx
1523
1524 def importMesh_IndexedFaceSet(geom, bpyima, ancestry):
1525         # print geom.lineno, geom.id, vrmlNode.DEF_NAMESPACE.keys()
1526         
1527         ccw =                           geom.getFieldAsBool('ccw', True, ancestry)
1528         ifs_colorPerVertex =    geom.getFieldAsBool('colorPerVertex', True, ancestry) # per vertex or per face
1529         ifs_normalPerVertex =   geom.getFieldAsBool('normalPerVertex', True, ancestry)
1530         
1531         # This is odd how point is inside Coordinate
1532         
1533         # VRML not x3d
1534         #coord = geom.getChildByName('coord') # 'Coordinate'
1535         
1536         coord = geom.getChildBySpec('Coordinate') # works for x3d and vrml
1537         
1538         if coord:       ifs_points = coord.getFieldAsArray('point', 3, ancestry)
1539         else:           coord = []
1540         
1541         if not coord:
1542                 print '\tWarnint: IndexedFaceSet has no points'
1543                 return None, ccw
1544         
1545         ifs_faces = geom.getFieldAsArray('coordIndex', 0, ancestry)
1546         
1547         coords_tex = None
1548         if ifs_faces: # In rare cases this causes problems - no faces but UVs???
1549                 
1550                 # WORKS - VRML ONLY
1551                 # coords_tex = geom.getChildByName('texCoord')
1552                 coords_tex = geom.getChildBySpec('TextureCoordinate')
1553                 
1554                 if coords_tex:
1555                         ifs_texpoints = coords_tex.getFieldAsArray('point', 2, ancestry)
1556                         ifs_texfaces = geom.getFieldAsArray('texCoordIndex', 0, ancestry)
1557                         
1558                         if not ifs_texpoints:
1559                                 # IF we have no coords, then dont bother
1560                                 coords_tex = None
1561                 
1562                 
1563         # WORKS - VRML ONLY
1564         # vcolor = geom.getChildByName('color')
1565         vcolor = geom.getChildBySpec('Color')
1566         vcolor_spot = None # spot color when we dont have an array of colors
1567         if vcolor:
1568                 # float to char
1569                 ifs_vcol = [(0,0,0)] # EEKADOODLE - vertex start at 1
1570                 ifs_vcol.extend([[int(c*256) for c in col] for col in vcolor.getFieldAsArray('color', 3, ancestry)])
1571                 ifs_color_index = geom.getFieldAsArray('colorIndex', 0, ancestry)
1572                 
1573                 if not ifs_vcol:
1574                         vcolor_spot = [int(c*256) for c in vcolor.getFieldAsFloatTuple('color', [], ancestry)]
1575         
1576         # Convert faces into somthing blender can use
1577         edges = []
1578         
1579         # All lists are aligned!
1580         faces = []
1581         faces_uv = [] # if ifs_texfaces is empty then the faces_uv will match faces exactly.
1582         faces_orig_index = [] # for ngons, we need to know our original index
1583         
1584         if coords_tex and ifs_texfaces:
1585                 do_uvmap = True
1586         else:
1587                 do_uvmap = False
1588         
1589         # current_face = [0] # pointer anyone
1590         
1591         def add_face(face, fuvs, orig_index):
1592                 l = len(face)
1593                 if l==3 or l==4:
1594                         faces.append(face)
1595                         # faces_orig_index.append(current_face[0])
1596                         if do_uvmap:
1597                                 faces_uv.append(fuvs)
1598                                 
1599                         faces_orig_index.append(orig_index)
1600                 elif l==2:                      edges.append(face)
1601                 elif l>4:
1602                         for i in xrange(2, len(face)):
1603                                 faces.append([face[0], face[i-1], face[i]])
1604                                 if do_uvmap:
1605                                         faces_uv.append([fuvs[0], fuvs[i-1], fuvs[i]])
1606                                 faces_orig_index.append(orig_index)
1607                 else:
1608                         # faces with 1 verts? pfft!
1609                         # still will affect index ordering
1610                         pass
1611         
1612         face = []
1613         fuvs = []
1614         orig_index = 0
1615         for i, fi in enumerate(ifs_faces):
1616                 # ifs_texfaces and ifs_faces should be aligned
1617                 if fi != -1:
1618                         # face.append(int(fi)) # in rare cases this is a float
1619                         # EEKADOODLE!!!
1620                         # Annoyance where faces that have a zero index vert get rotated. This will then mess up UVs and VColors
1621                         face.append(int(fi)+1) # in rare cases this is a float, +1 because of stupid EEKADOODLE :/
1622                         
1623                         if do_uvmap:
1624                                 if i >= len(ifs_texfaces):
1625                                         print '\tWarning: UV Texface index out of range'
1626                                         fuvs.append(ifs_texfaces[0])
1627                                 else:
1628                                         fuvs.append(ifs_texfaces[i])
1629                 else:
1630                         add_face(face, fuvs, orig_index)
1631                         face = []
1632                         if do_uvmap:
1633                                 fuvs = []
1634                         orig_index += 1
1635         
1636         add_face(face, fuvs, orig_index)
1637         del add_face # dont need this func anymore
1638         
1639         bpymesh = bpy.data.meshes.new()
1640         
1641         bpymesh.verts.extend([(0,0,0)]) # EEKADOODLE
1642         bpymesh.verts.extend(ifs_points)
1643         
1644         # print len(ifs_points), faces, edges, ngons
1645         
1646         try:
1647                 bpymesh.faces.extend(faces, smooth=True, ignoreDups=True)
1648         except KeyError:
1649                 print "one or more vert indicies out of range. corrupt file?"
1650                 #for f in faces:
1651                 #       bpymesh.faces.extend(faces, smooth=True)
1652         
1653         bpymesh.calcNormals()
1654         
1655         if len(bpymesh.faces) != len(faces):
1656                 print '\tWarning: adding faces did not work! file is invalid, not adding UVs or vcolors'
1657                 return bpymesh, ccw
1658         
1659         # Apply UVs if we have them
1660         if not do_uvmap:
1661                 faces_uv = faces # fallback, we didnt need a uvmap in the first place, fallback to the face/vert mapping.
1662         if coords_tex:
1663                 #print ifs_texpoints
1664                 # print geom
1665                 bpymesh.faceUV = True
1666                 for i,f in enumerate(bpymesh.faces):
1667                         f.image = bpyima
1668                         fuv = faces_uv[i] # uv indicies
1669                         for j,uv in enumerate(f.uv):
1670                                 # print fuv, j, len(ifs_texpoints)
1671                                 try:
1672                                         uv[:] = ifs_texpoints[fuv[j]]
1673                                 except:
1674                                         print '\tWarning: UV Index out of range'
1675                                         uv[:] = ifs_texpoints[0]
1676         
1677         elif bpyima and len(bpymesh.faces):
1678                 # Oh Bugger! - we cant really use blenders ORCO for for texture space since texspace dosnt rotate.
1679                 # we have to create VRML's coords as UVs instead.
1680                 
1681                 # VRML docs
1682                 '''
1683                 If the texCoord field is NULL, a default texture coordinate mapping is calculated using the local
1684                 coordinate system bounding box of the shape. The longest dimension of the bounding box defines the S coordinates,
1685                 and the next longest defines the T coordinates. If two or all three dimensions of the bounding box are equal,
1686                 ties shall be broken by choosing the X, Y, or Z dimension in that order of preference.
1687                 The value of the S coordinate ranges from 0 to 1, from one end of the bounding box to the other.
1688                 The T coordinate ranges between 0 and the ratio of the second greatest dimension of the bounding box to the greatest dimension.
1689                 '''
1690                 
1691                 # Note, S,T == U,V
1692                 # U gets longest, V gets second longest
1693                 xmin, ymin, zmin = ifs_points[0]
1694                 xmax, ymax, zmax = ifs_points[0]
1695                 for co in ifs_points:
1696                         x,y,z = co
1697                         if x < xmin: xmin = x
1698                         if y < ymin: ymin = y
1699                         if z < zmin: zmin = z
1700                         
1701                         if x > xmax: xmax = x
1702                         if y > ymax: ymax = y
1703                         if z > zmax: zmax = z
1704                         
1705                 xlen = xmax - xmin
1706                 ylen = ymax - ymin
1707                 zlen = zmax - zmin
1708                 
1709                 depth_min = xmin, ymin, zmin
1710                 depth_list = [xlen, ylen, zlen]
1711                 depth_sort = depth_list[:]
1712                 depth_sort.sort()
1713                 
1714                 depth_idx = [depth_list.index(val) for val in depth_sort]
1715                 
1716                 axis_u = depth_idx[-1]
1717                 axis_v = depth_idx[-2] # second longest
1718                 
1719                 # Hack, swap these !!! TODO - Why swap??? - it seems to work correctly but should not.
1720                 # axis_u,axis_v = axis_v,axis_u
1721                 
1722                 min_u = depth_min[axis_u]
1723                 min_v = depth_min[axis_v]
1724                 depth_u = depth_list[axis_u]
1725                 depth_v = depth_list[axis_v]
1726                 
1727                 depth_list[axis_u]
1728                 
1729                 if axis_u == axis_v:
1730                         # This should be safe because when 2 axies have the same length, the lower index will be used.
1731                         axis_v += 1
1732                 
1733                 bpymesh.faceUV = True
1734                 
1735                 # HACK !!! - seems to be compatible with Cosmo though.
1736                 depth_v = depth_u = max(depth_v, depth_u)
1737                 
1738                 for f in bpymesh.faces:
1739                         f.image = bpyima
1740                         fuv = f.uv
1741                         
1742                         for i,v in enumerate(f):
1743                                 co = v.co
1744                                 fuv[i][:] = (co[axis_u]-min_u) / depth_u, (co[axis_v]-min_v) / depth_v
1745         
1746         # Add vcote 
1747         if vcolor:
1748                 # print ifs_vcol
1749                 bpymesh.vertexColors = True
1750                 
1751                 for f in bpymesh.faces:
1752                         fcol = f.col
1753                         if ifs_colorPerVertex:
1754                                 fv = f.verts
1755                                 for i,c in enumerate(fcol):
1756                                         color_index = fv[i].index # color index is vert index
1757                                         if ifs_color_index:
1758                                                 try:
1759                                                         color_index = ifs_color_index[color_index]
1760                                                 except:
1761                                                         print '\tWarning: per vertex color index out of range'
1762                                                         continue
1763                                         
1764                                         if color_index < len(ifs_vcol):
1765                                                 c.r, c.g, c.b = ifs_vcol[color_index]
1766                                         else:
1767                                                 #print '\tWarning: per face color index out of range'
1768                                                 pass
1769                         else:
1770                                 if vcolor_spot: # use 1 color, when ifs_vcol is []
1771                                         for c in fcol:
1772                                                 c.r, c.g, c.b = vcolor_spot
1773                                 else:
1774                                         color_index = faces_orig_index[f.index] # color index is face index
1775                                         #print color_index, ifs_color_index
1776                                         if ifs_color_index:
1777                                                 if color_index <= len(ifs_color_index):
1778                                                         print '\tWarning: per face color index out of range'
1779                                                         color_index = 0
1780                                                 else:
1781                                                         color_index = ifs_color_index[color_index]
1782                                                 
1783                                         
1784                                         col = ifs_vcol[color_index]
1785                                         for i,c in enumerate(fcol):
1786                                                 try:
1787                                                         c.r, c.g, c.b = col
1788                                                 except:
1789                                                         pass # incase its not between 0 and 255
1790         
1791         bpymesh.verts.delete([0,]) # EEKADOODLE
1792         
1793         return bpymesh, ccw
1794
1795 def importMesh_IndexedLineSet(geom, ancestry):
1796         # VRML not x3d
1797         #coord = geom.getChildByName('coord') # 'Coordinate'
1798         coord = geom.getChildBySpec('Coordinate') # works for x3d and vrml
1799         if coord:       points = coord.getFieldAsArray('point', 3, ancestry)
1800         else:           points = []
1801         
1802         if not points:
1803                 print '\tWarning: IndexedLineSet had no points'
1804                 return None
1805         
1806         ils_lines = geom.getFieldAsArray('coordIndex', 0, ancestry)
1807         
1808         lines = []
1809         line = []
1810         
1811         for il in ils_lines:
1812                 if il==-1:
1813                         lines.append(line)
1814                         line = []
1815                 else:
1816                         line.append(int(il))
1817         lines.append(line)
1818         
1819         # vcolor = geom.getChildByName('color') # blender dosnt have per vertex color
1820         
1821         bpycurve = bpy.data.curves.new('IndexedCurve', 'Curve')
1822         bpycurve.setFlag(1)
1823         
1824         w=t=1
1825         
1826         curve_index = 0
1827         
1828         for line in lines:
1829                 if not line:
1830                         continue
1831                 co = points[line[0]]
1832                 bpycurve.appendNurb([co[0], co[1], co[2], w, t])
1833                 bpycurve[curve_index].type= 0 # Poly Line
1834                 
1835                 for il in line[1:]:
1836                         co = points[il]
1837                         bpycurve.appendPoint(curve_index, [co[0], co[1], co[2], w])
1838                 
1839                 
1840                 curve_index += 1
1841         
1842         return bpycurve
1843
1844
1845 def importMesh_PointSet(geom, ancestry):
1846         # VRML not x3d
1847         #coord = geom.getChildByName('coord') # 'Coordinate'
1848         coord = geom.getChildBySpec('Coordinate')  # works for x3d and vrml
1849         if coord:       points = coord.getFieldAsArray('point', 3, ancestry)
1850         else:           points = []
1851         
1852         # vcolor = geom.getChildByName('color') # blender dosnt have per vertex color
1853         
1854         bpymesh = bpy.data.meshes.new()
1855         bpymesh.verts.extend(points)
1856         bpymesh.calcNormals() # will just be dummy normals
1857         return bpymesh
1858
1859 GLOBALS['CIRCLE_DETAIL'] = 12
1860
1861 MATRIX_Z_TO_Y = RotationMatrix(90, 4, 'x')
1862
1863 def importMesh_Sphere(geom, ancestry):
1864         # bpymesh = bpy.data.meshes.new()
1865         diameter = geom.getFieldAsFloat('radius', 0.5, ancestry) * 2 # * 2 for the diameter
1866         bpymesh = Mesh.Primitives.UVsphere(GLOBALS['CIRCLE_DETAIL'], GLOBALS['CIRCLE_DETAIL'], diameter)  
1867         bpymesh.transform(MATRIX_Z_TO_Y)
1868         return bpymesh
1869
1870 def importMesh_Cylinder(geom, ancestry):
1871         # bpymesh = bpy.data.meshes.new()
1872         diameter = geom.getFieldAsFloat('radius', 1.0, ancestry) * 2 # * 2 for the diameter
1873         height = geom.getFieldAsFloat('height', 2, ancestry)
1874         bpymesh = Mesh.Primitives.Cylinder(GLOBALS['CIRCLE_DETAIL'], diameter, height) 
1875         bpymesh.transform(MATRIX_Z_TO_Y)
1876         
1877         # Warning - Rely in the order Blender adds verts
1878         # not nice design but wont change soon.
1879         
1880         bottom = geom.getFieldAsBool('bottom', True, ancestry)
1881         side = geom.getFieldAsBool('side', True, ancestry)
1882         top = geom.getFieldAsBool('top', True, ancestry)
1883         
1884         if not top: # last vert is top center of tri fan.
1885                 bpymesh.verts.delete([(GLOBALS['CIRCLE_DETAIL']+GLOBALS['CIRCLE_DETAIL'])+1])
1886         
1887         if not bottom: # second last vert is bottom of triangle fan
1888                 bpymesh.verts.delete([GLOBALS['CIRCLE_DETAIL']+GLOBALS['CIRCLE_DETAIL']])
1889         
1890         if not side:
1891                 # remove all quads
1892                 bpymesh.faces.delete(1, [f for f in bpymesh.faces if len(f)==4])
1893         
1894         return bpymesh
1895
1896 def importMesh_Cone(geom, ancestry):
1897         # bpymesh = bpy.data.meshes.new()
1898         diameter = geom.getFieldAsFloat('bottomRadius', 1.0, ancestry) * 2 # * 2 for the diameter
1899         height = geom.getFieldAsFloat('height', 2, ancestry)
1900         bpymesh = Mesh.Primitives.Cone(GLOBALS['CIRCLE_DETAIL'], diameter, height) 
1901         bpymesh.transform(MATRIX_Z_TO_Y)
1902         
1903         # Warning - Rely in the order Blender adds verts
1904         # not nice design but wont change soon.
1905         
1906         bottom = geom.getFieldAsBool('bottom', True, ancestry)
1907         side = geom.getFieldAsBool('side', True, ancestry)
1908         
1909         if not bottom: # last vert is on the bottom
1910                 bpymesh.verts.delete([GLOBALS['CIRCLE_DETAIL']+1])
1911         if not side: # second last vert is on the pointy bit of the cone
1912                 bpymesh.verts.delete([GLOBALS['CIRCLE_DETAIL']])
1913         
1914         return bpymesh
1915
1916 def importMesh_Box(geom, ancestry):
1917         # bpymesh = bpy.data.meshes.new()
1918         
1919         size = geom.getFieldAsFloatTuple('size', (2.0, 2.0, 2.0), ancestry)
1920         bpymesh = Mesh.Primitives.Cube(1.0) 
1921
1922         # Scale the box to the size set
1923         scale_mat = Matrix([size[0],0,0], [0, size[1], 0], [0, 0, size[2]])
1924         bpymesh.transform(scale_mat.resize4x4())
1925         
1926         return bpymesh
1927
1928 def importShape(node, ancestry):
1929         vrmlname = node.getDefName()
1930         if not vrmlname: vrmlname = 'Shape'
1931         
1932         # works 100% in vrml, but not x3d
1933         #appr = node.getChildByName('appearance') # , 'Appearance'
1934         #geom = node.getChildByName('geometry') # , 'IndexedFaceSet'
1935         
1936         # Works in vrml and x3d
1937         appr = node.getChildBySpec('Appearance')
1938         geom = node.getChildBySpec(['IndexedFaceSet', 'IndexedLineSet', 'PointSet', 'Sphere', 'Box', 'Cylinder', 'Cone'])
1939         
1940         # For now only import IndexedFaceSet's
1941         if geom:
1942                 bpymat = None
1943                 bpyima = None
1944                 texmtx = None
1945                 
1946                 depth = 0 # so we can set alpha face flag later
1947                 
1948                 if appr:
1949                         
1950                         #mat = appr.getChildByName('material') # 'Material'
1951                         #ima = appr.getChildByName('texture') # , 'ImageTexture'
1952                         #if ima and ima.getSpec() != 'ImageTexture':
1953                         #       print '\tWarning: texture type "%s" is not supported' % ima.getSpec() 
1954                         #       ima = None
1955                         # textx = appr.getChildByName('textureTransform')
1956                         
1957                         mat = appr.getChildBySpec('Material')
1958                         ima = appr.getChildBySpec('ImageTexture')
1959                         
1960                         textx = appr.getChildBySpec('TextureTransform')
1961                         
1962                         if textx:
1963                                 texmtx = translateTexTransform(textx, ancestry)
1964                         
1965
1966                         
1967                         # print mat, ima
1968                         if mat or ima:
1969                                 
1970                                 if not mat:
1971                                         mat = ima # This is a bit dumb, but just means we use default values for all
1972                                 
1973                                 # all values between 0.0 and 1.0, defaults from VRML docs
1974                                 bpymat = bpy.data.materials.new()
1975                                 bpymat.amb =            mat.getFieldAsFloat('ambientIntensity', 0.2, ancestry)
1976                                 bpymat.rgbCol =         mat.getFieldAsFloatTuple('diffuseColor', [0.8, 0.8, 0.8], ancestry)
1977                                 
1978                                 # NOTE - blender dosnt support emmisive color
1979                                 # Store in mirror color and approximate with emit.
1980                                 emit =                          mat.getFieldAsFloatTuple('emissiveColor', [0.0, 0.0, 0.0], ancestry)
1981                                 bpymat.mirCol =         emit
1982                                 bpymat.emit =           (emit[0]+emit[1]+emit[2])/3.0
1983                                 
1984                                 bpymat.hard =           int(1+(510*mat.getFieldAsFloat('shininess', 0.2, ancestry))) # 0-1 -> 1-511
1985                                 bpymat.specCol =        mat.getFieldAsFloatTuple('specularColor', [0.0, 0.0, 0.0], ancestry)
1986                                 bpymat.alpha =          1.0 - mat.getFieldAsFloat('transparency', 0.0, ancestry)
1987                                 if bpymat.alpha < 0.999:
1988                                         bpymat.mode |= Material.Modes.ZTRANSP
1989                         
1990                         
1991                         if ima:
1992                                 
1993                                 ima_url =                       ima.getFieldAsString('url', None, ancestry)
1994                                 
1995                                 if ima_url==None:
1996                                         try:            ima_url = ima.getFieldAsStringArray('url', ancestry)[0] # in some cases we get a list of images.
1997                                         except:         ima_url = None
1998                                 
1999                                 if ima_url==None:
2000                                         print "\twarning, image with no URL, this is odd"
2001                                 else:
2002                                         bpyima= BPyImage.comprehensiveImageLoad(ima_url, dirName(node.getFilename()), PLACE_HOLDER= False, RECURSIVE= False, CONVERT_CALLBACK= imageConvertCompat)
2003                                         if bpyima:
2004                                                 texture= bpy.data.textures.new()
2005                                                 texture.setType('Image')
2006                                                 texture.image = bpyima
2007                                                 
2008                                                 # Adds textures for materials (rendering)
2009                                                 try:    depth = bpyima.depth
2010                                                 except: depth = -1
2011                                                 
2012                                                 if depth == 32:
2013                                                         # Image has alpha
2014                                                         bpymat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL | Texture.MapTo.ALPHA)
2015                                                         texture.setImageFlags('MipMap', 'InterPol', 'UseAlpha')
2016                                                         bpymat.mode |= Material.Modes.ZTRANSP
2017                                                         bpymat.alpha = 0.0
2018                                                 else:
2019                                                         bpymat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL)
2020                                                         
2021                                                 ima_repS =                      ima.getFieldAsBool('repeatS', True, ancestry)
2022                                                 ima_repT =                      ima.getFieldAsBool('repeatT', True, ancestry)
2023                                                 
2024                                                 # To make this work properly we'd need to scale the UV's too, better to ignore th
2025                                                 # texture.repeat =      max(1, ima_repS * 512), max(1, ima_repT * 512)
2026                                                 
2027                                                 if not ima_repS: bpyima.clampX = True
2028                                                 if not ima_repT: bpyima.clampY = True
2029                 
2030                 bpydata = None
2031                 geom_spec = geom.getSpec()
2032                 ccw = True
2033                 if geom_spec == 'IndexedFaceSet':
2034                         bpydata, ccw = importMesh_IndexedFaceSet(geom, bpyima, ancestry)
2035                 elif geom_spec == 'IndexedLineSet':
2036                         bpydata = importMesh_IndexedLineSet(geom, ancestry)
2037                 elif geom_spec == 'PointSet':
2038                         bpydata = importMesh_PointSet(geom, ancestry)
2039                 elif geom_spec == 'Sphere':
2040                         bpydata = importMesh_Sphere(geom, ancestry)
2041                 elif geom_spec == 'Box':
2042                         bpydata = importMesh_Box(geom, ancestry)
2043                 elif geom_spec == 'Cylinder':
2044                         bpydata = importMesh_Cylinder(geom, ancestry)
2045                 elif geom_spec == 'Cone':
2046                         bpydata = importMesh_Cone(geom, ancestry)
2047                 else:
2048                         print '\tWarning: unsupported type "%s"' % geom_spec
2049                         return
2050                 
2051                 if bpydata:
2052                         vrmlname = vrmlname + geom_spec
2053                         
2054                         bpydata.name = vrmlname
2055                         
2056                         bpyob  = node.blendObject = bpy.data.scenes.active.objects.new(bpydata)
2057                         
2058                         if type(bpydata) == Types.MeshType:
2059                                 is_solid =                      geom.getFieldAsBool('solid', True, ancestry)
2060                                 creaseAngle =           geom.getFieldAsFloat('creaseAngle', None, ancestry)
2061                                 
2062                                 if creaseAngle != None:
2063                                         bpydata.maxSmoothAngle = 1+int(min(79, creaseAngle * RAD_TO_DEG))
2064                                         bpydata.mode |= Mesh.Modes.AUTOSMOOTH
2065                                 
2066                                 # Only ever 1 material per shape
2067                                 if bpymat:      bpydata.materials = [bpymat]
2068                                 
2069                                 if bpydata.faceUV:
2070                                         
2071                                         if depth==32: # set the faces alpha flag?
2072                                                 transp = Mesh.FaceTranspModes.ALPHA
2073                                                 for f in bpydata.faces:
2074                                                         f.transp = transp
2075                                 
2076                                         if texmtx:
2077                                                 # Apply texture transform?
2078                                                 uv_copy = Vector()
2079                                                 for f in bpydata.faces:
2080                                                         for uv in f.uv:
2081                                                                 uv_copy.x = uv.x
2082                                                                 uv_copy.y = uv.y
2083                                                                 
2084                                                                 uv.x, uv.y = (uv_copy * texmtx)[0:2]
2085                                 # Done transforming the texture
2086                                 
2087                                 
2088                                 # Must be here and not in IndexedFaceSet because it needs an object for the flip func. Messy :/
2089                                 if not ccw: bpydata.flipNormals()
2090                                 
2091                                 
2092                         # else could be a curve for example
2093                         
2094                         
2095                         
2096                         # Can transform data or object, better the object so we can instance the data
2097                         #bpymesh.transform(getFinalMatrix(node))
2098                         bpyob.setMatrix( getFinalMatrix(node, None, ancestry) )
2099
2100
2101 def importLamp_PointLight(node, ancestry):
2102         vrmlname = node.getDefName()
2103         if not vrmlname: vrmlname = 'PointLight'
2104         
2105         # ambientIntensity = node.getFieldAsFloat('ambientIntensity', 0.0, ancestry) # TODO
2106         # attenuation = node.getFieldAsFloatTuple('attenuation', (1.0, 0.0, 0.0), ancestry) # TODO
2107         color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry)
2108         intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher.
2109         location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry)
2110         # is_on = node.getFieldAsBool('on', True, ancestry) # TODO
2111         radius = node.getFieldAsFloat('radius', 100.0, ancestry)
2112         
2113         bpylamp = bpy.data.lamps.new()
2114         bpylamp.setType('Lamp')
2115         bpylamp.energy = intensity
2116         bpylamp.dist = radius
2117         bpylamp.col = color
2118         
2119         mtx = TranslationMatrix(Vector(location))
2120         
2121         return bpylamp, mtx
2122
2123 def importLamp_DirectionalLight(node, ancestry):
2124         vrmlname = node.getDefName()
2125         if not vrmlname: vrmlname = 'DirectLight'
2126         
2127         # ambientIntensity = node.getFieldAsFloat('ambientIntensity', 0.0) # TODO
2128         color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry)
2129         direction = node.getFieldAsFloatTuple('direction', (0.0, 0.0, -1.0), ancestry)
2130         intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher.
2131         # is_on = node.getFieldAsBool('on', True, ancestry) # TODO
2132         
2133         bpylamp = bpy.data.lamps.new(vrmlname)
2134         bpylamp.setType('Sun')
2135         bpylamp.energy = intensity
2136         bpylamp.col = color
2137         
2138         # lamps have their direction as -z, yup
2139         mtx = Vector(direction).toTrackQuat('-z', 'y').toMatrix().resize4x4()
2140         
2141         return bpylamp, mtx
2142
2143 # looks like default values for beamWidth and cutOffAngle were swapped in VRML docs.
2144
2145 def importLamp_SpotLight(node, ancestry):
2146         vrmlname = node.getDefName()
2147         if not vrmlname: vrmlname = 'SpotLight'
2148         
2149         # ambientIntensity = geom.getFieldAsFloat('ambientIntensity', 0.0, ancestry) # TODO
2150         # attenuation = geom.getFieldAsFloatTuple('attenuation', (1.0, 0.0, 0.0), ancestry) # TODO
2151         beamWidth = node.getFieldAsFloat('beamWidth', 1.570796, ancestry) * RAD_TO_DEG # max is documented to be 1.0 but some files have higher.
2152         color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry)
2153         cutOffAngle = node.getFieldAsFloat('cutOffAngle', 0.785398, ancestry) * RAD_TO_DEG # max is documented to be 1.0 but some files have higher.
2154         direction = node.getFieldAsFloatTuple('direction', (0.0, 0.0, -1.0), ancestry)
2155         intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher.
2156         location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry)
2157         # is_on = node.getFieldAsBool('on', True, ancestry) # TODO
2158         radius = node.getFieldAsFloat('radius', 100.0, ancestry)
2159         
2160         bpylamp = bpy.data.lamps.new(vrmlname)
2161         bpylamp.setType('Spot')
2162         bpylamp.energy = intensity
2163         bpylamp.dist = radius
2164         bpylamp.col = color
2165         bpylamp.spotSize = cutOffAngle
2166         if beamWidth > cutOffAngle:
2167                 bpylamp.spotBlend = 0.0
2168         else:
2169                 if cutOffAngle==0.0: #@#$%^&*(!!! - this should never happen
2170                         bpylamp.spotBlend = 0.5
2171                 else:
2172                         bpylamp.spotBlend = beamWidth / cutOffAngle
2173         
2174         # Convert 
2175         
2176         # lamps have their direction as -z, y==up
2177         mtx = Vector(direction).toTrackQuat('-z', 'y').toMatrix().resize4x4() * TranslationMatrix(Vector(location))
2178         
2179         return bpylamp, mtx
2180
2181
2182 def importLamp(node, spec, ancestry):
2183         if spec=='PointLight':
2184                 bpylamp,mtx = importLamp_PointLight(node, ancestry)
2185         elif spec=='DirectionalLight':
2186                 bpylamp,mtx = importLamp_DirectionalLight(node, ancestry)
2187         elif spec=='SpotLight':
2188                 bpylamp,mtx = importLamp_SpotLight(node, ancestry)
2189         else:
2190                 print "Error, not a lamp"
2191                 raise ValueError
2192         
2193         bpyob = node.blendObject = bpy.data.scenes.active.objects.new(bpylamp)
2194         bpyob.setMatrix( getFinalMatrix(node, mtx, ancestry) )
2195
2196
2197 def importViewpoint(node, ancestry):
2198         name = node.getDefName()
2199         if not name: name = 'Viewpoint'
2200         
2201         fieldOfView = node.getFieldAsFloat('fieldOfView', 0.785398, ancestry) * RAD_TO_DEG # max is documented to be 1.0 but some files have higher.
2202         # jump = node.getFieldAsBool('jump', True, ancestry)
2203         orientation = node.getFieldAsFloatTuple('orientation', (0.0, 0.0, 1.0, 0.0), ancestry)
2204         position = node.getFieldAsFloatTuple('position', (0.0, 0.0, 0.0), ancestry)
2205         description = node.getFieldAsString('description', '', ancestry)
2206         
2207         bpycam = bpy.data.cameras.new(name)
2208         
2209         bpycam.angle = fieldOfView
2210         
2211         mtx = translateRotation(orientation) * TranslationMatrix(Vector(position))
2212         
2213         
2214         bpyob = node.blendObject = bpy.data.scenes.active.objects.new(bpycam)
2215         bpyob.setMatrix( getFinalMatrix(node, mtx, ancestry) )
2216
2217
2218 def importTransform(node, ancestry):
2219         name = node.getDefName()
2220         if not name: name = 'Transform'
2221         
2222         bpyob = node.blendObject = bpy.data.scenes.active.objects.new('Empty', name) # , name)
2223         bpyob.setMatrix( getFinalMatrix(node, None, ancestry) )
2224
2225         # so they are not too annoying
2226         bpyob.emptyShape= Blender.Object.EmptyShapes.AXES
2227         bpyob.drawSize= 0.2
2228
2229         
2230 #def importTimeSensor(node):
2231
2232
2233 def translatePositionInterpolator(node, ipo, ancestry):
2234         key = node.getFieldAsArray('key', 0, ancestry)
2235         keyValue = node.getFieldAsArray('keyValue', 3, ancestry)
2236         
2237         try:
2238                 loc_x = ipo.addCurve('LocX')
2239                 loc_y = ipo.addCurve('LocY')
2240                 loc_z = ipo.addCurve('LocZ')
2241         except ValueError:
2242                 return
2243                 
2244         loc_x.interpolation = loc_y.interpolation = loc_z.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
2245         
2246         for i, time in enumerate(key):
2247                 try:            x,y,z = keyValue[i]
2248                 except: continue
2249                 
2250                 loc_x.append((time,x))
2251                 loc_y.append((time,y))
2252                 loc_z.append((time,z))
2253
2254 def translateOrientationInterpolator(node, ipo, ancestry):
2255         key = node.getFieldAsArray('key', 0, ancestry)
2256         keyValue = node.getFieldAsArray('keyValue', 4, ancestry)
2257         
2258         try:
2259                 rot_x = ipo.addCurve('RotX')
2260                 rot_y = ipo.addCurve('RotY')
2261                 rot_z = ipo.addCurve('RotZ')
2262         except ValueError:
2263                 return
2264         
2265         rot_x.interpolation = rot_y.interpolation = rot_z.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
2266         
2267         for i, time in enumerate(key):
2268                 try:            x,y,z,w = keyValue[i]
2269                 except: continue
2270                 
2271                 mtx = translateRotation((x,y,z,w))
2272                 eul = mtx.toEuler()
2273                 rot_x.append((time,eul.x/10.0))
2274                 rot_y.append((time,eul.y/10.0))
2275                 rot_z.append((time,eul.z/10.0))
2276
2277 # Untested!
2278 def translateScalarInterpolator(node, ipo, ancestry):
2279         key = node.getFieldAsArray('key', 0, ancestry)
2280         keyValue = node.getFieldAsArray('keyValue', 4, ancestry)
2281         
2282         try:
2283                 sca_x = ipo.addCurve('ScaleX')
2284                 sca_y = ipo.addCurve('ScaleY')
2285                 sca_z = ipo.addCurve('ScaleZ')
2286         except ValueError:
2287                 return
2288         
2289         sca_x.interpolation = sca_y.interpolation = sca_z.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
2290         
2291         for i, time in enumerate(key):
2292                 try:            x,y,z = keyValue[i]
2293                 except: continue
2294                 sca_x.append((time,x/10.0))
2295                 sca_y.append((time,y/10.0))
2296                 sca_z.append((time,z/10.0))
2297
2298 def translateTimeSensor(node, ipo, ancestry):
2299         '''
2300         Apply a time sensor to an IPO, VRML has many combinations of loop/start/stop/cycle times
2301         to give different results, for now just do the basics
2302         '''
2303         
2304         time_cu = ipo.addCurve('Time')
2305         time_cu.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
2306         
2307         cycleInterval = node.getFieldAsFloat('cycleInterval', None, ancestry)
2308         
2309         startTime = node.getFieldAsFloat('startTime', 0.0, ancestry)
2310         stopTime = node.getFieldAsFloat('stopTime', 250.0, ancestry)
2311                 
2312         if cycleInterval != None:
2313                 stopTime = startTime+cycleInterval
2314         
2315         loop = node.getFieldAsBool('loop', False, ancestry)
2316         
2317         time_cu.append((1+startTime, 0.0))
2318         time_cu.append((1+stopTime, 1.0/10.0))# anoying, the UI uses /10
2319         
2320         
2321         if loop:
2322                 time_cu.extend = Blender.IpoCurve.ExtendTypes.CYCLIC # or - EXTRAP, CYCLIC_EXTRAP, CONST, 
2323
2324
2325 def importRoute(node, ancestry):
2326         '''
2327         Animation route only at the moment
2328         '''
2329         
2330         if not hasattr(node, 'fields'):
2331                 return
2332         
2333         routeIpoDict = node.getRouteIpoDict()
2334         
2335         def getIpo(id):
2336                 try: ipo =      routeIpoDict[id]
2337                 except: ipo = routeIpoDict[id] = bpy.data.ipos.new('web3d_ipo', 'Object')
2338                 return ipo
2339         
2340         # for getting definitions
2341         defDict = node.getDefDict()
2342         '''
2343         Handles routing nodes to eachother
2344         
2345 ROUTE vpPI.value_changed TO champFly001.set_position
2346 ROUTE vpOI.value_changed TO champFly001.set_orientation
2347 ROUTE vpTs.fraction_changed TO vpPI.set_fraction
2348 ROUTE vpTs.fraction_changed TO vpOI.set_fraction
2349 ROUTE champFly001.bindTime TO vpTs.set_startTime
2350         '''
2351         
2352         #from_id, from_type = node.id[1].split('.')
2353         #to_id, to_type = node.id[3].split('.')
2354         
2355         #value_changed
2356         set_position_node = None
2357         set_orientation_node = None
2358         time_node = None
2359         
2360         for field in node.fields:
2361                 if field and field[0]=='ROUTE':
2362                         try:
2363                                 from_id, from_type = field[1].split('.')
2364                                 to_id, to_type = field[3].split('.')
2365                         except:
2366                                 print "Warning, invalid ROUTE", field
2367                                 continue
2368                         
2369                         if from_type == 'value_changed':
2370                                 if to_type == 'set_position':
2371                                         ipo = getIpo(to_id)
2372                                         set_data_from_node = defDict[from_id]
2373                                         translatePositionInterpolator(set_data_from_node, ipo, ancestry)
2374                                 
2375                                 if to_type in ('set_orientation', 'rotation'):
2376                                         ipo = getIpo(to_id)
2377                                         set_data_from_node = defDict[from_id]
2378                                         translateOrientationInterpolator(set_data_from_node, ipo, ancestry)
2379                                 
2380                                 if to_type == 'set_scale':
2381                                         ipo = getIpo(to_id)
2382                                         set_data_from_node = defDict[from_id]
2383                                         translateScalarInterpolator(set_data_from_node, ipo, ancestry)
2384                                 
2385                         elif from_type =='bindTime':
2386                                 ipo = getIpo(from_id)
2387                                 time_node = defDict[to_id]
2388                                 translateTimeSensor(time_node, ipo, ancestry)
2389                         
2390                 
2391
2392
2393 def load_web3d(path, PREF_FLAT=False, PREF_CIRCLE_DIV=16, HELPER_FUNC = None):
2394         
2395         # Used when adding blender primitives
2396         GLOBALS['CIRCLE_DETAIL'] = PREF_CIRCLE_DIV
2397         
2398         #root_node = vrml_parse('/_Cylinder.wrl')
2399         if path.lower().endswith('.x3d'):
2400                 root_node, msg = x3d_parse(path)
2401         else:
2402                 root_node, msg = vrml_parse(path)
2403         
2404         if not root_node:
2405                 if Blender.mode == 'background':
2406                         print msg
2407                 else:
2408                         Blender.Draw.PupMenu(msg)
2409                 return
2410         
2411         
2412         # fill with tuples - (node, [parents-parent, parent])
2413         all_nodes = root_node.getSerialized([], [])
2414         
2415         for node, ancestry in all_nodes:
2416                 #if 'castle.wrl' not in node.getFilename():
2417                 #       continue
2418                 
2419                 spec = node.getSpec()
2420                 '''
2421                 prefix = node.getPrefix()
2422                 if prefix=='PROTO':
2423                         pass
2424                 else
2425                 '''
2426                 if HELPER_FUNC and HELPER_FUNC(node, ancestry):
2427                         # Note, include this function so the VRML/X3D importer can be extended
2428                         # by an external script. - gets first pick 
2429                         pass
2430                 if spec=='Shape':
2431                         importShape(node, ancestry)
2432                 elif spec in ('PointLight', 'DirectionalLight', 'SpotLight'):
2433                         importLamp(node, spec, ancestry)
2434                 elif spec=='Viewpoint':
2435                         importViewpoint(node, ancestry)
2436                 elif spec=='Transform':
2437                         # Only use transform nodes when we are not importing a flat object hierarchy
2438                         if PREF_FLAT==False:
2439                                 importTransform(node, ancestry)
2440                         '''
2441                 # These are delt with later within importRoute
2442                 elif spec=='PositionInterpolator':
2443                         ipo = bpy.data.ipos.new('web3d_ipo', 'Object')
2444                         translatePositionInterpolator(node, ipo)
2445                         '''
2446         
2447         
2448         
2449         # After we import all nodes, route events - anim paths
2450         for node, ancestry in all_nodes:
2451                 importRoute(node, ancestry)
2452         
2453         for node, ancestry in all_nodes:
2454                 if node.isRoot():
2455                         # we know that all nodes referenced from will be in 
2456                         # routeIpoDict so no need to run node.getDefDict() for every node.
2457                         routeIpoDict = node.getRouteIpoDict()
2458                         defDict = node.getDefDict()
2459                         
2460                         for key, ipo in routeIpoDict.iteritems():
2461                                 
2462                                 # Assign anim curves
2463                                 node = defDict[key]
2464                                 if node.blendObject==None: # Add an object if we need one for animation
2465                                         node.blendObject = bpy.data.scenes.active.objects.new('Empty', 'AnimOb') # , name)
2466                                         
2467                                 node.blendObject.setIpo(ipo)
2468
2469         
2470         
2471         # Add in hierarchy
2472         if PREF_FLAT==False:
2473                 child_dict = {}
2474                 for node, ancestry in all_nodes:
2475                         if node.blendObject:
2476                                 blendObject = None
2477                                 
2478                                 # Get the last parent
2479                                 i = len(ancestry)
2480                                 while i:
2481                                         i-=1
2482                                         blendObject = ancestry[i].blendObject
2483                                         if blendObject:
2484                                                 break
2485                                 
2486                                 if blendObject:
2487                                         # Parent Slow, - 1 liner but works
2488                                         # blendObject.makeParent([node.blendObject], 0, 1)
2489                                         
2490                                         # Parent FAST
2491                                         try:    child_dict[blendObject].append(node.blendObject)
2492                                         except: child_dict[blendObject] = [node.blendObject]
2493                 
2494                 # Parent FAST
2495                 for parent, children in child_dict.iteritems():
2496                         parent.makeParent(children, 0, 1)
2497                 
2498                 # update deps
2499                 bpy.data.scenes.active.update(1)
2500                 del child_dict
2501
2502
2503 def load_ui(path):
2504         Draw = Blender.Draw
2505         PREF_HIERARCHY= Draw.Create(0)
2506         PREF_CIRCLE_DIV= Draw.Create(16)
2507         
2508         # Get USER Options
2509         pup_block= [\
2510         'Import...',\
2511         ('Hierarchy', PREF_HIERARCHY, 'Import transform nodes as empties to create a parent/child hierarchy'),\
2512         ('Circle Div:', PREF_CIRCLE_DIV, 3, 128, 'Number of divisions to use for circular primitives')
2513         ]
2514         
2515         if not Draw.PupBlock('Import X3D/VRML...', pup_block):
2516                 return
2517         
2518         Window.WaitCursor(1)
2519         
2520         load_web3d(path,\
2521           (not PREF_HIERARCHY.val),\
2522           PREF_CIRCLE_DIV.val,\
2523         )
2524         
2525         Window.WaitCursor(0)
2526         
2527
2528 if __name__ == '__main__':
2529         Window.FileSelector(load_ui, 'Import X3D/VRML97')
2530         
2531
2532 # Testing stuff
2533
2534 # load_web3d('/test.x3d')
2535 # load_web3d('/_Cylinder.x3d')
2536
2537 # Testing below
2538 # load_web3d('m:\\root\\Desktop\\_Cylinder.wrl')
2539 # load_web3d('/_Cylinder.wrl')
2540 # load_web3d('/fe/wrl/Vrml/EGS/BCKGD.WRL')
2541
2542 # load_web3d('/fe/wrl/Vrml/EGS/GRNDPLNE.WRL')
2543 # load_web3d('/fe/wrl/Vrml/EGS/INDEXFST.WRL')
2544 # load_web3d('/fe/wrl/panel1c.wrl')
2545 # load_web3d('/test.wrl')
2546 # load_web3d('/fe/wrl/dulcimer.wrl')
2547 # load_web3d('/fe/wrl/rccad/Ju-52.wrl') # Face index out of range
2548 # load_web3d('/fe/wrl/16lat.wrl') # spotlight
2549 # load_web3d('/fe/wrl/Vrml/EGS/FOG.WRL') # spotlight
2550 # load_web3d('/fe/wrl/Vrml/EGS/LOD.WRL') # vcolor per face
2551
2552 # load_web3d('/fe/wrl/new/daybreak_final.wrl') # no faces in mesh, face duplicate error
2553 # load_web3d('/fe/wrl/new/earth.wrl')
2554 # load_web3d('/fe/wrl/new/hendrix.ei.dtu.dk/vrml/talairach/fourd/TalaDruryRight.wrl') # define/use fields
2555 # load_web3d('/fe/wrl/new/imac.wrl') # extrusion and define/use fields, face index is a float somehow
2556 # load_web3d('/fe/wrl/new/www.igs.net/~mascott/vrml/vrml2/mcastle.wrl') 
2557 # load_web3d('/fe/wrl/new/www.igs.net/~mascott/vrml/vrml2/tower.wrl') 
2558 # load_web3d('/fe/wrl/new/www.igs.net/~mascott/vrml/vrml2/temple.wrl') 
2559 # load_web3d('/fe/wrl/brain.wrl')  # field define test 'a IS b'
2560 # load_web3d('/fe/wrl/new/coaster.wrl')  # fields that are confusing to read.
2561
2562 # X3D 
2563
2564 # load_web3d('/fe/x3d/www.web3d.org/x3d/content/examples/Basic/StudentProjects/PlayRoom.x3d') # invalid UVs
2565
2566
2567
2568 def test():
2569         import os
2570         
2571         files = os.popen('find /fe/wrl -iname "*.wrl"').readlines()
2572         # files = os.popen('find /fe/x3d -iname "*.x3d"').readlines()
2573         # files = os.popen('find   /fe/x3d/X3dExamplesSavage   -iname "*.x3d"').readlines()
2574
2575         files.sort()
2576         tot = len(files)
2577         for i, f in enumerate(files):
2578                 if i < 124 or i > 1000000:
2579                         continue
2580                 
2581                 #if i != 1068:
2582                 #       continue
2583                 
2584                 #if i != 12686:
2585                 #       continue
2586                 
2587                 f = f.strip()
2588                 print f, i, tot
2589                 sce = bpy.data.scenes.new(str(i) + '_' + f.split('/')[-1])
2590                 bpy.data.scenes.active = sce
2591                 # Window.
2592                 load_web3d(f, PREF_FLAT=True)
2593         
2594 # test()