2.50: svn merge https://svn.blender.org/svnroot/bf-blender/trunk/blender -r17853...
[blender-staging.git] / release / scripts / import_web3d.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 pradict.
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):
1336                 self_real = self.getRealNode() # incase we're an instance
1337                 field_xml = self.x3dNode.getAttributeNode(field)
1338                 if field_xml:
1339                         value = field_xml.value
1340                         
1341                         # We may want to edit. for x3d spesific stuff
1342                         # Sucks a bit to return the field name in the list but vrml excepts this :/
1343                         return value.split()
1344                 else:
1345                         return None
1346
1347 def x3d_parse(path):
1348         '''
1349         Sets up the root node and returns it so load_web3d() can deal with the blender side of things.
1350         Return root (x3dNode, '') or (None, 'Error String')
1351         '''
1352         
1353         try:
1354                 import xml.dom.minidom
1355         except:
1356                 return None, 'Error, import XML parsing module (xml.dom.minidom) failed, install python'
1357         
1358         '''
1359         try:    doc = xml.dom.minidom.parse(path)
1360         except: return None, 'Could not parse this X3D file, XML error'
1361         '''
1362         
1363         # Could add a try/except here, but a console error is more useful.
1364         data = gzipOpen(path)
1365         
1366         if data==None:
1367                 return None, 'Failed to open file: ' + path
1368         
1369         doc = xml.dom.minidom.parseString(data)
1370         
1371         
1372         try:
1373                 x3dnode = doc.getElementsByTagName('X3D')[0]
1374         except:
1375                 return None, 'Not a valid x3d document, cannot import'
1376         
1377         root = x3dNode(None, NODE_NORMAL, x3dnode)
1378         root.setRoot(path) # so images and Inline's we load have a relative path
1379         root.parse()
1380         
1381         return root, ''
1382
1383
1384
1385 ## f = open('/_Cylinder.wrl', 'r')
1386 # f = open('/fe/wrl/Vrml/EGS/TOUCHSN.WRL', 'r')
1387 # vrml_parse('/fe/wrl/Vrml/EGS/TOUCHSN.WRL')
1388 #vrml_parse('/fe/wrl/Vrml/EGS/SCRIPT.WRL')
1389 '''
1390
1391 import os
1392 files = os.popen('find /fe/wrl -iname "*.wrl"').readlines()
1393 files.sort()
1394 tot = len(files)
1395 for i, f in enumerate(files):
1396         #if i < 801:
1397         #       continue
1398         
1399         f = f.strip()
1400         print f, i, tot
1401         vrml_parse(f)
1402 '''
1403
1404 # NO BLENDER CODE ABOVE THIS LINE.
1405 # -----------------------------------------------------------------------------------
1406 import bpy
1407 import BPyImage
1408 import BPySys
1409 reload(BPySys)
1410 reload(BPyImage)
1411 import Blender
1412 from Blender import Texture, Material, Mathutils, Mesh, Types, Window
1413 from Blender.Mathutils import TranslationMatrix
1414 from Blender.Mathutils import RotationMatrix
1415 from Blender.Mathutils import Vector
1416 from Blender.Mathutils import Matrix
1417
1418 RAD_TO_DEG = 57.29578
1419
1420 GLOBALS = {'CIRCLE_DETAIL':16}
1421
1422 def translateRotation(rot):
1423         '''     axis, angle     '''
1424         return RotationMatrix(rot[3]*RAD_TO_DEG, 4, 'r', Vector(rot[:3]))
1425
1426 def translateScale(sca):
1427         mat = Matrix() # 4x4 default
1428         mat[0][0] = sca[0]
1429         mat[1][1] = sca[1]
1430         mat[2][2] = sca[2]
1431         return mat
1432
1433 def translateTransform(node, ancestry):
1434         cent =          node.getFieldAsFloatTuple('center', None, ancestry) # (0.0, 0.0, 0.0)
1435         rot =           node.getFieldAsFloatTuple('rotation', None, ancestry) # (0.0, 0.0, 1.0, 0.0)
1436         sca =           node.getFieldAsFloatTuple('scale', None, ancestry) # (1.0, 1.0, 1.0)
1437         scaori =        node.getFieldAsFloatTuple('scaleOrientation', None, ancestry) # (0.0, 0.0, 1.0, 0.0)
1438         tx =            node.getFieldAsFloatTuple('translation', None, ancestry) # (0.0, 0.0, 0.0)
1439         
1440         if cent:
1441                 cent_mat = TranslationMatrix(Vector(cent)).resize4x4()
1442                 cent_imat = cent_mat.copy().invert()
1443         else:
1444                 cent_mat = cent_imat = None
1445         
1446         if rot:         rot_mat = translateRotation(rot)
1447         else:           rot_mat = None
1448         
1449         if sca:         sca_mat = translateScale(sca)
1450         else:           sca_mat = None
1451         
1452         if scaori:
1453                 scaori_mat = translateRotation(scaori)
1454                 scaori_imat = scaori_mat.copy().invert()
1455         else:
1456                 scaori_mat = scaori_imat = None
1457         
1458         if tx:          tx_mat = TranslationMatrix(Vector(tx)).resize4x4()
1459         else:           tx_mat = None
1460         
1461         new_mat = Matrix()
1462         
1463         mats = [tx_mat, cent_mat, rot_mat, scaori_mat, sca_mat, scaori_imat, cent_imat]
1464         for mtx in mats:
1465                 if mtx:
1466                         new_mat = mtx * new_mat
1467         
1468         return new_mat
1469
1470 def translateTexTransform(node, ancestry):
1471         cent =          node.getFieldAsFloatTuple('center', None, ancestry) # (0.0, 0.0)
1472         rot =           node.getFieldAsFloat('rotation', None, ancestry) # 0.0
1473         sca =           node.getFieldAsFloatTuple('scale', None, ancestry) # (1.0, 1.0)
1474         tx =            node.getFieldAsFloatTuple('translation', None, ancestry) # (0.0, 0.0)
1475         
1476         
1477         if cent:
1478                 # cent is at a corner by default
1479                 cent_mat = TranslationMatrix(Vector(cent).resize3D()).resize4x4()
1480                 cent_imat = cent_mat.copy().invert()
1481         else:
1482                 cent_mat = cent_imat = None
1483         
1484         if rot:         rot_mat = RotationMatrix(rot*RAD_TO_DEG, 4, 'z') # translateRotation(rot)
1485         else:           rot_mat = None
1486         
1487         if sca:         sca_mat = translateScale((sca[0], sca[1], 0.0))
1488         else:           sca_mat = None
1489         
1490         if tx:          tx_mat = TranslationMatrix(Vector(tx).resize3D()).resize4x4()
1491         else:           tx_mat = None
1492         
1493         new_mat = Matrix()
1494         
1495         # as specified in VRML97 docs
1496         mats = [cent_imat, sca_mat, rot_mat, cent_mat, tx_mat]
1497
1498         for mtx in mats:
1499                 if mtx:
1500                         new_mat = mtx * new_mat
1501         
1502         return new_mat
1503
1504
1505
1506 def getFinalMatrix(node, mtx, ancestry):
1507         
1508         transform_nodes = [node_tx for node_tx in ancestry if node_tx.getSpec() == 'Transform']
1509         if node.getSpec()=='Transform':
1510                 transform_nodes.append(node)
1511         transform_nodes.reverse()
1512         
1513         if mtx==None:
1514                 mtx = Matrix()
1515         
1516         for node_tx in transform_nodes:
1517                 mat = translateTransform(node_tx, ancestry)
1518                 mtx = mtx * mat
1519         
1520         return mtx
1521
1522 def importMesh_IndexedFaceSet(geom, bpyima, ancestry):
1523         # print geom.lineno, geom.id, vrmlNode.DEF_NAMESPACE.keys()
1524         
1525         ccw =                           geom.getFieldAsBool('ccw', True, ancestry)
1526         ifs_colorPerVertex =    geom.getFieldAsBool('colorPerVertex', True, ancestry) # per vertex or per face
1527         ifs_normalPerVertex =   geom.getFieldAsBool('normalPerVertex', True, ancestry)
1528         
1529         # This is odd how point is inside Coordinate
1530         
1531         # VRML not x3d
1532         #coord = geom.getChildByName('coord') # 'Coordinate'
1533         
1534         coord = geom.getChildBySpec('Coordinate') # works for x3d and vrml
1535         
1536         if coord:       ifs_points = coord.getFieldAsArray('point', 3, ancestry)
1537         else:           coord = []
1538         
1539         if not coord:
1540                 print '\tWarnint: IndexedFaceSet has no points'
1541                 return None, ccw
1542         
1543         ifs_faces = geom.getFieldAsArray('coordIndex', 0, ancestry)
1544         
1545         coords_tex = None
1546         if ifs_faces: # In rare cases this causes problems - no faces but UVs???
1547                 
1548                 # WORKS - VRML ONLY
1549                 # coords_tex = geom.getChildByName('texCoord')
1550                 coords_tex = geom.getChildBySpec('TextureCoordinate')
1551                 
1552                 if coords_tex:
1553                         ifs_texpoints = coords_tex.getFieldAsArray('point', 2, ancestry)
1554                         ifs_texfaces = geom.getFieldAsArray('texCoordIndex', 0, ancestry)
1555                         
1556                         if not ifs_texpoints:
1557                                 # IF we have no coords, then dont bother
1558                                 coords_tex = None
1559                 
1560                 
1561         # WORKS - VRML ONLY
1562         # vcolor = geom.getChildByName('color')
1563         vcolor = geom.getChildBySpec('Color')
1564         vcolor_spot = None # spot color when we dont have an array of colors
1565         if vcolor:
1566                 # float to char
1567                 ifs_vcol = [[int(c*256) for c in col] for col in vcolor.getFieldAsArray('color', 3, ancestry)]
1568                 ifs_color_index = geom.getFieldAsArray('colorIndex', 0, ancestry)
1569                 
1570                 if not ifs_vcol:
1571                         vcolor_spot = [int(c*256) for c in vcolor.getFieldAsFloatTuple('color', [], ancestry)]
1572         
1573         # Convert faces into somthing blender can use
1574         edges = []
1575         
1576         # All lists are aligned!
1577         faces = []
1578         faces_uv = [] # if ifs_texfaces is empty then the faces_uv will match faces exactly.
1579         faces_orig_index = [] # for ngons, we need to know our original index
1580         
1581         if coords_tex and ifs_texfaces:
1582                 do_uvmap = True
1583         else:
1584                 do_uvmap = False
1585         
1586         # current_face = [0] # pointer anyone
1587         
1588         def add_face(face, fuvs, orig_index):
1589                 l = len(face)
1590                 if l==3 or l==4:
1591                         faces.append(face)
1592                         # faces_orig_index.append(current_face[0])
1593                         if do_uvmap:
1594                                 faces_uv.append(fuvs)
1595                                 
1596                         faces_orig_index.append(orig_index)
1597                 elif l==2:                      edges.append(face)
1598                 elif l>4:
1599                         for i in xrange(2, len(face)):
1600                                 faces.append([face[0], face[i-1], face[i]])
1601                                 if do_uvmap:
1602                                         faces_uv.append([fuvs[0], fuvs[i-1], fuvs[i]])
1603                                 faces_orig_index.append(orig_index)
1604                 else:
1605                         # faces with 1 verts? pfft!
1606                         # still will affect index ordering
1607                         pass
1608         
1609         face = []
1610         fuvs = []
1611         orig_index = 0
1612         for i, fi in enumerate(ifs_faces):
1613                 # ifs_texfaces and ifs_faces should be aligned
1614                 if fi != -1:
1615                         # face.append(int(fi)) # in rare cases this is a float
1616                         # EEKADOODLE!!!
1617                         # Annoyance where faces that have a zero index vert get rotated. This will then mess up UVs and VColors
1618                         face.append(int(fi)+1) # in rare cases this is a float, +1 because of stupid EEKADOODLE :/
1619                         
1620                         if do_uvmap:
1621                                 if i >= len(ifs_texfaces):
1622                                         print '\tWarning: UV Texface index out of range'
1623                                         fuvs.append(ifs_texfaces[0])
1624                                 else:
1625                                         fuvs.append(ifs_texfaces[i])
1626                 else:
1627                         add_face(face, fuvs, orig_index)
1628                         face = []
1629                         if do_uvmap:
1630                                 fuvs = []
1631                         orig_index += 1
1632         
1633         add_face(face, fuvs, orig_index)
1634         del add_face # dont need this func anymore
1635         
1636         bpymesh = bpy.data.meshes.new()
1637         
1638         bpymesh.verts.extend([(0,0,0)]) # EEKADOODLE
1639         bpymesh.verts.extend(ifs_points)
1640         
1641         # print len(ifs_points), faces, edges, ngons
1642         
1643         try:
1644                 bpymesh.faces.extend(faces, smooth=True, ignoreDups=True)
1645         except KeyError:
1646                 print "one or more vert indicies out of range. corrupt file?"
1647                 #for f in faces:
1648                 #       bpymesh.faces.extend(faces, smooth=True)
1649         
1650         bpymesh.calcNormals()
1651         
1652         if len(bpymesh.faces) != len(faces):
1653                 print '\tWarning: adding faces did not work! file is invalid, not adding UVs or vcolors'
1654                 return bpymesh, ccw
1655         
1656         # Apply UVs if we have them
1657         if not do_uvmap:
1658                 faces_uv = faces # fallback, we didnt need a uvmap in the first place, fallback to the face/vert mapping.
1659         if coords_tex:
1660                 #print ifs_texpoints
1661                 # print geom
1662                 bpymesh.faceUV = True
1663                 for i,f in enumerate(bpymesh.faces):
1664                         f.image = bpyima
1665                         fuv = faces_uv[i] # uv indicies
1666                         for j,uv in enumerate(f.uv):
1667                                 # print fuv, j, len(ifs_texpoints)
1668                                 try:
1669                                         uv[:] = ifs_texpoints[fuv[j]]
1670                                 except:
1671                                         print '\tWarning: UV Index out of range'
1672                                         uv[:] = ifs_texpoints[0]
1673         
1674         elif bpyima and len(bpymesh.faces):
1675                 # Oh Bugger! - we cant really use blenders ORCO for for texture space since texspace dosnt rotate.
1676                 # we have to create VRML's coords as UVs instead.
1677                 
1678                 # VRML docs
1679                 '''
1680                 If the texCoord field is NULL, a default texture coordinate mapping is calculated using the local
1681                 coordinate system bounding box of the shape. The longest dimension of the bounding box defines the S coordinates,
1682                 and the next longest defines the T coordinates. If two or all three dimensions of the bounding box are equal,
1683                 ties shall be broken by choosing the X, Y, or Z dimension in that order of preference.
1684                 The value of the S coordinate ranges from 0 to 1, from one end of the bounding box to the other.
1685                 The T coordinate ranges between 0 and the ratio of the second greatest dimension of the bounding box to the greatest dimension.
1686                 '''
1687                 
1688                 # Note, S,T == U,V
1689                 # U gets longest, V gets second longest
1690                 xmin, ymin, zmin = ifs_points[0]
1691                 xmax, ymax, zmax = ifs_points[0]
1692                 for co in ifs_points:
1693                         x,y,z = co
1694                         if x < xmin: xmin = x
1695                         if y < ymin: ymin = y
1696                         if z < zmin: zmin = z
1697                         
1698                         if x > xmax: xmax = x
1699                         if y > ymax: ymax = y
1700                         if z > zmax: zmax = z
1701                         
1702                 xlen = xmax - xmin
1703                 ylen = ymax - ymin
1704                 zlen = zmax - zmin
1705                 
1706                 depth_min = xmin, ymin, zmin
1707                 depth_list = [xlen, ylen, zlen]
1708                 depth_sort = depth_list[:]
1709                 depth_sort.sort()
1710                 
1711                 depth_idx = [depth_list.index(val) for val in depth_sort]
1712                 
1713                 axis_u = depth_idx[-1]
1714                 axis_v = depth_idx[-2] # second longest
1715                 
1716                 # Hack, swap these !!! TODO - Why swap??? - it seems to work correctly but should not.
1717                 # axis_u,axis_v = axis_v,axis_u
1718                 
1719                 min_u = depth_min[axis_u]
1720                 min_v = depth_min[axis_v]
1721                 depth_u = depth_list[axis_u]
1722                 depth_v = depth_list[axis_v]
1723                 
1724                 depth_list[axis_u]
1725                 
1726                 if axis_u == axis_v:
1727                         # This should be safe because when 2 axies have the same length, the lower index will be used.
1728                         axis_v += 1
1729                 
1730                 bpymesh.faceUV = True
1731                 
1732                 # HACK !!! - seems to be compatible with Cosmo though.
1733                 depth_v = depth_u = max(depth_v, depth_u)
1734                 
1735                 for f in bpymesh.faces:
1736                         f.image = bpyima
1737                         fuv = f.uv
1738                         
1739                         for i,v in enumerate(f):
1740                                 co = v.co
1741                                 fuv[i][:] = (co[axis_u]-min_u) / depth_u, (co[axis_v]-min_v) / depth_v
1742         
1743         # Add vcote 
1744         if vcolor:
1745                 # print ifs_vcol
1746                 bpymesh.vertexColors = True
1747                 
1748                 for f in bpymesh.faces:
1749                         fcol = f.col
1750                         if ifs_colorPerVertex:
1751                                 fv = f.verts
1752                                 for i,c in enumerate(fcol):
1753                                         color_index = fv[i].index # color index is vert index
1754                                         if ifs_color_index:
1755                                                 try:
1756                                                         color_index = ifs_color_index[color_index]
1757                                                 except:
1758                                                         print '\tWarning: per vertex color index out of range'
1759                                                         continue
1760                                         
1761                                         if len(ifs_vcol) < color_index:
1762                                                 c.r, c.g, c.b = ifs_vcol[color_index]
1763                                         else:
1764                                                 #print '\tWarning: per face color index out of range'
1765                                                 pass
1766                         else:
1767                                 if vcolor_spot: # use 1 color, when ifs_vcol is []
1768                                         for c in fcol:
1769                                                 c.r, c.g, c.b = vcolor_spot
1770                                 else:
1771                                         color_index = faces_orig_index[f.index] # color index is face index
1772                                         #print color_index, ifs_color_index
1773                                         if ifs_color_index:
1774                                                 if color_index <= len(ifs_color_index):
1775                                                         print '\tWarning: per face color index out of range'
1776                                                         color_index = 0
1777                                                 else:
1778                                                         color_index = ifs_color_index[color_index]
1779                                                 
1780                                         
1781                                         col = ifs_vcol[color_index]
1782                                         for i,c in enumerate(fcol):
1783                                                 try:
1784                                                         c.r, c.g, c.b = col
1785                                                 except:
1786                                                         pass # incase its not between 0 and 255
1787         
1788         bpymesh.verts.delete([0,]) # EEKADOODLE
1789         
1790         return bpymesh, ccw
1791
1792 def importMesh_IndexedLineSet(geom, ancestry):
1793         # VRML not x3d
1794         #coord = geom.getChildByName('coord') # 'Coordinate'
1795         coord = geom.getChildBySpec('Coordinate') # works for x3d and vrml
1796         if coord:       points = coord.getFieldAsArray('point', 3, ancestry)
1797         else:           points = []
1798         
1799         if not points:
1800                 print '\tWarning: IndexedLineSet had no points'
1801                 return None
1802         
1803         ils_lines = geom.getFieldAsArray('coordIndex', 0, ancestry)
1804         
1805         lines = []
1806         line = []
1807         
1808         for il in ils_lines:
1809                 if il==-1:
1810                         lines.append(line)
1811                         line = []
1812                 else:
1813                         line.append(int(il))
1814         lines.append(line)
1815         
1816         # vcolor = geom.getChildByName('color') # blender dosnt have per vertex color
1817         
1818         bpycurve = bpy.data.curves.new('IndexedCurve', 'Curve')
1819         bpycurve.setFlag(1)
1820         
1821         w=t=1
1822         
1823         curve_index = 0
1824         
1825         for line in lines:
1826                 if not line:
1827                         continue
1828                 co = points[line[0]]
1829                 bpycurve.appendNurb([co[0], co[1], co[2], w, t])
1830                 bpycurve[curve_index].type= 0 # Poly Line
1831                 
1832                 for il in line[1:]:
1833                         co = points[il]
1834                         bpycurve.appendPoint(curve_index, [co[0], co[1], co[2], w])
1835                 
1836                 
1837                 curve_index += 1
1838         
1839         return bpycurve
1840
1841
1842 def importMesh_PointSet(geom, ancestry):
1843         # VRML not x3d
1844         #coord = geom.getChildByName('coord') # 'Coordinate'
1845         coord = geom.getChildBySpec('Coordinate')  # works for x3d and vrml
1846         if coord:       points = coord.getFieldAsArray('point', 3, ancestry)
1847         else:           points = []
1848         
1849         # vcolor = geom.getChildByName('color') # blender dosnt have per vertex color
1850         
1851         bpymesh = bpy.data.meshes.new()
1852         bpymesh.verts.extend(points)
1853         bpymesh.calcNormals() # will just be dummy normals
1854         return bpymesh
1855
1856 GLOBALS['CIRCLE_DETAIL'] = 12
1857
1858 MATRIX_Z_TO_Y = RotationMatrix(90, 4, 'x')
1859
1860 def importMesh_Sphere(geom, ancestry):
1861         # bpymesh = bpy.data.meshes.new()
1862         diameter = geom.getFieldAsFloat('radius', 0.5, ancestry) * 2 # * 2 for the diameter
1863         bpymesh = Mesh.Primitives.UVsphere(GLOBALS['CIRCLE_DETAIL'], GLOBALS['CIRCLE_DETAIL'], diameter)  
1864         bpymesh.transform(MATRIX_Z_TO_Y)
1865         return bpymesh
1866
1867 def importMesh_Cylinder(geom, ancestry):
1868         # bpymesh = bpy.data.meshes.new()
1869         diameter = geom.getFieldAsFloat('radius', 1.0, ancestry) * 2 # * 2 for the diameter
1870         height = geom.getFieldAsFloat('height', 2, ancestry)
1871         bpymesh = Mesh.Primitives.Cylinder(GLOBALS['CIRCLE_DETAIL'], diameter, height) 
1872         bpymesh.transform(MATRIX_Z_TO_Y)
1873         
1874         # Warning - Rely in the order Blender adds verts
1875         # not nice design but wont change soon.
1876         
1877         bottom = geom.getFieldAsBool('bottom', True, ancestry)
1878         side = geom.getFieldAsBool('side', True, ancestry)
1879         top = geom.getFieldAsBool('top', True, ancestry)
1880         
1881         if not top: # last vert is top center of tri fan.
1882                 bpymesh.verts.delete([(GLOBALS['CIRCLE_DETAIL']+GLOBALS['CIRCLE_DETAIL'])+1])
1883         
1884         if not bottom: # second last vert is bottom of triangle fan
1885                 bpymesh.verts.delete([GLOBALS['CIRCLE_DETAIL']+GLOBALS['CIRCLE_DETAIL']])
1886         
1887         if not side:
1888                 # remove all quads
1889                 bpymesh.faces.delete(1, [f for f in bpymesh.faces if len(f)==4])
1890         
1891         return bpymesh
1892
1893 def importMesh_Cone(geom, ancestry):
1894         # bpymesh = bpy.data.meshes.new()
1895         diameter = geom.getFieldAsFloat('bottomRadius', 1.0, ancestry) * 2 # * 2 for the diameter
1896         height = geom.getFieldAsFloat('height', 2, ancestry)
1897         bpymesh = Mesh.Primitives.Cone(GLOBALS['CIRCLE_DETAIL'], diameter, height) 
1898         bpymesh.transform(MATRIX_Z_TO_Y)
1899         
1900         # Warning - Rely in the order Blender adds verts
1901         # not nice design but wont change soon.
1902         
1903         bottom = geom.getFieldAsBool('bottom', True, ancestry)
1904         side = geom.getFieldAsBool('side', True, ancestry)
1905         
1906         if not bottom: # last vert is on the bottom
1907                 bpymesh.verts.delete([GLOBALS['CIRCLE_DETAIL']+1])
1908         if not side: # second last vert is on the pointy bit of the cone
1909                 bpymesh.verts.delete([GLOBALS['CIRCLE_DETAIL']])
1910         
1911         return bpymesh
1912
1913 def importMesh_Box(geom, ancestry):
1914         # bpymesh = bpy.data.meshes.new()
1915         
1916         size = geom.getFieldAsFloatTuple('size', (2.0, 2.0, 2.0), ancestry)
1917         bpymesh = Mesh.Primitives.Cube(1.0) 
1918
1919         # Scale the box to the size set
1920         scale_mat = Matrix([size[0],0,0], [0, size[1], 0], [0, 0, size[2]])
1921         bpymesh.transform(scale_mat.resize4x4())
1922         
1923         return bpymesh
1924
1925 def importShape(node, ancestry):
1926         vrmlname = node.getDefName()
1927         if not vrmlname: vrmlname = 'Shape'
1928         
1929         # works 100% in vrml, but not x3d
1930         #appr = node.getChildByName('appearance') # , 'Appearance'
1931         #geom = node.getChildByName('geometry') # , 'IndexedFaceSet'
1932         
1933         # Works in vrml and x3d
1934         appr = node.getChildBySpec('Appearance')
1935         geom = node.getChildBySpec(['IndexedFaceSet', 'IndexedLineSet', 'PointSet', 'Sphere', 'Box', 'Cylinder', 'Cone'])
1936         
1937         # For now only import IndexedFaceSet's
1938         if geom:
1939                 bpymat = None
1940                 bpyima = None
1941                 texmtx = None
1942                 
1943                 depth = 0 # so we can set alpha face flag later
1944                 
1945                 if appr:
1946                         
1947                         #mat = appr.getChildByName('material') # 'Material'
1948                         #ima = appr.getChildByName('texture') # , 'ImageTexture'
1949                         #if ima and ima.getSpec() != 'ImageTexture':
1950                         #       print '\tWarning: texture type "%s" is not supported' % ima.getSpec() 
1951                         #       ima = None
1952                         # textx = appr.getChildByName('textureTransform')
1953                         
1954                         mat = appr.getChildBySpec('Material')
1955                         ima = appr.getChildBySpec('ImageTexture')
1956                         
1957                         textx = appr.getChildBySpec('TextureTransform')
1958                         
1959                         if textx:
1960                                 texmtx = translateTexTransform(textx, ancestry)
1961                         
1962
1963                         
1964                         # print mat, ima
1965                         if mat or ima:
1966                                 
1967                                 if not mat:
1968                                         mat = ima # This is a bit dumb, but just means we use default values for all
1969                                 
1970                                 # all values between 0.0 and 1.0, defaults from VRML docs
1971                                 bpymat = bpy.data.materials.new()
1972                                 bpymat.amb =            mat.getFieldAsFloat('ambientIntensity', 0.2, ancestry)
1973                                 bpymat.rgbCol =         mat.getFieldAsFloatTuple('diffuseColor', [0.8, 0.8, 0.8], ancestry)
1974                                 
1975                                 # NOTE - blender dosnt support emmisive color
1976                                 # Store in mirror color and approximate with emit.
1977                                 emit =                          mat.getFieldAsFloatTuple('emissiveColor', [0.0, 0.0, 0.0], ancestry)
1978                                 bpymat.mirCol =         emit
1979                                 bpymat.emit =           (emit[0]+emit[1]+emit[2])/3.0
1980                                 
1981                                 bpymat.hard =           int(1+(510*mat.getFieldAsFloat('shininess', 0.2, ancestry))) # 0-1 -> 1-511
1982                                 bpymat.specCol =        mat.getFieldAsFloatTuple('specularColor', [0.0, 0.0, 0.0], ancestry)
1983                                 bpymat.alpha =          1.0 - mat.getFieldAsFloat('transparency', 0.0, ancestry)
1984                                 if bpymat.alpha < 0.999:
1985                                         bpymat.mode |= Material.Modes.ZTRANSP
1986                         
1987                         
1988                         if ima:
1989                                 
1990                                 ima_url =                       ima.getFieldAsString('url', None, ancestry)
1991                                 
1992                                 if ima_url==None:
1993                                         try:            ima_url = ima.getFieldAsStringArray('url', ancestry)[0] # in some cases we get a list of images.
1994                                         except:         ima_url = None
1995                                 
1996                                 if ima_url==None:
1997                                         print "\twarning, image with no URL, this is odd"
1998                                 else:
1999                                         bpyima= BPyImage.comprehensiveImageLoad(ima_url, dirName(node.getFilename()), PLACE_HOLDER= False, RECURSIVE= False, CONVERT_CALLBACK= imageConvertCompat)
2000                                         if bpyima:
2001                                                 texture= bpy.data.textures.new()
2002                                                 texture.setType('Image')
2003                                                 texture.image = bpyima
2004                                                 
2005                                                 # Adds textures for materials (rendering)
2006                                                 try:    depth = bpyima.depth
2007                                                 except: depth = -1
2008                                                 
2009                                                 if depth == 32:
2010                                                         # Image has alpha
2011                                                         bpymat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL | Texture.MapTo.ALPHA)
2012                                                         texture.setImageFlags('MipMap', 'InterPol', 'UseAlpha')
2013                                                         bpymat.mode |= Material.Modes.ZTRANSP
2014                                                         bpymat.alpha = 0.0
2015                                                 else:
2016                                                         bpymat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL)
2017                                                         
2018                                                 ima_repS =                      ima.getFieldAsBool('repeatS', True, ancestry)
2019                                                 ima_repT =                      ima.getFieldAsBool('repeatT', True, ancestry)
2020                                                 
2021                                                 # To make this work properly we'd need to scale the UV's too, better to ignore th
2022                                                 # texture.repeat =      max(1, ima_repS * 512), max(1, ima_repT * 512)
2023                                                 
2024                                                 if not ima_repS: bpyima.clampX = True
2025                                                 if not ima_repT: bpyima.clampY = True
2026                 
2027                 bpydata = None
2028                 geom_spec = geom.getSpec()
2029                 ccw = True
2030                 if geom_spec == 'IndexedFaceSet':
2031                         bpydata, ccw = importMesh_IndexedFaceSet(geom, bpyima, ancestry)
2032                 elif geom_spec == 'IndexedLineSet':
2033                         bpydata = importMesh_IndexedLineSet(geom, ancestry)
2034                 elif geom_spec == 'PointSet':
2035                         bpydata = importMesh_PointSet(geom, ancestry)
2036                 elif geom_spec == 'Sphere':
2037                         bpydata = importMesh_Sphere(geom, ancestry)
2038                 elif geom_spec == 'Box':
2039                         bpydata = importMesh_Box(geom, ancestry)
2040                 elif geom_spec == 'Cylinder':
2041                         bpydata = importMesh_Cylinder(geom, ancestry)
2042                 elif geom_spec == 'Cone':
2043                         bpydata = importMesh_Cone(geom, ancestry)
2044                 else:
2045                         print '\tWarning: unsupported type "%s"' % geom_spec
2046                         return
2047                 
2048                 if bpydata:
2049                         vrmlname = vrmlname + geom_spec
2050                         
2051                         bpydata.name = vrmlname
2052                         
2053                         bpyob  = node.blendObject = bpy.data.scenes.active.objects.new(bpydata)
2054                         
2055                         if type(bpydata) == Types.MeshType:
2056                                 is_solid =                      geom.getFieldAsBool('solid', True, ancestry)
2057                                 creaseAngle =           geom.getFieldAsFloat('creaseAngle', None, ancestry)
2058                                 
2059                                 if creaseAngle != None:
2060                                         bpydata.maxSmoothAngle = 1+int(min(79, creaseAngle * RAD_TO_DEG))
2061                                         bpydata.mode |= Mesh.Modes.AUTOSMOOTH
2062                                 
2063                                 # Only ever 1 material per shape
2064                                 if bpymat:      bpydata.materials = [bpymat]
2065                                 
2066                                 if bpydata.faceUV:
2067                                         
2068                                         if depth==32: # set the faces alpha flag?
2069                                                 transp = Mesh.FaceTranspModes.ALPHA
2070                                                 for f in bpydata.faces:
2071                                                         f.transp = transp
2072                                 
2073                                         if texmtx:
2074                                                 # Apply texture transform?
2075                                                 uv_copy = Vector()
2076                                                 for f in bpydata.faces:
2077                                                         for uv in f.uv:
2078                                                                 uv_copy.x = uv.x
2079                                                                 uv_copy.y = uv.y
2080                                                                 
2081                                                                 uv.x, uv.y = (uv_copy * texmtx)[0:2]
2082                                 # Done transforming the texture
2083                                 
2084                                 
2085                                 # Must be here and not in IndexedFaceSet because it needs an object for the flip func. Messy :/
2086                                 if not ccw: bpydata.flipNormals()
2087                                 
2088                                 
2089                         # else could be a curve for example
2090                         
2091                         
2092                         
2093                         # Can transform data or object, better the object so we can instance the data
2094                         #bpymesh.transform(getFinalMatrix(node))
2095                         bpyob.setMatrix( getFinalMatrix(node, None, ancestry) )
2096
2097
2098 def importLamp_PointLight(node, ancestry):
2099         vrmlname = node.getDefName()
2100         if not vrmlname: vrmlname = 'PointLight'
2101         
2102         # ambientIntensity = node.getFieldAsFloat('ambientIntensity', 0.0, ancestry) # TODO
2103         # attenuation = node.getFieldAsFloatTuple('attenuation', (1.0, 0.0, 0.0), ancestry) # TODO
2104         color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry)
2105         intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher.
2106         location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry)
2107         # is_on = node.getFieldAsBool('on', True, ancestry) # TODO
2108         radius = node.getFieldAsFloat('radius', 100.0, ancestry)
2109         
2110         bpylamp = bpy.data.lamps.new()
2111         bpylamp.setType('Lamp')
2112         bpylamp.energy = intensity
2113         bpylamp.dist = radius
2114         bpylamp.col = color
2115         
2116         mtx = TranslationMatrix(Vector(location))
2117         
2118         return bpylamp, mtx
2119
2120 def importLamp_DirectionalLight(node, ancestry):
2121         vrmlname = node.getDefName()
2122         if not vrmlname: vrmlname = 'DirectLight'
2123         
2124         # ambientIntensity = node.getFieldAsFloat('ambientIntensity', 0.0) # TODO
2125         color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry)
2126         direction = node.getFieldAsFloatTuple('direction', (0.0, 0.0, -1.0), ancestry)
2127         intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher.
2128         # is_on = node.getFieldAsBool('on', True, ancestry) # TODO
2129         
2130         bpylamp = bpy.data.lamps.new(vrmlname)
2131         bpylamp.setType('Sun')
2132         bpylamp.energy = intensity
2133         bpylamp.col = color
2134         
2135         # lamps have their direction as -z, yup
2136         mtx = Vector(direction).toTrackQuat('-z', 'y').toMatrix().resize4x4()
2137         
2138         return bpylamp, mtx
2139
2140 # looks like default values for beamWidth and cutOffAngle were swapped in VRML docs.
2141
2142 def importLamp_SpotLight(node, ancestry):
2143         vrmlname = node.getDefName()
2144         if not vrmlname: vrmlname = 'SpotLight'
2145         
2146         # ambientIntensity = geom.getFieldAsFloat('ambientIntensity', 0.0, ancestry) # TODO
2147         # attenuation = geom.getFieldAsFloatTuple('attenuation', (1.0, 0.0, 0.0), ancestry) # TODO
2148         beamWidth = node.getFieldAsFloat('beamWidth', 1.570796, ancestry) * RAD_TO_DEG # max is documented to be 1.0 but some files have higher.
2149         color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry)
2150         cutOffAngle = node.getFieldAsFloat('cutOffAngle', 0.785398, ancestry) * RAD_TO_DEG # max is documented to be 1.0 but some files have higher.
2151         direction = node.getFieldAsFloatTuple('direction', (0.0, 0.0, -1.0), ancestry)
2152         intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher.
2153         location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry)
2154         # is_on = node.getFieldAsBool('on', True, ancestry) # TODO
2155         radius = node.getFieldAsFloat('radius', 100.0, ancestry)
2156         
2157         bpylamp = bpy.data.lamps.new(vrmlname)
2158         bpylamp.setType('Spot')
2159         bpylamp.energy = intensity
2160         bpylamp.dist = radius
2161         bpylamp.col = color
2162         bpylamp.spotSize = cutOffAngle
2163         if beamWidth > cutOffAngle:
2164                 bpylamp.spotBlend = 0.0
2165         else:
2166                 if cutOffAngle==0.0: #@#$%^&*(!!! - this should never happen
2167                         bpylamp.spotBlend = 0.5
2168                 else:
2169                         bpylamp.spotBlend = beamWidth / cutOffAngle
2170         
2171         # Convert 
2172         
2173         # lamps have their direction as -z, y==up
2174         mtx = Vector(direction).toTrackQuat('-z', 'y').toMatrix().resize4x4() * TranslationMatrix(Vector(location))
2175         
2176         return bpylamp, mtx
2177
2178
2179 def importLamp(node, spec, ancestry):
2180         if spec=='PointLight':
2181                 bpylamp,mtx = importLamp_PointLight(node, ancestry)
2182         elif spec=='DirectionalLight':
2183                 bpylamp,mtx = importLamp_DirectionalLight(node, ancestry)
2184         elif spec=='SpotLight':
2185                 bpylamp,mtx = importLamp_SpotLight(node, ancestry)
2186         else:
2187                 print "Error, not a lamp"
2188                 raise ValueError
2189         
2190         bpyob = node.blendObject = bpy.data.scenes.active.objects.new(bpylamp)
2191         bpyob.setMatrix( getFinalMatrix(node, mtx, ancestry) )
2192
2193
2194 def importViewpoint(node, ancestry):
2195         name = node.getDefName()
2196         if not name: name = 'Viewpoint'
2197         
2198         fieldOfView = node.getFieldAsFloat('fieldOfView', 0.785398, ancestry) * RAD_TO_DEG # max is documented to be 1.0 but some files have higher.
2199         # jump = node.getFieldAsBool('jump', True, ancestry)
2200         orientation = node.getFieldAsFloatTuple('orientation', (0.0, 0.0, 1.0, 0.0), ancestry)
2201         position = node.getFieldAsFloatTuple('position', (0.0, 0.0, 0.0), ancestry)
2202         description = node.getFieldAsString('description', '', ancestry)
2203         
2204         bpycam = bpy.data.cameras.new(name)
2205         
2206         bpycam.angle = fieldOfView
2207         
2208         mtx = translateRotation(orientation) * TranslationMatrix(Vector(position))
2209         
2210         
2211         bpyob = node.blendObject = bpy.data.scenes.active.objects.new(bpycam)
2212         bpyob.setMatrix( getFinalMatrix(node, mtx, ancestry) )
2213
2214
2215 def importTransform(node, ancestry):
2216         name = node.getDefName()
2217         if not name: name = 'Transform'
2218         
2219         bpyob = node.blendObject = bpy.data.scenes.active.objects.new('Empty', name) # , name)
2220         bpyob.setMatrix( getFinalMatrix(node, None, ancestry) )
2221
2222         # so they are not too annoying
2223         bpyob.emptyShape= Blender.Object.EmptyShapes.AXES
2224         bpyob.drawSize= 0.2
2225
2226         
2227 #def importTimeSensor(node):
2228
2229
2230 def translatePositionInterpolator(node, ipo, ancestry):
2231         key = node.getFieldAsArray('key', 0, ancestry)
2232         keyValue = node.getFieldAsArray('keyValue', 3, ancestry)
2233         
2234         try:
2235                 loc_x = ipo.addCurve('LocX')
2236                 loc_y = ipo.addCurve('LocY')
2237                 loc_z = ipo.addCurve('LocZ')
2238         except ValueError:
2239                 return
2240                 
2241         loc_x.interpolation = loc_y.interpolation = loc_z.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
2242         
2243         for i, time in enumerate(key):
2244                 try:            x,y,z = keyValue[i]
2245                 except: continue
2246                 
2247                 loc_x.append((time,x))
2248                 loc_y.append((time,y))
2249                 loc_z.append((time,z))
2250
2251 def translateOrientationInterpolator(node, ipo, ancestry):
2252         key = node.getFieldAsArray('key', 0, ancestry)
2253         keyValue = node.getFieldAsArray('keyValue', 4, ancestry)
2254         
2255         try:
2256                 rot_x = ipo.addCurve('RotX')
2257                 rot_y = ipo.addCurve('RotY')
2258                 rot_z = ipo.addCurve('RotZ')
2259         except ValueError:
2260                 return
2261         
2262         rot_x.interpolation = rot_y.interpolation = rot_z.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
2263         
2264         for i, time in enumerate(key):
2265                 try:            x,y,z,w = keyValue[i]
2266                 except: continue
2267                 
2268                 mtx = translateRotation((x,y,z,w))
2269                 eul = mtx.toEuler()
2270                 rot_x.append((time,eul.x/10.0))
2271                 rot_y.append((time,eul.y/10.0))
2272                 rot_z.append((time,eul.z/10.0))
2273
2274 # Untested!
2275 def translateScalarInterpolator(node, ipo, ancestry):
2276         key = node.getFieldAsArray('key', 0, ancestry)
2277         keyValue = node.getFieldAsArray('keyValue', 4, ancestry)
2278         
2279         try:
2280                 sca_x = ipo.addCurve('ScaleX')
2281                 sca_y = ipo.addCurve('ScaleY')
2282                 sca_z = ipo.addCurve('ScaleZ')
2283         except ValueError:
2284                 return
2285         
2286         sca_x.interpolation = sca_y.interpolation = sca_z.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
2287         
2288         for i, time in enumerate(key):
2289                 try:            x,y,z = keyValue[i]
2290                 except: continue
2291                 sca_x.append((time,x/10.0))
2292                 sca_y.append((time,y/10.0))
2293                 sca_z.append((time,z/10.0))
2294
2295 def translateTimeSensor(node, ipo, ancestry):
2296         '''
2297         Apply a time sensor to an IPO, VRML has many combinations of loop/start/stop/cycle times
2298         to give different results, for now just do the basics
2299         '''
2300         
2301         time_cu = ipo.addCurve('Time')
2302         time_cu.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
2303         
2304         cycleInterval = node.getFieldAsFloat('cycleInterval', None, ancestry)
2305         
2306         startTime = node.getFieldAsFloat('startTime', 0.0, ancestry)
2307         stopTime = node.getFieldAsFloat('stopTime', 250.0, ancestry)
2308                 
2309         if cycleInterval != None:
2310                 stopTime = startTime+cycleInterval
2311         
2312         loop = node.getFieldAsBool('loop', False, ancestry)
2313         
2314         time_cu.append((1+startTime, 0.0))
2315         time_cu.append((1+stopTime, 1.0/10.0))# anoying, the UI uses /10
2316         
2317         
2318         if loop:
2319                 time_cu.extend = Blender.IpoCurve.ExtendTypes.CYCLIC # or - EXTRAP, CYCLIC_EXTRAP, CONST, 
2320
2321
2322 def importRoute(node, ancestry):
2323         '''
2324         Animation route only at the moment
2325         '''
2326         
2327         if not hasattr(node, 'fields'):
2328                 return
2329         
2330         routeIpoDict = node.getRouteIpoDict()
2331         
2332         def getIpo(id):
2333                 try: ipo =      routeIpoDict[id]
2334                 except: ipo = routeIpoDict[id] = bpy.data.ipos.new('web3d_ipo', 'Object')
2335                 return ipo
2336         
2337         # for getting definitions
2338         defDict = node.getDefDict()
2339         '''
2340         Handles routing nodes to eachother
2341         
2342 ROUTE vpPI.value_changed TO champFly001.set_position
2343 ROUTE vpOI.value_changed TO champFly001.set_orientation
2344 ROUTE vpTs.fraction_changed TO vpPI.set_fraction
2345 ROUTE vpTs.fraction_changed TO vpOI.set_fraction
2346 ROUTE champFly001.bindTime TO vpTs.set_startTime
2347         '''
2348         
2349         #from_id, from_type = node.id[1].split('.')
2350         #to_id, to_type = node.id[3].split('.')
2351         
2352         #value_changed
2353         set_position_node = None
2354         set_orientation_node = None
2355         time_node = None
2356         
2357         for field in node.fields:
2358                 if field and field[0]=='ROUTE':
2359                         try:
2360                                 from_id, from_type = field[1].split('.')
2361                                 to_id, to_type = field[3].split('.')
2362                         except:
2363                                 print "Warning, invalid ROUTE", field
2364                                 continue
2365                         
2366                         if from_type == 'value_changed':
2367                                 if to_type == 'set_position':
2368                                         ipo = getIpo(to_id)
2369                                         set_data_from_node = defDict[from_id]
2370                                         translatePositionInterpolator(set_data_from_node, ipo, ancestry)
2371                                 
2372                                 if to_type in ('set_orientation', 'rotation'):
2373                                         ipo = getIpo(to_id)
2374                                         set_data_from_node = defDict[from_id]
2375                                         translateOrientationInterpolator(set_data_from_node, ipo, ancestry)
2376                                 
2377                                 if to_type == 'set_scale':
2378                                         ipo = getIpo(to_id)
2379                                         set_data_from_node = defDict[from_id]
2380                                         translateScalarInterpolator(set_data_from_node, ipo, ancestry)
2381                                 
2382                         elif from_type =='bindTime':
2383                                 ipo = getIpo(from_id)
2384                                 time_node = defDict[to_id]
2385                                 translateTimeSensor(time_node, ipo, ancestry)
2386                         
2387                 
2388
2389
2390 def load_web3d(path, PREF_FLAT=False, PREF_CIRCLE_DIV=16, HELPER_FUNC = None):
2391         
2392         # Used when adding blender primitives
2393         GLOBALS['CIRCLE_DETAIL'] = PREF_CIRCLE_DIV
2394         
2395         #root_node = vrml_parse('/_Cylinder.wrl')
2396         if path.lower().endswith('.x3d'):
2397                 root_node, msg = x3d_parse(path)
2398         else:
2399                 root_node, msg = vrml_parse(path)
2400         
2401         if not root_node:
2402                 if Blender.mode == 'background':
2403                         print msg
2404                 else:
2405                         Blender.Draw.PupMenu(msg)
2406                 return
2407         
2408         
2409         # fill with tuples - (node, [parents-parent, parent])
2410         all_nodes = root_node.getSerialized([], [])
2411         
2412         for node, ancestry in all_nodes:
2413                 #if 'castle.wrl' not in node.getFilename():
2414                 #       continue
2415                 
2416                 spec = node.getSpec()
2417                 '''
2418                 prefix = node.getPrefix()
2419                 if prefix=='PROTO':
2420                         pass
2421                 else
2422                 '''
2423                 if HELPER_FUNC and HELPER_FUNC(node, ancestry):
2424                         # Note, include this function so the VRML/X3D importer can be extended
2425                         # by an external script. - gets first pick 
2426                         pass
2427                 if spec=='Shape':
2428                         importShape(node, ancestry)
2429                 elif spec in ('PointLight', 'DirectionalLight', 'SpotLight'):
2430                         importLamp(node, spec, ancestry)
2431                 elif spec=='Viewpoint':
2432                         importViewpoint(node, ancestry)
2433                 elif spec=='Transform':
2434                         # Only use transform nodes when we are not importing a flat object hierarchy
2435                         if PREF_FLAT==False:
2436                                 importTransform(node, ancestry)
2437                         '''
2438                 # These are delt with later within importRoute
2439                 elif spec=='PositionInterpolator':
2440                         ipo = bpy.data.ipos.new('web3d_ipo', 'Object')
2441                         translatePositionInterpolator(node, ipo)
2442                         '''
2443         
2444         
2445         
2446         # After we import all nodes, route events - anim paths
2447         for node, ancestry in all_nodes:
2448                 importRoute(node, ancestry)
2449         
2450         for node, ancestry in all_nodes:
2451                 if node.isRoot():
2452                         # we know that all nodes referenced from will be in 
2453                         # routeIpoDict so no need to run node.getDefDict() for every node.
2454                         routeIpoDict = node.getRouteIpoDict()
2455                         defDict = node.getDefDict()
2456                         
2457                         for key, ipo in routeIpoDict.iteritems():
2458                                 
2459                                 # Assign anim curves
2460                                 node = defDict[key]
2461                                 if node.blendObject==None: # Add an object if we need one for animation
2462                                         node.blendObject = bpy.data.scenes.active.objects.new('Empty', 'AnimOb') # , name)
2463                                         
2464                                 node.blendObject.setIpo(ipo)
2465
2466         
2467         
2468         # Add in hierarchy
2469         if PREF_FLAT==False:
2470                 child_dict = {}
2471                 for node, ancestry in all_nodes:
2472                         if node.blendObject:
2473                                 blendObject = None
2474                                 
2475                                 # Get the last parent
2476                                 i = len(ancestry)
2477                                 while i:
2478                                         i-=1
2479                                         blendObject = ancestry[i].blendObject
2480                                         if blendObject:
2481                                                 break
2482                                 
2483                                 if blendObject:
2484                                         # Parent Slow, - 1 liner but works
2485                                         # blendObject.makeParent([node.blendObject], 0, 1)
2486                                         
2487                                         # Parent FAST
2488                                         try:    child_dict[blendObject].append(node.blendObject)
2489                                         except: child_dict[blendObject] = [node.blendObject]
2490                 
2491                 # Parent FAST
2492                 for parent, children in child_dict.iteritems():
2493                         parent.makeParent(children, 0, 1)
2494                 
2495                 # update deps
2496                 bpy.data.scenes.active.update(1)
2497                 del child_dict
2498
2499
2500 def load_ui(path):
2501         Draw = Blender.Draw
2502         PREF_HIERARCHY= Draw.Create(0)
2503         PREF_CIRCLE_DIV= Draw.Create(16)
2504         
2505         # Get USER Options
2506         pup_block= [\
2507         'Import...',\
2508         ('Hierarchy', PREF_HIERARCHY, 'Import transform nodes as empties to create a parent/child hierarchy'),\
2509         ('Circle Div:', PREF_CIRCLE_DIV, 3, 128, 'Number of divisions to use for circular primitives')
2510         ]
2511         
2512         if not Draw.PupBlock('Import X3D/VRML...', pup_block):
2513                 return
2514         
2515         Window.WaitCursor(1)
2516         
2517         load_web3d(path,\
2518           (not PREF_HIERARCHY.val),\
2519           PREF_CIRCLE_DIV.val,\
2520         )
2521         
2522         Window.WaitCursor(0)
2523         
2524
2525 if __name__ == '__main__':
2526         Window.FileSelector(load_ui, 'Import X3D/VRML97')
2527         
2528
2529 # Testing stuff
2530
2531 # load_web3d('/test.x3d')
2532 # load_web3d('/_Cylinder.x3d')
2533
2534 # Testing below
2535 # load_web3d('m:\\root\\Desktop\\_Cylinder.wrl')
2536 # load_web3d('/_Cylinder.wrl')
2537 # load_web3d('/fe/wrl/Vrml/EGS/BCKGD.WRL')
2538
2539 # load_web3d('/fe/wrl/Vrml/EGS/GRNDPLNE.WRL')
2540 # load_web3d('/fe/wrl/Vrml/EGS/INDEXFST.WRL')
2541 # load_web3d('/fe/wrl/panel1c.wrl')
2542 # load_web3d('/test.wrl')
2543 # load_web3d('/fe/wrl/dulcimer.wrl')
2544 # load_web3d('/fe/wrl/rccad/Ju-52.wrl') # Face index out of range
2545 # load_web3d('/fe/wrl/16lat.wrl') # spotlight
2546 # load_web3d('/fe/wrl/Vrml/EGS/FOG.WRL') # spotlight
2547 # load_web3d('/fe/wrl/Vrml/EGS/LOD.WRL') # vcolor per face
2548
2549 # load_web3d('/fe/wrl/new/daybreak_final.wrl') # no faces in mesh, face duplicate error
2550 # load_web3d('/fe/wrl/new/earth.wrl')
2551 # load_web3d('/fe/wrl/new/hendrix.ei.dtu.dk/vrml/talairach/fourd/TalaDruryRight.wrl') # define/use fields
2552 # load_web3d('/fe/wrl/new/imac.wrl') # extrusion and define/use fields, face index is a float somehow
2553 # load_web3d('/fe/wrl/new/www.igs.net/~mascott/vrml/vrml2/mcastle.wrl') 
2554 # load_web3d('/fe/wrl/new/www.igs.net/~mascott/vrml/vrml2/tower.wrl') 
2555 # load_web3d('/fe/wrl/new/www.igs.net/~mascott/vrml/vrml2/temple.wrl') 
2556 # load_web3d('/fe/wrl/brain.wrl')  # field define test 'a IS b'
2557 # load_web3d('/fe/wrl/new/coaster.wrl')  # fields that are confusing to read.
2558
2559 # X3D 
2560
2561 # load_web3d('/fe/x3d/www.web3d.org/x3d/content/examples/Basic/StudentProjects/PlayRoom.x3d') # invalid UVs
2562
2563
2564
2565 def test():
2566         import os
2567         
2568         files = os.popen('find /fe/wrl -iname "*.wrl"').readlines()
2569         # files = os.popen('find /fe/x3d -iname "*.x3d"').readlines()
2570         # files = os.popen('find   /fe/x3d/X3dExamplesSavage   -iname "*.x3d"').readlines()
2571
2572         files.sort()
2573         tot = len(files)
2574         for i, f in enumerate(files):
2575                 if i < 124 or i > 1000000:
2576                         continue
2577                 
2578                 #if i != 1068:
2579                 #       continue
2580                 
2581                 #if i != 12686:
2582                 #       continue
2583                 
2584                 f = f.strip()
2585                 print f, i, tot
2586                 sce = bpy.data.scenes.new(str(i) + '_' + f.split('/')[-1])
2587                 bpy.data.scenes.active = sce
2588                 # Window.
2589                 load_web3d(f, PREF_FLAT=True)
2590         
2591 # test()