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