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