copy x3d/vrml importer from blender 2.4x.
authorCampbell Barton <ideasman42@gmail.com>
Mon, 10 Jan 2011 13:11:56 +0000 (13:11 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Mon, 10 Jan 2011 13:11:56 +0000 (13:11 +0000)
svn cp https://svn.blender.org/svnroot/bf-blender/branches/blender2.4/release/scripts/import_web3d.py release/scripts/op/io_scene_x3d/import_x3d.py

release/scripts/op/io_scene_x3d/import_x3d.py [new file with mode: 0644]

diff --git a/release/scripts/op/io_scene_x3d/import_x3d.py b/release/scripts/op/io_scene_x3d/import_x3d.py
new file mode 100644 (file)
index 0000000..a554750
--- /dev/null
@@ -0,0 +1,2594 @@
+#!BPY
+"""
+Name: 'X3D & VRML97 (.x3d / wrl)...'
+Blender: 248
+Group: 'Import'
+Tooltip: 'Load an X3D or VRML97 file'
+"""
+
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# (C) Copyright 2008 Paravizion
+# Written by Campbell Barton aka Ideasman42
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+# --------------------------------------------------------------------------
+
+__author__ = "Campbell Barton"
+__url__ = ['www.blender.org', 'blenderartists.org', 'http://wiki.blender.org/index.php/Scripts/Manual/Import/X3D_VRML97']
+__version__ = "0.1"
+
+__bpydoc__ = """\
+This script is an importer for the X3D and VRML97 file formats.
+"""
+
+DEBUG = False
+
+# This should work without a blender at all
+try:
+       from Blender.sys import exists
+except:
+       from os.path import exists
+
+def baseName(path):
+       return path.split('/')[-1].split('\\')[-1]
+
+def dirName(path):
+       return path[:-len(baseName(path))]
+
+def imageConvertCompat(path):
+       
+       try:             import os
+       except:          return path
+       if os.sep=='\\': return path # assime win32 has quicktime, dont convert
+       
+       if path.lower().endswith('.gif'):
+               path_to = path[:-3] + 'png'
+               
+               '''
+               if exists(path_to):
+                       return path_to
+               '''
+               # print '\n'+path+'\n'+path_to+'\n'
+               os.system('convert "%s" "%s"' % (path, path_to)) # for now just hope we have image magick
+               
+               if exists(path_to):
+                       return path_to
+       
+       return path
+
+# notes
+# transform are relative 
+# order dosnt matter for loc/size/rot
+# right handed rotation
+# angles are in radians
+# rotation first defines axis then ammount in radians
+
+
+
+# =============================== VRML Spesific
+
+
+def vrmlFormat(data):
+       '''
+       Keep this as a valid vrml file, but format in a way we can predict.
+       '''
+       # Strip all commends - # not in strings - warning multiline strings are ignored.
+       def strip_comment(l):
+               #l = ' '.join(l.split())
+               l = l.strip()
+               
+               if l.startswith('#'):
+                       return ''
+               
+               i = l.find('#')
+               
+               if i==-1:
+                       return l
+               
+               # Most cases accounted for! if we have a comment at the end of the line do this...
+               #j = l.find('url "')
+               j = l.find('"')
+               
+               if j == -1: # simple no strings
+                       return l[:i].strip()
+               
+               q = False
+               for i,c in enumerate(l):
+                       if c == '"':
+                               q = not q # invert
+                       
+                       elif c == '#':
+                               if q==False:
+                                       return l[:i-1]
+               
+               return l
+       
+       data = '\n'.join([strip_comment(l) for l in data.split('\n') ]) # remove all whitespace
+       
+       EXTRACT_STRINGS = True # only needed when strings or filesnames containe ,[]{} chars :/
+       
+       if EXTRACT_STRINGS:
+               
+               # We need this so we can detect URL's
+               data = '\n'.join([' '.join(l.split()) for l in data.split('\n')]) # remove all whitespace
+               
+               string_ls = []
+               
+               #search = 'url "'
+               search = '"'
+               
+               ok = True
+               last_i = 0
+               while ok:
+                       ok = False
+                       i = data.find(search, last_i)
+                       if i != -1:
+                               
+                               start = i + len(search) # first char after end of search
+                               end = data.find('"', start)
+                               if end != -1:
+                                       item = data[start:end]
+                                       string_ls.append( item )
+                                       data = data[:start] + data[end:]
+                                       ok = True # keep looking
+                                       
+                                       last_i = (end - len(item)) + 1
+                                       # print last_i, item, '|' + data[last_i] + '|'
+               
+       # done with messy extracting strings part
+       
+       
+       # Bad, dont take strings into account
+       '''
+       data = data.replace('#', '\n#')
+       data = '\n'.join([ll for l in data.split('\n') for ll in (l.strip(),) if not ll.startswith('#')]) # remove all whitespace
+       '''
+       data = data.replace('{', '\n{\n')
+       data = data.replace('}', '\n}\n')
+       data = data.replace('[', '\n[\n')
+       data = data.replace(']', '\n]\n')
+       data = data.replace(',', ' , ') # make sure comma's seperate
+       
+       if EXTRACT_STRINGS:
+               # add strings back in 
+               
+               search = '"' # fill in these empty strings
+               
+               ok = True
+               last_i = 0
+               while ok:
+                       ok = False
+                       i = data.find(search + '"', last_i)
+                       # print i
+                       if i != -1:
+                               start = i + len(search) # first char after end of search
+                               item = string_ls.pop(0)
+                               # print item
+                               data = data[:start] + item + data[start:]
+                               
+                               last_i = start + len(item) + 1
+                               
+                               ok = True
+       
+       
+       # More annoying obscure cases where USE or DEF are placed on a newline
+       # data = data.replace('\nDEF ', ' DEF ')
+       # data = data.replace('\nUSE ', ' USE ')
+       
+       data = '\n'.join([' '.join(l.split()) for l in data.split('\n')]) # remove all whitespace
+       
+       # Better to parse the file accounting for multiline arrays
+       '''
+       data = data.replace(',\n', ' , ') # remove line endings with commas
+       data = data.replace(']', '\n]\n') # very very annoying - but some comma's are at the end of the list, must run this again.
+       '''
+       
+       return [l for l in data.split('\n') if l]
+
+NODE_NORMAL = 1 # {}
+NODE_ARRAY = 2 # []
+NODE_REFERENCE = 3 # USE foobar
+# NODE_PROTO = 4 #
+
+lines = []
+
+def getNodePreText(i, words):
+       # print lines[i]
+       use_node = False
+       while len(words) < 5:
+               
+               if i>=len(lines):
+                       break
+                       '''
+               elif lines[i].startswith('PROTO'):
+                       return NODE_PROTO, i+1
+                       '''
+               elif lines[i]=='{':
+                       # words.append(lines[i]) # no need
+                       # print "OK"
+                       return NODE_NORMAL, i+1
+               elif lines[i].count('"') % 2 != 0: # odd number of quotes? - part of a string.
+                       # print 'ISSTRING'
+                       break
+               else:
+                       new_words = lines[i].split()
+                       if 'USE' in new_words:
+                               use_node = True
+                       
+                       words.extend(new_words)
+                       i += 1
+               
+               # Check for USE node - no {
+               # USE #id - should always be on the same line.
+               if use_node:
+                       # print 'LINE', i, words[:words.index('USE')+2]
+                       words[:] = words[:words.index('USE')+2]
+                       if lines[i] == '{' and lines[i+1] == '}':
+                               # USE sometimes has {} after it anyway
+                               i+=2 
+                       return NODE_REFERENCE, i
+               
+       # print "error value!!!", words
+       return 0, -1
+
+def is_nodeline(i, words):
+       
+       if not lines[i][0].isalpha():
+               return 0, 0
+       
+       #if lines[i].startswith('field'):
+       #       return 0, 0
+       
+       # Is this a prototype??
+       if lines[i].startswith('PROTO'):
+               words[:] = lines[i].split()
+               return NODE_NORMAL, i+1 # TODO - assumes the next line is a '[\n', skip that
+       if lines[i].startswith('EXTERNPROTO'):
+               words[:] = lines[i].split()
+               return NODE_ARRAY, i+1 # TODO - assumes the next line is a '[\n', skip that
+       
+       '''
+       proto_type, new_i = is_protoline(i, words, proto_field_defs)
+       if new_i != -1:
+               return proto_type, new_i
+       '''
+       
+       # Simple "var [" type
+       if lines[i+1] == '[':
+               if lines[i].count('"') % 2 == 0:
+                       words[:] = lines[i].split()
+                       return NODE_ARRAY, i+2
+       
+       node_type, new_i = getNodePreText(i, words)
+       
+       if not node_type:
+               if DEBUG: print "not node_type", lines[i]
+               return 0, 0
+       
+       # Ok, we have a { after some values
+       # Check the values are not fields
+       for i, val in enumerate(words):
+               if i != 0 and words[i-1] in ('DEF', 'USE'):
+                       # ignore anything after DEF, it is a ID and can contain any chars.
+                       pass
+               elif val[0].isalpha() and val not in ('TRUE', 'FALSE'):
+                       pass
+               else:
+                       # There is a number in one of the values, therefor we are not a node.
+                       return 0, 0
+       
+       #if node_type==NODE_REFERENCE:
+       #       print words, "REF_!!!!!!!"
+       return node_type, new_i
+
+def is_numline(i):
+       '''
+       Does this line start with a number?
+       '''
+       
+       # Works but too slow.
+       '''
+       l = lines[i]
+       for w in l.split():
+               if w==',':
+                       pass
+               else:
+                       try:
+                               float(w)
+                               return True
+                       
+                       except:
+                               return False
+       
+       return False
+       '''
+       
+       l = lines[i]
+       
+       line_start = 0
+       
+       if l.startswith(', '):
+               line_start += 2
+       
+       line_end = len(l)-1
+       line_end_new = l.find(' ', line_start) # comma's always have a space before them
+       
+       if line_end_new != -1:
+               line_end = line_end_new
+       
+       try:
+               float(l[line_start:line_end]) # works for a float or int
+               return True
+       except:
+               return False
+
+
+class vrmlNode(object):
+       __slots__ = 'id', 'fields', 'proto_node', 'proto_field_defs', 'proto_fields', 'node_type', 'parent', 'children', 'parent', 'array_data', 'reference', 'lineno', 'filename', 'blendObject', 'DEF_NAMESPACE', 'ROUTE_IPO_NAMESPACE', 'PROTO_NAMESPACE', 'x3dNode'
+       def __init__(self, parent, node_type, lineno):
+               self.id = None
+               self.node_type = node_type
+               self.parent = parent
+               self.blendObject = None
+               self.x3dNode = None # for x3d import only
+               if parent:
+                       parent.children.append(self)
+               
+               self.lineno = lineno
+               
+               # This is only set from the root nodes.
+               # Having a filename also denotes a root node
+               self.filename = None
+               self.proto_node = None # proto field definition eg: "field SFColor seatColor .6 .6 .1"
+               
+               # Store in the root node because each inline file needs its own root node and its own namespace
+               self.DEF_NAMESPACE = None 
+               self.ROUTE_IPO_NAMESPACE = None 
+               '''
+               self.FIELD_NAMESPACE = None
+               '''
+               
+               
+               self.PROTO_NAMESPACE = None
+               
+               self.reference = None
+               
+               if node_type==NODE_REFERENCE:
+                       # For references, only the parent and ID are needed
+                       # the reference its self is assigned on parsing
+                       return 
+               
+               self.fields = [] # fields have no order, in some cases rool level values are not unique so dont use a dict
+               
+               self.proto_field_defs = [] # proto field definition eg: "field SFColor seatColor .6 .6 .1"
+               self.proto_fields = [] # proto field usage "diffuseColor IS seatColor"
+               self.children = []
+               self.array_data = [] # use for arrays of data - should only be for NODE_ARRAY types
+               
+       
+       # Only available from the root node
+       '''
+       def getFieldDict(self):
+               if self.FIELD_NAMESPACE != None:
+                       return self.FIELD_NAMESPACE
+               else:
+                       return self.parent.getFieldDict()
+       '''
+       def getProtoDict(self):
+               if self.PROTO_NAMESPACE != None:
+                       return self.PROTO_NAMESPACE
+               else:
+                       return self.parent.getProtoDict()
+       
+       def getDefDict(self):
+               if self.DEF_NAMESPACE != None:
+                       return self.DEF_NAMESPACE
+               else:
+                       return self.parent.getDefDict()
+                       
+       def getRouteIpoDict(self):
+               if self.ROUTE_IPO_NAMESPACE != None:
+                       return self.ROUTE_IPO_NAMESPACE
+               else:
+                       return self.parent.getRouteIpoDict()
+       
+       def setRoot(self, filename):
+               self.filename = filename
+               # self.FIELD_NAMESPACE =                {}
+               self.DEF_NAMESPACE =            {}
+               self.ROUTE_IPO_NAMESPACE =      {}
+               self.PROTO_NAMESPACE =          {}
+       
+       def isRoot(self):
+               if self.filename == None:
+                       return False
+               else:
+                       return True
+       
+       def getFilename(self):
+               if self.filename:
+                       return self.filename
+               elif self.parent:
+                       return self.parent.getFilename()
+               else:
+                       return None
+       
+       def getRealNode(self):
+               if self.reference:
+                       return self.reference
+               else:
+                       return self
+       
+       def getSpec(self):
+               self_real = self.getRealNode()
+               try:
+                       return self_real.id[-1] # its possible this node has no spec
+               except:
+                       return None
+       
+       def findSpecRecursive(self, spec):
+               self_real = self.getRealNode()
+               if spec == self_real.getSpec():
+                       return self
+               
+               for child in self_real.children:
+                       if child.findSpecRecursive(spec):
+                               return child
+               
+               return None
+       
+       def getPrefix(self):
+               if self.id:
+                       return self.id[0]
+               return None
+       
+       def getSpecialTypeName(self, typename):
+               self_real = self.getRealNode()
+               try:            return self_real.id[ list(self_real.id).index(typename)+1 ]
+               except: return None
+       
+       
+       def getDefName(self):
+               return self.getSpecialTypeName('DEF')
+       
+       def getProtoName(self):
+               return self.getSpecialTypeName('PROTO')
+               
+       def getExternprotoName(self):
+               return self.getSpecialTypeName('EXTERNPROTO')
+       
+       def getChildrenBySpec(self, node_spec): # spec could be Transform, Shape, Appearance
+               self_real = self.getRealNode()
+               # using getSpec functions allows us to use the spec of USE children that dont have their spec in their ID
+               if type(node_spec) == str:
+                       return [child for child in self_real.children if child.getSpec()==node_spec]
+               else:
+                       # Check inside a list of optional types
+                       return [child for child in self_real.children if child.getSpec() in node_spec]
+       
+       def getChildBySpec(self, node_spec): # spec could be Transform, Shape, Appearance
+               # Use in cases where there is only ever 1 child of this type
+               ls = self.getChildrenBySpec(node_spec)
+               if ls: return ls[0]
+               else: return None
+       
+       def getChildrenByName(self, node_name): # type could be geometry, children, appearance
+               self_real = self.getRealNode()
+               return [child for child in self_real.children if child.id if child.id[0]==node_name]
+       
+       def getChildByName(self, node_name):
+               self_real = self.getRealNode()
+               for child in self_real.children:
+                       if child.id and child.id[0]==node_name: # and child.id[-1]==node_spec:
+                               return child
+       
+       def getSerialized(self, results, ancestry):
+               '''     Return this node and all its children in a flat list '''
+               ancestry = ancestry[:] # always use a copy
+               
+               # self_real = self.getRealNode()
+               
+               results.append((self, tuple(ancestry)))
+               ancestry.append(self)
+               for child in self.getRealNode().children:
+                       if child not in ancestry:
+                               # We dont want to load proto's, they are only references
+                               # We could enforce this elsewhere
+                               
+                               # Only add this in a very special case
+                               # where the parent of this object is not the real parent
+                               # - In this case we have added the proto as a child to a node instancing it.
+                               # This is a bit arbitary, but its how Proto's are done with this importer.
+                               if child.getProtoName() == None and child.getExternprotoName() == None:
+                                       child.getSerialized(results, ancestry)
+                               else:
+                                       
+                                       if DEBUG: print 'getSerialized() is proto:', child.getProtoName(), child.getExternprotoName(), self.getSpec()
+                                       
+                                       self_spec = self.getSpec()
+                                       
+                                       if child.getProtoName() == self_spec or child.getExternprotoName() == self_spec:
+                                               if DEBUG: "FoundProto!"
+                                               child.getSerialized(results, ancestry)
+                                       
+                                       
+               
+               return results
+               
+       def searchNodeTypeID(self, node_spec, results):
+               self_real = self.getRealNode()
+               # print self.lineno, self.id
+               if self_real.id and self_real.id[-1]==node_spec: # use last element, could also be only element
+                       results.append(self_real)
+               for child in self_real.children:
+                       child.searchNodeTypeID(node_spec, results)
+               return results
+       
+       def getFieldName(self, field, ancestry, AS_CHILD=False):
+               self_real = self.getRealNode() # incase we're an instance
+               
+               for f in self_real.fields:
+                       # print f
+                       if f and f[0] == field:
+                               # print '\tfound field', f
+                               
+                               if len(f)>=3 and f[1] == 'IS': # eg: 'diffuseColor IS legColor'
+                                       field_id = f[2]
+                                       
+                                       # print "\n\n\n\n\n\nFOND IS!!!"
+                                       f_proto_lookup = None
+                                       f_proto_child_lookup = None
+                                       i = len(ancestry)
+                                       while i:
+                                               i -= 1
+                                               node = ancestry[i]
+                                               node = node.getRealNode()
+                                               
+                                               # proto settings are stored in "self.proto_node"
+                                               if node.proto_node: 
+                                                       # Get the default value from the proto, this can be overwridden by the proto instace
+                                                       # 'field SFColor legColor .8 .4 .7'
+                                                       if AS_CHILD:
+                                                               for child in node.proto_node.children:
+                                                                       #if child.id  and  len(child.id) >= 3  and child.id[2]==field_id:
+                                                                       if child.id and ('point' in child.id or 'points' in child.id):
+                                                                               f_proto_child_lookup = child
+                                                       
+                                                       else:
+                                                               for f_def in node.proto_node.proto_field_defs:
+                                                                       if len(f_def) >= 4:
+                                                                               if f_def[0]=='field' and f_def[2]==field_id:
+                                                                                       f_proto_lookup = f_def[3:]
+                                               
+                                               # Node instance, Will be 1 up from the proto-node in the ancestry list. but NOT its parent.
+                                               # This is the setting as defined by the instance, including this setting is optional,
+                                               # and will override the default PROTO value 
+                                               # eg: 'legColor 1 0 0'
+                                               if AS_CHILD:
+                                                       for child in node.children:
+                                                               if child.id and child.id[0]==field_id:
+                                                                       f_proto_child_lookup = child
+                                               else:
+                                                       for f_def in node.fields:
+                                                               if len(f_def) >= 2:
+                                                                       if f_def[0]==field_id:
+                                                                               if DEBUG: print "getFieldName(), found proto", f_def
+                                                                               f_proto_lookup = f_def[1:]
+                                               
+                                       
+                                       if AS_CHILD:
+                                               if f_proto_child_lookup:
+                                                       if DEBUG:
+                                                               print "getFieldName() - AS_CHILD=True, child found"
+                                                               print f_proto_child_lookup
+                                               return f_proto_child_lookup
+                                       else:
+                                               return f_proto_lookup
+                               else:
+                                       if AS_CHILD:
+                                               return None
+                                       else:
+                                               # Not using a proto
+                                               return f[1:]
+                                               
+               # print '\tfield not found', field
+               
+               
+               # See if this is a proto name
+               if AS_CHILD:
+                       child_array = None
+                       for child in self_real.children:
+                               if child.id and len(child.id) == 1 and child.id[0] == field:
+                                       return child
+               
+               return None
+       
+       def getFieldAsInt(self, field, default, ancestry):
+               self_real = self.getRealNode() # incase we're an instance
+               
+               f = self_real.getFieldName(field, ancestry)
+               if f==None:     return default
+               if ',' in f: f = f[:f.index(',')] # strip after the comma
+               
+               if len(f) != 1:
+                       print '\t"%s" wrong length for int conversion for field "%s"' % (f, field)
+                       return default
+               
+               try:
+                       return int(f[0])
+               except:
+                       print '\tvalue "%s" could not be used as an int for field "%s"' % (f[0], field)
+                       return default
+       
+       def getFieldAsFloat(self, field, default, ancestry):
+               self_real = self.getRealNode() # incase we're an instance
+               
+               f = self_real.getFieldName(field, ancestry)
+               if f==None:     return default
+               if ',' in f: f = f[:f.index(',')] # strip after the comma
+               
+               if len(f) != 1:
+                       print '\t"%s" wrong length for float conversion for field "%s"' % (f, field)
+                       return default
+               
+               try:
+                       return float(f[0])
+               except:
+                       print '\tvalue "%s" could not be used as a float for field "%s"' % (f[0], field)
+                       return default
+       
+       def getFieldAsFloatTuple(self, field, default, ancestry):
+               self_real = self.getRealNode() # incase we're an instance
+               
+               f = self_real.getFieldName(field, ancestry)
+               if f==None:     return default
+               # if ',' in f: f = f[:f.index(',')] # strip after the comma
+               
+               if len(f) < 1:
+                       print '"%s" wrong length for float tuple conversion for field "%s"' % (f, field)
+                       return default
+               
+               ret = []
+               for v in f:
+                       if v != ',':
+                               try:            ret.append(float(v))
+                               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
+               # print ret
+               
+               if ret:
+                       return ret
+               if not ret:
+                       print '\tvalue "%s" could not be used as a float tuple for field "%s"' % (f, field)
+                       return default
+       
+       def getFieldAsBool(self, field, default, ancestry):
+               self_real = self.getRealNode() # incase we're an instance
+               
+               f = self_real.getFieldName(field, ancestry)
+               if f==None:     return default
+               if ',' in f: f = f[:f.index(',')] # strip after the comma
+               
+               if len(f) != 1:
+                       print '\t"%s" wrong length for bool conversion for field "%s"' % (f, field)
+                       return default
+               
+               if f[0].upper()=='"TRUE"' or f[0].upper()=='TRUE':
+                       return True
+               elif f[0].upper()=='"FALSE"' or f[0].upper()=='FALSE':
+                       return False
+               else:
+                       print '\t"%s" could not be used as a bool for field "%s"' % (f[1], field)
+                       return default
+       
+       def getFieldAsString(self, field, default, ancestry):
+               self_real = self.getRealNode() # incase we're an instance
+               
+               f = self_real.getFieldName(field, ancestry)
+               if f==None:     return default
+               if len(f) < 1:
+                       print '\t"%s" wrong length for string conversion for field "%s"' % (f, field)
+                       return default
+               
+               if len(f) > 1:
+                       # String may contain spaces
+                       st = ' '.join(f)
+               else:
+                       st = f[0]
+               
+               # X3D HACK 
+               if self.x3dNode: 
+                       return st
+                       
+               if st[0]=='"' and st[-1]=='"':
+                       return st[1:-1]
+               else:
+                       print '\tvalue "%s" could not be used as a string for field "%s"' % (f[0], field)
+                       return default
+       
+       def getFieldAsArray(self, field, group, ancestry):
+               '''
+               For this parser arrays are children
+               '''
+               self_real = self.getRealNode() # incase we're an instance
+               
+               child_array = self_real.getFieldName(field, ancestry, True)
+               
+               #if type(child_array)==list: # happens occasionaly
+               #       array_data = child_array 
+                       
+               if child_array==None:
+                       
+                       # For x3d, should work ok with vrml too
+                       # for x3d arrays are fields, vrml they are nodes, annoying but not tooo bad.
+                       data_split = self.getFieldName(field, ancestry)
+                       if not data_split:
+                               return []
+                       array_data = ' '.join(data_split)
+                       if array_data == None:
+                               return []
+                       
+                       array_data = array_data.replace(',', ' ')
+                       data_split = array_data.split()
+                       try:
+                               array_data = [int(val) for val in data_split]
+                       except:
+                               try:
+                                       array_data = [float(val) for val in data_split]
+                               except:
+                                       print '\tWarning, could not parse array data from field'
+                                       array_data = []
+               else:
+                       # print child_array
+                       # Normal vrml
+                       array_data = child_array.array_data
+                       
+               
+               # print 'array_data', array_data
+               
+               if group==-1 or len(array_data)==0:
+                       return array_data
+               
+               # We want a flat list
+               flat = True
+               for item in array_data:
+                       if type(item) == list:
+                               flat = False
+                               break
+               
+               # make a flat array
+               if flat:
+                       flat_array = array_data # we are alredy flat.
+               else:
+                       flat_array = []
+                       
+                       def extend_flat(ls):
+                               for item in ls:
+                                       if type(item)==list:    extend_flat(item)
+                                       else:                                   flat_array.append(item)
+                       
+                       extend_flat(array_data)
+               
+               
+               # We requested a flat array
+               if group == 0:
+                       return flat_array
+               
+               new_array = []
+               sub_array = []
+               
+               for item in flat_array:
+                       sub_array.append(item)
+                       if len(sub_array)==group:
+                               new_array.append(sub_array)
+                               sub_array = []
+               
+               if sub_array:
+                       print '\twarning, array was not aligned to requested grouping', group, 'remaining value', sub_array
+               
+               return new_array
+       
+       def getFieldAsStringArray(self, field, ancestry):
+               '''
+               Get a list of strings
+               '''
+               self_real = self.getRealNode() # incase we're an instance
+               
+               child_array = None
+               for child in self_real.children:
+                       if child.id and len(child.id) == 1 and child.id[0] == field:
+                               child_array = child
+                               break
+               if not child_array:
+                       return []
+               
+               # each string gets its own list, remove ""'s
+               try:
+                       new_array = [f[0][1:-1] for f in child_array.fields]
+               except:
+                       print '\twarning, string array could not be made'
+                       new_array = []
+               
+               return new_array
+       
+       
+       def getLevel(self):
+               # Ignore self_real
+               level = 0
+               p = self.parent
+               while p:
+                       level +=1
+                       p = p.parent
+                       if not p: break
+                       
+               return level
+       
+       def __repr__(self):
+               level = self.getLevel()
+               ind = '  ' * level
+               if self.node_type==NODE_REFERENCE:
+                       brackets = ''
+               elif self.node_type==NODE_NORMAL:
+                       brackets = '{}'
+               else:
+                       brackets = '[]'
+               
+               if brackets:
+                       text = ind + brackets[0] + '\n'
+               else:
+                       text = ''
+               
+               text += ind + 'ID: ' + str(self.id) + ' ' + str(level) + (' lineno %d\n' % self.lineno)
+               
+               if self.node_type==NODE_REFERENCE:
+                       text += ind + "(reference node)\n"
+                       return text
+               
+               if self.proto_node:
+                       text += ind + 'PROTO NODE...\n'
+                       text += str(self.proto_node)
+                       text += ind + 'PROTO NODE_DONE\n'
+               
+               text += ind + 'FIELDS:' + str(len(self.fields)) + '\n'
+               
+               for i,item in enumerate(self.fields):
+                       text += ind + 'FIELD:\n'
+                       text += ind + str(item) +'\n'
+
+               text += ind + 'PROTO_FIELD_DEFS:' + str(len(self.proto_field_defs)) + '\n'
+               
+               for i,item in enumerate(self.proto_field_defs):
+                       text += ind + 'PROTO_FIELD:\n'
+                       text += ind + str(item) +'\n'
+
+               text += ind + 'ARRAY: ' + str(len(self.array_data)) + ' ' + str(self.array_data) + '\n'
+               #text += ind + 'ARRAY: ' + str(len(self.array_data)) + '[...] \n'
+               
+               text += ind + 'CHILDREN: ' + str(len(self.children)) + '\n'
+               for i, child in enumerate(self.children):
+                       text += ind + ('CHILD%d:\n' % i)
+                       text += str(child)
+               
+               text += '\n' + ind + brackets[1]
+               
+               return text
+       
+       def parse(self, i, IS_PROTO_DATA=False):
+               new_i = self.__parse(i, IS_PROTO_DATA)
+               
+               # print self.id, self.getFilename()
+               
+               # Check if this node was an inline or externproto
+               
+               url_ls = []
+               
+               if self.node_type == NODE_NORMAL and self.getSpec() == 'Inline':
+                       ancestry = [] # Warning! - PROTO's using this wont work at all.
+                       url = self.getFieldAsString('url', None, ancestry)
+                       if url:
+                               url_ls = [(url, None)]
+                       del ancestry
+               
+               elif self.getExternprotoName():
+                       # externproto
+                       url_ls = []
+                       for f in self.fields:
+                               
+                               if type(f)==str:
+                                       f = [f]
+                               
+                               for ff in f:
+                                       for f_split in ff.split('"'):
+                                               # print f_split
+                                               # "someextern.vrml#SomeID"
+                                               if '#' in f_split:
+                                                       
+                                                       f_split, f_split_id = f_split.split('#') # there should only be 1 # anyway
+                                                       
+                                                       url_ls.append( (f_split, f_split_id) )
+                                               else:
+                                                       url_ls.append( (f_split, None) )
+               
+               
+               # Was either an Inline or an EXTERNPROTO
+               if url_ls:
+                       
+                       # print url_ls
+                       
+                       for url, extern_key in url_ls:
+                               print url
+                               urls = []
+                               urls.append( url )
+                               urls.append( BPySys.caseInsensitivePath(urls[-1]) )
+                               
+                               urls.append( dirName(self.getFilename()) + url )
+                               urls.append( BPySys.caseInsensitivePath(urls[-1]) )
+                               
+                               urls.append( dirName(self.getFilename()) + baseName(url) )
+                               urls.append( BPySys.caseInsensitivePath(urls[-1]) )
+                               
+                               try:
+                                       url = [url for url in urls if exists(url)][0]
+                                       url_found = True
+                               except:
+                                       url_found = False
+                               
+                               if not url_found:
+                                       print '\tWarning: Inline URL could not be found:', url
+                               else:
+                                       if url==self.getFilename(): 
+                                               print '\tWarning: cant Inline yourself recursively:', url
+                                       else:
+                                               
+                                               try:
+                                                       data = gzipOpen(url)
+                                               except:
+                                                       print '\tWarning: cant open the file:', url
+                                                       data = None
+                                               
+                                               if data:
+                                                       # Tricky - inline another VRML
+                                                       print '\tLoading Inline:"%s"...' % url
+                                                       
+                                                       # Watch it! - backup lines
+                                                       lines_old = lines[:]
+                                                       
+                                                       
+                                                       lines[:] = vrmlFormat( data )
+                                                       
+                                                       lines.insert(0, '{')
+                                                       lines.insert(0, 'root_node____')
+                                                       lines.append('}')
+                                                       '''
+                                                       ff = open('/tmp/test.txt', 'w')
+                                                       ff.writelines([l+'\n' for l in lines])
+                                                       '''
+                                                       
+                                                       child = vrmlNode(self, NODE_NORMAL, -1)
+                                                       child.setRoot(url) # initialized dicts
+                                                       child.parse(0)
+                                                       
+                                                       # if self.getExternprotoName():
+                                                       if self.getExternprotoName():
+                                                               if not extern_key: # if none is spesified - use the name
+                                                                       extern_key = self.getSpec()
+                                                               
+                                                               if extern_key:
+                                                                       
+                                                                       self.children.remove(child)
+                                                                       child.parent = None
+                                                                       
+                                                                       extern_child = child.findSpecRecursive(extern_key)
+                                                                       
+                                                                       if extern_child:
+                                                                               self.children.append(extern_child)
+                                                                               extern_child.parent = self
+                                                                               
+                                                                               if DEBUG: print "\tEXTERNPROTO ID found!:", extern_key
+                                                                       else:
+                                                                               print "\tEXTERNPROTO ID not found!:", extern_key
+                                                                       
+                                                       # Watch it! - restore lines
+                                                       lines[:] = lines_old
+               
+               return new_i
+       
+       def __parse(self, i, IS_PROTO_DATA=False):
+               '''
+               print 'parsing at', i,
+               print i, self.id, self.lineno
+               '''
+               l = lines[i]
+               
+               if l=='[':
+                       # An anonymous list
+                       self.id = None
+                       i+=1
+               else:
+                       words = []
+                       
+                       node_type, new_i = is_nodeline(i, words)
+                       if not node_type: # fail for parsing new node.
+                               print "Failed to parse new node"
+                               raise ValueError
+                       
+                       if self.node_type==NODE_REFERENCE:
+                               # Only assign the reference and quit
+                               key = words[words.index('USE')+1]
+                               self.id = (words[0],)
+                               
+                               self.reference = self.getDefDict()[key]
+                               return new_i
+                       
+                       self.id = tuple(words)
+                       
+                       # fill in DEF/USE
+                       key = self.getDefName()
+                       if key != None:
+                               self.getDefDict()[ key ] = self
+                       
+                       key = self.getProtoName()
+                       if not key:     key = self.getExternprotoName()
+                       
+                       proto_dict = self.getProtoDict()
+                       if key != None:
+                               proto_dict[ key ] = self
+                               
+                               # Parse the proto nodes fields
+                               self.proto_node = vrmlNode(self, NODE_ARRAY, new_i)
+                               new_i = self.proto_node.parse(new_i)
+                               
+                               self.children.remove(self.proto_node)
+                               
+                               # print self.proto_node
+                               
+                               new_i += 1 # skip past the {
+                               
+                       
+                       else: # If we're a proto instance, add the proto node as our child.
+                               spec = self.getSpec()
+                               try:
+                                       self.children.append( proto_dict[spec] )
+                                       #pass
+                               except:
+                                       pass
+                               
+                               del spec
+                       
+                       del proto_dict, key
+                       
+                       i = new_i
+               
+               # print self.id
+               ok = True
+               while ok:
+                       if i>=len(lines):
+                               return len(lines)-1
+                       
+                       l = lines[i]
+                       # print '\tDEBUG:', i, self.node_type, l
+                       if l=='':
+                               i+=1
+                               continue 
+                       
+                       if l=='}':
+                               if self.node_type != NODE_NORMAL: # also ends proto nodes, we may want a type for these too.
+                                       print 'wrong node ending, expected an } ' + str(i) + ' ' + str(self.node_type)
+                                       if DEBUG:
+                                               raise ValueError
+                               ### print "returning", i
+                               return i+1
+                       if l==']':
+                               if self.node_type != NODE_ARRAY:
+                                       print 'wrong node ending, expected a ] ' + str(i) + ' ' + str(self.node_type)
+                                       if DEBUG:
+                                               raise ValueError
+                               ### print "returning", i
+                               return i+1
+                               
+                       node_type, new_i = is_nodeline(i, [])
+                       if node_type: # check text\n{
+                               child = vrmlNode(self, node_type, i)
+                               i = child.parse(i)
+                               
+                       elif l=='[': # some files have these anonymous lists
+                               child = vrmlNode(self, NODE_ARRAY, i)
+                               i = child.parse(i)
+                               
+                       elif is_numline(i):
+                               l_split = l.split(',')
+                               
+                               values = None
+                               # See if each item is a float?
+                               
+                               for num_type in (int, float):
+                                       try:
+                                               values = [num_type(v) for v in l_split ]
+                                               break
+                                       except:
+                                               pass
+                                       
+                                       
+                                       try:
+                                               values = [[num_type(v) for v in segment.split()] for segment in l_split ]
+                                               break
+                                       except:
+                                               pass
+                               
+                               if values == None: # dont parse
+                                       values = l_split
+                               
+                               # This should not extend over multiple lines however it is possible
+                               # print self.array_data
+                               if values:
+                                       self.array_data.extend( values )
+                               i+=1
+                       else:
+                               words = l.split()
+                               if len(words) > 2 and words[1] == 'USE':
+                                       vrmlNode(self, NODE_REFERENCE, i)
+                               else:
+                                       
+                                       # print "FIELD", i, l
+                                       # 
+                                       #words = l.split()
+                                       ### print '\t\ttag', i
+                                       # this is a tag/
+                                       # print words, i, l
+                                       value = l
+                                       # print i
+                                       # javastrips can exist as values.
+                                       quote_count = l.count('"')
+                                       if quote_count % 2: # odd number?
+                                               # print 'MULTILINE'
+                                               while 1:
+                                                       i+=1
+                                                       l = lines[i]
+                                                       quote_count = l.count('"')
+                                                       if quote_count % 2: # odd number?
+                                                               value += '\n'+ l[:l.rfind('"')]
+                                                               break # assume
+                                                       else:
+                                                               value += '\n'+ l
+                                       
+                                       value_all = value.split()
+                                       
+                                       def iskey(k):
+                                               if k[0] != '"' and k[0].isalpha() and k.upper() not in ('TRUE', 'FALSE'):
+                                                       return True
+                                               return False
+                                       
+                                       def split_fields(value):
+                                               '''
+                                               key 0.0 otherkey 1,2,3 opt1 opt1 0.0
+                                                       -> [key 0.0], [otherkey 1,2,3], [opt1 opt1 0.0]
+                                               '''
+                                               field_list = []
+                                               field_context = []
+                                               
+                                               for j in xrange(len(value)):
+                                                       if iskey(value[j]):
+                                                               if field_context:
+                                                                       # this IS a key but the previous value was not a key, ot it was a defined field.
+                                                                       if (not iskey(field_context[-1])) or ((len(field_context)==3 and field_context[1]=='IS')):
+                                                                               field_list.append(field_context)
+                                                                               
+                                                                               field_context = [value[j]]
+                                                                       else:
+                                                                               # The last item was not a value, multiple keys are needed in some cases.
+                                                                               field_context.append(value[j])
+                                                               else:
+                                                                       # Is empty, just add this on
+                                                                       field_context.append(value[j])
+                                                       else:
+                                                               # Add a value to the list
+                                                               field_context.append(value[j])
+                                               
+                                               if field_context:
+                                                       field_list.append(field_context)
+                                               
+                                               return field_list
+                                       
+                                       
+                                       for value in split_fields(value_all):
+                                               # Split 
+                                               
+                                               if value[0]=='field':
+                                                       # field SFFloat creaseAngle 4
+                                                       self.proto_field_defs.append(value)
+                                               else:
+                                                       self.fields.append(value)
+                               i+=1
+
+def gzipOpen(path):
+       try: import gzip
+       except: gzip = None
+       
+       data = None
+       if gzip:
+               try: data = gzip.open(path, 'r').read()
+               except: pass
+       else:
+               print '\tNote, gzip module could not be imported, compressed files will fail to load'
+       
+       if data==None:
+               try:    data = open(path, 'rU').read()
+               except: pass
+       
+       return data
+
+def vrml_parse(path):
+       '''
+       Sets up the root node and returns it so load_web3d() can deal with the blender side of things.
+       Return root (vrmlNode, '') or (None, 'Error String')
+       '''
+       data = gzipOpen(path)
+       
+       if data==None:
+               return None, 'Failed to open file: ' + path
+       
+       # Stripped above
+       lines[:] = vrmlFormat( data )
+       
+       lines.insert(0, '{')
+       lines.insert(0, 'dymmy_node')
+       lines.append('}')
+       # Use for testing our parsed output, so we can check on line numbers.
+       
+       '''
+       ff = open('/tmp/test.txt', 'w')
+       ff.writelines([l+'\n' for l in lines])
+       ff.close()
+       '''
+       
+       # Now evaluate it
+       node_type, new_i = is_nodeline(0, [])
+       if not node_type:
+               return None, 'Error: VRML file has no starting Node'
+       
+       # Trick to make sure we get all root nodes.
+       lines.insert(0, '{')
+       lines.insert(0, 'root_node____') # important the name starts with an ascii char
+       lines.append('}')
+       
+       root = vrmlNode(None, NODE_NORMAL, -1)
+       root.setRoot(path) # we need to set the root so we have a namespace and know the path incase of inlineing
+       
+       # Parse recursively
+       root.parse(0)
+       
+       # This prints a load of text
+       if DEBUG:
+               print root
+       
+       return root, ''
+
+
+# ====================== END VRML 
+
+
+
+# ====================== X3d Support
+
+# Sane as vrml but replace the parser
+class x3dNode(vrmlNode):
+       def __init__(self, parent, node_type, x3dNode):
+               vrmlNode.__init__(self, parent, node_type, -1)
+               self.x3dNode = x3dNode
+               
+       def parse(self, IS_PROTO_DATA=False):
+               # print self.x3dNode.tagName
+               
+               define = self.x3dNode.getAttributeNode('DEF')
+               if define:
+                       self.getDefDict()[define.value] = self
+               else:
+                       use = self.x3dNode.getAttributeNode('USE')
+                       if use:
+                               try:
+                                       self.reference = self.getDefDict()[use.value]
+                                       self.node_type = NODE_REFERENCE
+                               except:
+                                       print '\tWarning: reference', use.value, 'not found'
+                                       self.parent.children.remove(self)
+                               
+                               return
+               
+               for x3dChildNode in self.x3dNode.childNodes:
+                       if x3dChildNode.nodeType in (x3dChildNode.TEXT_NODE, x3dChildNode.COMMENT_NODE, x3dChildNode.CDATA_SECTION_NODE):
+                               continue
+                       
+                       node_type = NODE_NORMAL
+                       # print x3dChildNode, dir(x3dChildNode)
+                       if x3dChildNode.getAttributeNode('USE'):
+                               node_type = NODE_REFERENCE
+                       
+                       child = x3dNode(self, node_type, x3dChildNode)
+                       child.parse()
+               
+               # TODO - x3d Inline
+               
+       def getSpec(self):
+               return self.x3dNode.tagName # should match vrml spec
+       
+       def getDefName(self):
+               data = self.x3dNode.getAttributeNode('DEF')
+               if data: data.value
+               return None
+       
+       # Other funcs operate from vrml, but this means we can wrap XML fields, still use nice utility funcs
+       # getFieldAsArray getFieldAsBool etc
+       def getFieldName(self, field, ancestry, AS_CHILD=False):
+               # ancestry and AS_CHILD are ignored, only used for VRML now
+               
+               self_real = self.getRealNode() # incase we're an instance
+               field_xml = self.x3dNode.getAttributeNode(field)
+               if field_xml:
+                       value = field_xml.value
+                       
+                       # We may want to edit. for x3d spesific stuff
+                       # Sucks a bit to return the field name in the list but vrml excepts this :/
+                       return value.split()
+               else:
+                       return None
+
+def x3d_parse(path):
+       '''
+       Sets up the root node and returns it so load_web3d() can deal with the blender side of things.
+       Return root (x3dNode, '') or (None, 'Error String')
+       '''
+       
+       try:
+               import xml.dom.minidom
+       except:
+               return None, 'Error, import XML parsing module (xml.dom.minidom) failed, install python'
+       
+       '''
+       try:    doc = xml.dom.minidom.parse(path)
+       except: return None, 'Could not parse this X3D file, XML error'
+       '''
+       
+       # Could add a try/except here, but a console error is more useful.
+       data = gzipOpen(path)
+       
+       if data==None:
+               return None, 'Failed to open file: ' + path
+       
+       doc = xml.dom.minidom.parseString(data)
+       
+       
+       try:
+               x3dnode = doc.getElementsByTagName('X3D')[0]
+       except:
+               return None, 'Not a valid x3d document, cannot import'
+       
+       root = x3dNode(None, NODE_NORMAL, x3dnode)
+       root.setRoot(path) # so images and Inline's we load have a relative path
+       root.parse()
+       
+       return root, ''
+
+
+
+## f = open('/_Cylinder.wrl', 'r')
+# f = open('/fe/wrl/Vrml/EGS/TOUCHSN.WRL', 'r')
+# vrml_parse('/fe/wrl/Vrml/EGS/TOUCHSN.WRL')
+#vrml_parse('/fe/wrl/Vrml/EGS/SCRIPT.WRL')
+'''
+
+import os
+files = os.popen('find /fe/wrl -iname "*.wrl"').readlines()
+files.sort()
+tot = len(files)
+for i, f in enumerate(files):
+       #if i < 801:
+       #       continue
+       
+       f = f.strip()
+       print f, i, tot
+       vrml_parse(f)
+'''
+
+# NO BLENDER CODE ABOVE THIS LINE.
+# -----------------------------------------------------------------------------------
+import bpy
+import BPyImage
+import BPySys
+reload(BPySys)
+reload(BPyImage)
+import Blender
+from Blender import Texture, Material, Mathutils, Mesh, Types, Window
+from Blender.Mathutils import TranslationMatrix
+from Blender.Mathutils import RotationMatrix
+from Blender.Mathutils import Vector
+from Blender.Mathutils import Matrix
+
+RAD_TO_DEG = 57.29578
+
+GLOBALS = {'CIRCLE_DETAIL':16}
+
+def translateRotation(rot):
+       '''     axis, angle     '''
+       return RotationMatrix(rot[3]*RAD_TO_DEG, 4, 'r', Vector(rot[:3]))
+
+def translateScale(sca):
+       mat = Matrix() # 4x4 default
+       mat[0][0] = sca[0]
+       mat[1][1] = sca[1]
+       mat[2][2] = sca[2]
+       return mat
+
+def translateTransform(node, ancestry):
+       cent =          node.getFieldAsFloatTuple('center', None, ancestry) # (0.0, 0.0, 0.0)
+       rot =           node.getFieldAsFloatTuple('rotation', None, ancestry) # (0.0, 0.0, 1.0, 0.0)
+       sca =           node.getFieldAsFloatTuple('scale', None, ancestry) # (1.0, 1.0, 1.0)
+       scaori =        node.getFieldAsFloatTuple('scaleOrientation', None, ancestry) # (0.0, 0.0, 1.0, 0.0)
+       tx =            node.getFieldAsFloatTuple('translation', None, ancestry) # (0.0, 0.0, 0.0)
+       
+       if cent:
+               cent_mat = TranslationMatrix(Vector(cent)).resize4x4()
+               cent_imat = cent_mat.copy().invert()
+       else:
+               cent_mat = cent_imat = None
+       
+       if rot:         rot_mat = translateRotation(rot)
+       else:           rot_mat = None
+       
+       if sca:         sca_mat = translateScale(sca)
+       else:           sca_mat = None
+       
+       if scaori:
+               scaori_mat = translateRotation(scaori)
+               scaori_imat = scaori_mat.copy().invert()
+       else:
+               scaori_mat = scaori_imat = None
+       
+       if tx:          tx_mat = TranslationMatrix(Vector(tx)).resize4x4()
+       else:           tx_mat = None
+       
+       new_mat = Matrix()
+       
+       mats = [tx_mat, cent_mat, rot_mat, scaori_mat, sca_mat, scaori_imat, cent_imat]
+       for mtx in mats:
+               if mtx:
+                       new_mat = mtx * new_mat
+       
+       return new_mat
+
+def translateTexTransform(node, ancestry):
+       cent =          node.getFieldAsFloatTuple('center', None, ancestry) # (0.0, 0.0)
+       rot =           node.getFieldAsFloat('rotation', None, ancestry) # 0.0
+       sca =           node.getFieldAsFloatTuple('scale', None, ancestry) # (1.0, 1.0)
+       tx =            node.getFieldAsFloatTuple('translation', None, ancestry) # (0.0, 0.0)
+       
+       
+       if cent:
+               # cent is at a corner by default
+               cent_mat = TranslationMatrix(Vector(cent).resize3D()).resize4x4()
+               cent_imat = cent_mat.copy().invert()
+       else:
+               cent_mat = cent_imat = None
+       
+       if rot:         rot_mat = RotationMatrix(rot*RAD_TO_DEG, 4, 'z') # translateRotation(rot)
+       else:           rot_mat = None
+       
+       if sca:         sca_mat = translateScale((sca[0], sca[1], 0.0))
+       else:           sca_mat = None
+       
+       if tx:          tx_mat = TranslationMatrix(Vector(tx).resize3D()).resize4x4()
+       else:           tx_mat = None
+       
+       new_mat = Matrix()
+       
+       # as specified in VRML97 docs
+       mats = [cent_imat, sca_mat, rot_mat, cent_mat, tx_mat]
+
+       for mtx in mats:
+               if mtx:
+                       new_mat = mtx * new_mat
+       
+       return new_mat
+
+
+
+def getFinalMatrix(node, mtx, ancestry):
+       
+       transform_nodes = [node_tx for node_tx in ancestry if node_tx.getSpec() == 'Transform']
+       if node.getSpec()=='Transform':
+               transform_nodes.append(node)
+       transform_nodes.reverse()
+       
+       if mtx==None:
+               mtx = Matrix()
+       
+       for node_tx in transform_nodes:
+               mat = translateTransform(node_tx, ancestry)
+               mtx = mtx * mat
+       
+       return mtx
+
+def importMesh_IndexedFaceSet(geom, bpyima, ancestry):
+       # print geom.lineno, geom.id, vrmlNode.DEF_NAMESPACE.keys()
+       
+       ccw =                           geom.getFieldAsBool('ccw', True, ancestry)
+       ifs_colorPerVertex =    geom.getFieldAsBool('colorPerVertex', True, ancestry) # per vertex or per face
+       ifs_normalPerVertex =   geom.getFieldAsBool('normalPerVertex', True, ancestry)
+       
+       # This is odd how point is inside Coordinate
+       
+       # VRML not x3d
+       #coord = geom.getChildByName('coord') # 'Coordinate'
+       
+       coord = geom.getChildBySpec('Coordinate') # works for x3d and vrml
+       
+       if coord:       ifs_points = coord.getFieldAsArray('point', 3, ancestry)
+       else:           coord = []
+       
+       if not coord:
+               print '\tWarnint: IndexedFaceSet has no points'
+               return None, ccw
+       
+       ifs_faces = geom.getFieldAsArray('coordIndex', 0, ancestry)
+       
+       coords_tex = None
+       if ifs_faces: # In rare cases this causes problems - no faces but UVs???
+               
+               # WORKS - VRML ONLY
+               # coords_tex = geom.getChildByName('texCoord')
+               coords_tex = geom.getChildBySpec('TextureCoordinate')
+               
+               if coords_tex:
+                       ifs_texpoints = coords_tex.getFieldAsArray('point', 2, ancestry)
+                       ifs_texfaces = geom.getFieldAsArray('texCoordIndex', 0, ancestry)
+                       
+                       if not ifs_texpoints:
+                               # IF we have no coords, then dont bother
+                               coords_tex = None
+               
+               
+       # WORKS - VRML ONLY
+       # vcolor = geom.getChildByName('color')
+       vcolor = geom.getChildBySpec('Color')
+       vcolor_spot = None # spot color when we dont have an array of colors
+       if vcolor:
+               # float to char
+               ifs_vcol = [(0,0,0)] # EEKADOODLE - vertex start at 1
+               ifs_vcol.extend([[int(c*256) for c in col] for col in vcolor.getFieldAsArray('color', 3, ancestry)])
+               ifs_color_index = geom.getFieldAsArray('colorIndex', 0, ancestry)
+               
+               if not ifs_vcol:
+                       vcolor_spot = [int(c*256) for c in vcolor.getFieldAsFloatTuple('color', [], ancestry)]
+       
+       # Convert faces into somthing blender can use
+       edges = []
+       
+       # All lists are aligned!
+       faces = []
+       faces_uv = [] # if ifs_texfaces is empty then the faces_uv will match faces exactly.
+       faces_orig_index = [] # for ngons, we need to know our original index
+       
+       if coords_tex and ifs_texfaces:
+               do_uvmap = True
+       else:
+               do_uvmap = False
+       
+       # current_face = [0] # pointer anyone
+       
+       def add_face(face, fuvs, orig_index):
+               l = len(face)
+               if l==3 or l==4:
+                       faces.append(face)
+                       # faces_orig_index.append(current_face[0])
+                       if do_uvmap:
+                               faces_uv.append(fuvs)
+                               
+                       faces_orig_index.append(orig_index)
+               elif l==2:                      edges.append(face)
+               elif l>4:
+                       for i in xrange(2, len(face)):
+                               faces.append([face[0], face[i-1], face[i]])
+                               if do_uvmap:
+                                       faces_uv.append([fuvs[0], fuvs[i-1], fuvs[i]])
+                               faces_orig_index.append(orig_index)
+               else:
+                       # faces with 1 verts? pfft!
+                       # still will affect index ordering
+                       pass
+       
+       face = []
+       fuvs = []
+       orig_index = 0
+       for i, fi in enumerate(ifs_faces):
+               # ifs_texfaces and ifs_faces should be aligned
+               if fi != -1:
+                       # face.append(int(fi)) # in rare cases this is a float
+                       # EEKADOODLE!!!
+                       # Annoyance where faces that have a zero index vert get rotated. This will then mess up UVs and VColors
+                       face.append(int(fi)+1) # in rare cases this is a float, +1 because of stupid EEKADOODLE :/
+                       
+                       if do_uvmap:
+                               if i >= len(ifs_texfaces):
+                                       print '\tWarning: UV Texface index out of range'
+                                       fuvs.append(ifs_texfaces[0])
+                               else:
+                                       fuvs.append(ifs_texfaces[i])
+               else:
+                       add_face(face, fuvs, orig_index)
+                       face = []
+                       if do_uvmap:
+                               fuvs = []
+                       orig_index += 1
+       
+       add_face(face, fuvs, orig_index)
+       del add_face # dont need this func anymore
+       
+       bpymesh = bpy.data.meshes.new()
+       
+       bpymesh.verts.extend([(0,0,0)]) # EEKADOODLE
+       bpymesh.verts.extend(ifs_points)
+       
+       # print len(ifs_points), faces, edges, ngons
+       
+       try:
+               bpymesh.faces.extend(faces, smooth=True, ignoreDups=True)
+       except KeyError:
+               print "one or more vert indicies out of range. corrupt file?"
+               #for f in faces:
+               #       bpymesh.faces.extend(faces, smooth=True)
+       
+       bpymesh.calcNormals()
+       
+       if len(bpymesh.faces) != len(faces):
+               print '\tWarning: adding faces did not work! file is invalid, not adding UVs or vcolors'
+               return bpymesh, ccw
+       
+       # Apply UVs if we have them
+       if not do_uvmap:
+               faces_uv = faces # fallback, we didnt need a uvmap in the first place, fallback to the face/vert mapping.
+       if coords_tex:
+               #print ifs_texpoints
+               # print geom
+               bpymesh.faceUV = True
+               for i,f in enumerate(bpymesh.faces):
+                       f.image = bpyima
+                       fuv = faces_uv[i] # uv indicies
+                       for j,uv in enumerate(f.uv):
+                               # print fuv, j, len(ifs_texpoints)
+                               try:
+                                       uv[:] = ifs_texpoints[fuv[j]]
+                               except:
+                                       print '\tWarning: UV Index out of range'
+                                       uv[:] = ifs_texpoints[0]
+       
+       elif bpyima and len(bpymesh.faces):
+               # Oh Bugger! - we cant really use blenders ORCO for for texture space since texspace dosnt rotate.
+               # we have to create VRML's coords as UVs instead.
+               
+               # VRML docs
+               '''
+               If the texCoord field is NULL, a default texture coordinate mapping is calculated using the local
+               coordinate system bounding box of the shape. The longest dimension of the bounding box defines the S coordinates,
+               and the next longest defines the T coordinates. If two or all three dimensions of the bounding box are equal,
+               ties shall be broken by choosing the X, Y, or Z dimension in that order of preference.
+               The value of the S coordinate ranges from 0 to 1, from one end of the bounding box to the other.
+               The T coordinate ranges between 0 and the ratio of the second greatest dimension of the bounding box to the greatest dimension.
+               '''
+               
+               # Note, S,T == U,V
+               # U gets longest, V gets second longest
+               xmin, ymin, zmin = ifs_points[0]
+               xmax, ymax, zmax = ifs_points[0]
+               for co in ifs_points:
+                       x,y,z = co
+                       if x < xmin: xmin = x
+                       if y < ymin: ymin = y
+                       if z < zmin: zmin = z
+                       
+                       if x > xmax: xmax = x
+                       if y > ymax: ymax = y
+                       if z > zmax: zmax = z
+                       
+               xlen = xmax - xmin
+               ylen = ymax - ymin
+               zlen = zmax - zmin
+               
+               depth_min = xmin, ymin, zmin
+               depth_list = [xlen, ylen, zlen]
+               depth_sort = depth_list[:]
+               depth_sort.sort()
+               
+               depth_idx = [depth_list.index(val) for val in depth_sort]
+               
+               axis_u = depth_idx[-1]
+               axis_v = depth_idx[-2] # second longest
+               
+               # Hack, swap these !!! TODO - Why swap??? - it seems to work correctly but should not.
+               # axis_u,axis_v = axis_v,axis_u
+               
+               min_u = depth_min[axis_u]
+               min_v = depth_min[axis_v]
+               depth_u = depth_list[axis_u]
+               depth_v = depth_list[axis_v]
+               
+               depth_list[axis_u]
+               
+               if axis_u == axis_v:
+                       # This should be safe because when 2 axies have the same length, the lower index will be used.
+                       axis_v += 1
+               
+               bpymesh.faceUV = True
+               
+               # HACK !!! - seems to be compatible with Cosmo though.
+               depth_v = depth_u = max(depth_v, depth_u)
+               
+               for f in bpymesh.faces:
+                       f.image = bpyima
+                       fuv = f.uv
+                       
+                       for i,v in enumerate(f):
+                               co = v.co
+                               fuv[i][:] = (co[axis_u]-min_u) / depth_u, (co[axis_v]-min_v) / depth_v
+       
+       # Add vcote 
+       if vcolor:
+               # print ifs_vcol
+               bpymesh.vertexColors = True
+               
+               for f in bpymesh.faces:
+                       fcol = f.col
+                       if ifs_colorPerVertex:
+                               fv = f.verts
+                               for i,c in enumerate(fcol):
+                                       color_index = fv[i].index # color index is vert index
+                                       if ifs_color_index:
+                                               try:
+                                                       color_index = ifs_color_index[color_index]
+                                               except:
+                                                       print '\tWarning: per vertex color index out of range'
+                                                       continue
+                                       
+                                       if color_index < len(ifs_vcol):
+                                               c.r, c.g, c.b = ifs_vcol[color_index]
+                                       else:
+                                               #print '\tWarning: per face color index out of range'
+                                               pass
+                       else:
+                               if vcolor_spot: # use 1 color, when ifs_vcol is []
+                                       for c in fcol:
+                                               c.r, c.g, c.b = vcolor_spot
+                               else:
+                                       color_index = faces_orig_index[f.index] # color index is face index
+                                       #print color_index, ifs_color_index
+                                       if ifs_color_index:
+                                               if color_index <= len(ifs_color_index):
+                                                       print '\tWarning: per face color index out of range'
+                                                       color_index = 0
+                                               else:
+                                                       color_index = ifs_color_index[color_index]
+                                               
+                                       
+                                       col = ifs_vcol[color_index]
+                                       for i,c in enumerate(fcol):
+                                               try:
+                                                       c.r, c.g, c.b = col
+                                               except:
+                                                       pass # incase its not between 0 and 255
+       
+       bpymesh.verts.delete([0,]) # EEKADOODLE
+       
+       return bpymesh, ccw
+
+def importMesh_IndexedLineSet(geom, ancestry):
+       # VRML not x3d
+       #coord = geom.getChildByName('coord') # 'Coordinate'
+       coord = geom.getChildBySpec('Coordinate') # works for x3d and vrml
+       if coord:       points = coord.getFieldAsArray('point', 3, ancestry)
+       else:           points = []
+       
+       if not points:
+               print '\tWarning: IndexedLineSet had no points'
+               return None
+       
+       ils_lines = geom.getFieldAsArray('coordIndex', 0, ancestry)
+       
+       lines = []
+       line = []
+       
+       for il in ils_lines:
+               if il==-1:
+                       lines.append(line)
+                       line = []
+               else:
+                       line.append(int(il))
+       lines.append(line)
+       
+       # vcolor = geom.getChildByName('color') # blender dosnt have per vertex color
+       
+       bpycurve = bpy.data.curves.new('IndexedCurve', 'Curve')
+       bpycurve.setFlag(1)
+       
+       w=t=1
+       
+       curve_index = 0
+       
+       for line in lines:
+               if not line:
+                       continue
+               co = points[line[0]]
+               bpycurve.appendNurb([co[0], co[1], co[2], w, t])
+               bpycurve[curve_index].type= 0 # Poly Line
+               
+               for il in line[1:]:
+                       co = points[il]
+                       bpycurve.appendPoint(curve_index, [co[0], co[1], co[2], w])
+               
+               
+               curve_index += 1
+       
+       return bpycurve
+
+
+def importMesh_PointSet(geom, ancestry):
+       # VRML not x3d
+       #coord = geom.getChildByName('coord') # 'Coordinate'
+       coord = geom.getChildBySpec('Coordinate')  # works for x3d and vrml
+       if coord:       points = coord.getFieldAsArray('point', 3, ancestry)
+       else:           points = []
+       
+       # vcolor = geom.getChildByName('color') # blender dosnt have per vertex color
+       
+       bpymesh = bpy.data.meshes.new()
+       bpymesh.verts.extend(points)
+       bpymesh.calcNormals() # will just be dummy normals
+       return bpymesh
+
+GLOBALS['CIRCLE_DETAIL'] = 12
+
+MATRIX_Z_TO_Y = RotationMatrix(90, 4, 'x')
+
+def importMesh_Sphere(geom, ancestry):
+       # bpymesh = bpy.data.meshes.new()
+       diameter = geom.getFieldAsFloat('radius', 0.5, ancestry) * 2 # * 2 for the diameter
+       bpymesh = Mesh.Primitives.UVsphere(GLOBALS['CIRCLE_DETAIL'], GLOBALS['CIRCLE_DETAIL'], diameter)  
+       bpymesh.transform(MATRIX_Z_TO_Y)
+       return bpymesh
+
+def importMesh_Cylinder(geom, ancestry):
+       # bpymesh = bpy.data.meshes.new()
+       diameter = geom.getFieldAsFloat('radius', 1.0, ancestry) * 2 # * 2 for the diameter
+       height = geom.getFieldAsFloat('height', 2, ancestry)
+       bpymesh = Mesh.Primitives.Cylinder(GLOBALS['CIRCLE_DETAIL'], diameter, height) 
+       bpymesh.transform(MATRIX_Z_TO_Y)
+       
+       # Warning - Rely in the order Blender adds verts
+       # not nice design but wont change soon.
+       
+       bottom = geom.getFieldAsBool('bottom', True, ancestry)
+       side = geom.getFieldAsBool('side', True, ancestry)
+       top = geom.getFieldAsBool('top', True, ancestry)
+       
+       if not top: # last vert is top center of tri fan.
+               bpymesh.verts.delete([(GLOBALS['CIRCLE_DETAIL']+GLOBALS['CIRCLE_DETAIL'])+1])
+       
+       if not bottom: # second last vert is bottom of triangle fan
+               bpymesh.verts.delete([GLOBALS['CIRCLE_DETAIL']+GLOBALS['CIRCLE_DETAIL']])
+       
+       if not side:
+               # remove all quads
+               bpymesh.faces.delete(1, [f for f in bpymesh.faces if len(f)==4])
+       
+       return bpymesh
+
+def importMesh_Cone(geom, ancestry):
+       # bpymesh = bpy.data.meshes.new()
+       diameter = geom.getFieldAsFloat('bottomRadius', 1.0, ancestry) * 2 # * 2 for the diameter
+       height = geom.getFieldAsFloat('height', 2, ancestry)
+       bpymesh = Mesh.Primitives.Cone(GLOBALS['CIRCLE_DETAIL'], diameter, height) 
+       bpymesh.transform(MATRIX_Z_TO_Y)
+       
+       # Warning - Rely in the order Blender adds verts
+       # not nice design but wont change soon.
+       
+       bottom = geom.getFieldAsBool('bottom', True, ancestry)
+       side = geom.getFieldAsBool('side', True, ancestry)
+       
+       if not bottom: # last vert is on the bottom
+               bpymesh.verts.delete([GLOBALS['CIRCLE_DETAIL']+1])
+       if not side: # second last vert is on the pointy bit of the cone
+               bpymesh.verts.delete([GLOBALS['CIRCLE_DETAIL']])
+       
+       return bpymesh
+
+def importMesh_Box(geom, ancestry):
+       # bpymesh = bpy.data.meshes.new()
+       
+       size = geom.getFieldAsFloatTuple('size', (2.0, 2.0, 2.0), ancestry)
+       bpymesh = Mesh.Primitives.Cube(1.0) 
+
+       # Scale the box to the size set
+       scale_mat = Matrix([size[0],0,0], [0, size[1], 0], [0, 0, size[2]])
+       bpymesh.transform(scale_mat.resize4x4())
+       
+       return bpymesh
+
+def importShape(node, ancestry):
+       vrmlname = node.getDefName()
+       if not vrmlname: vrmlname = 'Shape'
+       
+       # works 100% in vrml, but not x3d
+       #appr = node.getChildByName('appearance') # , 'Appearance'
+       #geom = node.getChildByName('geometry') # , 'IndexedFaceSet'
+       
+       # Works in vrml and x3d
+       appr = node.getChildBySpec('Appearance')
+       geom = node.getChildBySpec(['IndexedFaceSet', 'IndexedLineSet', 'PointSet', 'Sphere', 'Box', 'Cylinder', 'Cone'])
+       
+       # For now only import IndexedFaceSet's
+       if geom:
+               bpymat = None
+               bpyima = None
+               texmtx = None
+               
+               depth = 0 # so we can set alpha face flag later
+               
+               if appr:
+                       
+                       #mat = appr.getChildByName('material') # 'Material'
+                       #ima = appr.getChildByName('texture') # , 'ImageTexture'
+                       #if ima and ima.getSpec() != 'ImageTexture':
+                       #       print '\tWarning: texture type "%s" is not supported' % ima.getSpec() 
+                       #       ima = None
+                       # textx = appr.getChildByName('textureTransform')
+                       
+                       mat = appr.getChildBySpec('Material')
+                       ima = appr.getChildBySpec('ImageTexture')
+                       
+                       textx = appr.getChildBySpec('TextureTransform')
+                       
+                       if textx:
+                               texmtx = translateTexTransform(textx, ancestry)
+                       
+
+                       
+                       # print mat, ima
+                       if mat or ima:
+                               
+                               if not mat:
+                                       mat = ima # This is a bit dumb, but just means we use default values for all
+                               
+                               # all values between 0.0 and 1.0, defaults from VRML docs
+                               bpymat = bpy.data.materials.new()
+                               bpymat.amb =            mat.getFieldAsFloat('ambientIntensity', 0.2, ancestry)
+                               bpymat.rgbCol =         mat.getFieldAsFloatTuple('diffuseColor', [0.8, 0.8, 0.8], ancestry)
+                               
+                               # NOTE - blender dosnt support emmisive color
+                               # Store in mirror color and approximate with emit.
+                               emit =                          mat.getFieldAsFloatTuple('emissiveColor', [0.0, 0.0, 0.0], ancestry)
+                               bpymat.mirCol =         emit
+                               bpymat.emit =           (emit[0]+emit[1]+emit[2])/3.0
+                               
+                               bpymat.hard =           int(1+(510*mat.getFieldAsFloat('shininess', 0.2, ancestry))) # 0-1 -> 1-511
+                               bpymat.specCol =        mat.getFieldAsFloatTuple('specularColor', [0.0, 0.0, 0.0], ancestry)
+                               bpymat.alpha =          1.0 - mat.getFieldAsFloat('transparency', 0.0, ancestry)
+                               if bpymat.alpha < 0.999:
+                                       bpymat.mode |= Material.Modes.ZTRANSP
+                       
+                       
+                       if ima:
+                               
+                               ima_url =                       ima.getFieldAsString('url', None, ancestry)
+                               
+                               if ima_url==None:
+                                       try:            ima_url = ima.getFieldAsStringArray('url', ancestry)[0] # in some cases we get a list of images.
+                                       except:         ima_url = None
+                               
+                               if ima_url==None:
+                                       print "\twarning, image with no URL, this is odd"
+                               else:
+                                       bpyima= BPyImage.comprehensiveImageLoad(ima_url, dirName(node.getFilename()), PLACE_HOLDER= False, RECURSIVE= False, CONVERT_CALLBACK= imageConvertCompat)
+                                       if bpyima:
+                                               texture= bpy.data.textures.new()
+                                               texture.setType('Image')
+                                               texture.image = bpyima
+                                               
+                                               # Adds textures for materials (rendering)
+                                               try:    depth = bpyima.depth
+                                               except: depth = -1
+                                               
+                                               if depth == 32:
+                                                       # Image has alpha
+                                                       bpymat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL | Texture.MapTo.ALPHA)
+                                                       texture.setImageFlags('MipMap', 'InterPol', 'UseAlpha')
+                                                       bpymat.mode |= Material.Modes.ZTRANSP
+                                                       bpymat.alpha = 0.0
+                                               else:
+                                                       bpymat.setTexture(0, texture, Texture.TexCo.UV, Texture.MapTo.COL)
+                                                       
+                                               ima_repS =                      ima.getFieldAsBool('repeatS', True, ancestry)
+                                               ima_repT =                      ima.getFieldAsBool('repeatT', True, ancestry)
+                                               
+                                               # To make this work properly we'd need to scale the UV's too, better to ignore th
+                                               # texture.repeat =      max(1, ima_repS * 512), max(1, ima_repT * 512)
+                                               
+                                               if not ima_repS: bpyima.clampX = True
+                                               if not ima_repT: bpyima.clampY = True
+               
+               bpydata = None
+               geom_spec = geom.getSpec()
+               ccw = True
+               if geom_spec == 'IndexedFaceSet':
+                       bpydata, ccw = importMesh_IndexedFaceSet(geom, bpyima, ancestry)
+               elif geom_spec == 'IndexedLineSet':
+                       bpydata = importMesh_IndexedLineSet(geom, ancestry)
+               elif geom_spec == 'PointSet':
+                       bpydata = importMesh_PointSet(geom, ancestry)
+               elif geom_spec == 'Sphere':
+                       bpydata = importMesh_Sphere(geom, ancestry)
+               elif geom_spec == 'Box':
+                       bpydata = importMesh_Box(geom, ancestry)
+               elif geom_spec == 'Cylinder':
+                       bpydata = importMesh_Cylinder(geom, ancestry)
+               elif geom_spec == 'Cone':
+                       bpydata = importMesh_Cone(geom, ancestry)
+               else:
+                       print '\tWarning: unsupported type "%s"' % geom_spec
+                       return
+               
+               if bpydata:
+                       vrmlname = vrmlname + geom_spec
+                       
+                       bpydata.name = vrmlname
+                       
+                       bpyob  = node.blendObject = bpy.data.scenes.active.objects.new(bpydata)
+                       
+                       if type(bpydata) == Types.MeshType:
+                               is_solid =                      geom.getFieldAsBool('solid', True, ancestry)
+                               creaseAngle =           geom.getFieldAsFloat('creaseAngle', None, ancestry)
+                               
+                               if creaseAngle != None:
+                                       bpydata.maxSmoothAngle = 1+int(min(79, creaseAngle * RAD_TO_DEG))
+                                       bpydata.mode |= Mesh.Modes.AUTOSMOOTH
+                               
+                               # Only ever 1 material per shape
+                               if bpymat:      bpydata.materials = [bpymat]
+                               
+                               if bpydata.faceUV:
+                                       
+                                       if depth==32: # set the faces alpha flag?
+                                               transp = Mesh.FaceTranspModes.ALPHA
+                                               for f in bpydata.faces:
+                                                       f.transp = transp
+                               
+                                       if texmtx:
+                                               # Apply texture transform?
+                                               uv_copy = Vector()
+                                               for f in bpydata.faces:
+                                                       for uv in f.uv:
+                                                               uv_copy.x = uv.x
+                                                               uv_copy.y = uv.y
+                                                               
+                                                               uv.x, uv.y = (uv_copy * texmtx)[0:2]
+                               # Done transforming the texture
+                               
+                               
+                               # Must be here and not in IndexedFaceSet because it needs an object for the flip func. Messy :/
+                               if not ccw: bpydata.flipNormals()
+                               
+                               
+                       # else could be a curve for example
+                       
+                       
+                       
+                       # Can transform data or object, better the object so we can instance the data
+                       #bpymesh.transform(getFinalMatrix(node))
+                       bpyob.setMatrix( getFinalMatrix(node, None, ancestry) )
+
+
+def importLamp_PointLight(node, ancestry):
+       vrmlname = node.getDefName()
+       if not vrmlname: vrmlname = 'PointLight'
+       
+       # ambientIntensity = node.getFieldAsFloat('ambientIntensity', 0.0, ancestry) # TODO
+       # attenuation = node.getFieldAsFloatTuple('attenuation', (1.0, 0.0, 0.0), ancestry) # TODO
+       color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry)
+       intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher.
+       location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry)
+       # is_on = node.getFieldAsBool('on', True, ancestry) # TODO
+       radius = node.getFieldAsFloat('radius', 100.0, ancestry)
+       
+       bpylamp = bpy.data.lamps.new()
+       bpylamp.setType('Lamp')
+       bpylamp.energy = intensity
+       bpylamp.dist = radius
+       bpylamp.col = color
+       
+       mtx = TranslationMatrix(Vector(location))
+       
+       return bpylamp, mtx
+
+def importLamp_DirectionalLight(node, ancestry):
+       vrmlname = node.getDefName()
+       if not vrmlname: vrmlname = 'DirectLight'
+       
+       # ambientIntensity = node.getFieldAsFloat('ambientIntensity', 0.0) # TODO
+       color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry)
+       direction = node.getFieldAsFloatTuple('direction', (0.0, 0.0, -1.0), ancestry)
+       intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher.
+       # is_on = node.getFieldAsBool('on', True, ancestry) # TODO
+       
+       bpylamp = bpy.data.lamps.new(vrmlname)
+       bpylamp.setType('Sun')
+       bpylamp.energy = intensity
+       bpylamp.col = color
+       
+       # lamps have their direction as -z, yup
+       mtx = Vector(direction).toTrackQuat('-z', 'y').toMatrix().resize4x4()
+       
+       return bpylamp, mtx
+
+# looks like default values for beamWidth and cutOffAngle were swapped in VRML docs.
+
+def importLamp_SpotLight(node, ancestry):
+       vrmlname = node.getDefName()
+       if not vrmlname: vrmlname = 'SpotLight'
+       
+       # ambientIntensity = geom.getFieldAsFloat('ambientIntensity', 0.0, ancestry) # TODO
+       # attenuation = geom.getFieldAsFloatTuple('attenuation', (1.0, 0.0, 0.0), ancestry) # TODO
+       beamWidth = node.getFieldAsFloat('beamWidth', 1.570796, ancestry) * RAD_TO_DEG # max is documented to be 1.0 but some files have higher.
+       color = node.getFieldAsFloatTuple('color', (1.0, 1.0, 1.0), ancestry)
+       cutOffAngle = node.getFieldAsFloat('cutOffAngle', 0.785398, ancestry) * RAD_TO_DEG # max is documented to be 1.0 but some files have higher.
+       direction = node.getFieldAsFloatTuple('direction', (0.0, 0.0, -1.0), ancestry)
+       intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher.
+       location = node.getFieldAsFloatTuple('location', (0.0, 0.0, 0.0), ancestry)
+       # is_on = node.getFieldAsBool('on', True, ancestry) # TODO
+       radius = node.getFieldAsFloat('radius', 100.0, ancestry)
+       
+       bpylamp = bpy.data.lamps.new(vrmlname)
+       bpylamp.setType('Spot')
+       bpylamp.energy = intensity
+       bpylamp.dist = radius
+       bpylamp.col = color
+       bpylamp.spotSize = cutOffAngle
+       if beamWidth > cutOffAngle:
+               bpylamp.spotBlend = 0.0
+       else:
+               if cutOffAngle==0.0: #@#$%^&*(!!! - this should never happen
+                       bpylamp.spotBlend = 0.5
+               else:
+                       bpylamp.spotBlend = beamWidth / cutOffAngle
+       
+       # Convert 
+       
+       # lamps have their direction as -z, y==up
+       mtx = Vector(direction).toTrackQuat('-z', 'y').toMatrix().resize4x4() * TranslationMatrix(Vector(location))
+       
+       return bpylamp, mtx
+
+
+def importLamp(node, spec, ancestry):
+       if spec=='PointLight':
+               bpylamp,mtx = importLamp_PointLight(node, ancestry)
+       elif spec=='DirectionalLight':
+               bpylamp,mtx = importLamp_DirectionalLight(node, ancestry)
+       elif spec=='SpotLight':
+               bpylamp,mtx = importLamp_SpotLight(node, ancestry)
+       else:
+               print "Error, not a lamp"
+               raise ValueError
+       
+       bpyob = node.blendObject = bpy.data.scenes.active.objects.new(bpylamp)
+       bpyob.setMatrix( getFinalMatrix(node, mtx, ancestry) )
+
+
+def importViewpoint(node, ancestry):
+       name = node.getDefName()
+       if not name: name = 'Viewpoint'
+       
+       fieldOfView = node.getFieldAsFloat('fieldOfView', 0.785398, ancestry) * RAD_TO_DEG # max is documented to be 1.0 but some files have higher.
+       # jump = node.getFieldAsBool('jump', True, ancestry)
+       orientation = node.getFieldAsFloatTuple('orientation', (0.0, 0.0, 1.0, 0.0), ancestry)
+       position = node.getFieldAsFloatTuple('position', (0.0, 0.0, 0.0), ancestry)
+       description = node.getFieldAsString('description', '', ancestry)
+       
+       bpycam = bpy.data.cameras.new(name)
+       
+       bpycam.angle = fieldOfView
+       
+       mtx = translateRotation(orientation) * TranslationMatrix(Vector(position))
+       
+       
+       bpyob = node.blendObject = bpy.data.scenes.active.objects.new(bpycam)
+       bpyob.setMatrix( getFinalMatrix(node, mtx, ancestry) )
+
+
+def importTransform(node, ancestry):
+       name = node.getDefName()
+       if not name: name = 'Transform'
+       
+       bpyob = node.blendObject = bpy.data.scenes.active.objects.new('Empty', name) # , name)
+       bpyob.setMatrix( getFinalMatrix(node, None, ancestry) )
+
+       # so they are not too annoying
+       bpyob.emptyShape= Blender.Object.EmptyShapes.AXES
+       bpyob.drawSize= 0.2
+
+       
+#def importTimeSensor(node):
+
+
+def translatePositionInterpolator(node, ipo, ancestry):
+       key = node.getFieldAsArray('key', 0, ancestry)
+       keyValue = node.getFieldAsArray('keyValue', 3, ancestry)
+       
+       try:
+               loc_x = ipo.addCurve('LocX')
+               loc_y = ipo.addCurve('LocY')
+               loc_z = ipo.addCurve('LocZ')
+       except ValueError:
+               return
+               
+       loc_x.interpolation = loc_y.interpolation = loc_z.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
+       
+       for i, time in enumerate(key):
+               try:            x,y,z = keyValue[i]
+               except: continue
+               
+               loc_x.append((time,x))
+               loc_y.append((time,y))
+               loc_z.append((time,z))
+
+def translateOrientationInterpolator(node, ipo, ancestry):
+       key = node.getFieldAsArray('key', 0, ancestry)
+       keyValue = node.getFieldAsArray('keyValue', 4, ancestry)
+       
+       try:
+               rot_x = ipo.addCurve('RotX')
+               rot_y = ipo.addCurve('RotY')
+               rot_z = ipo.addCurve('RotZ')
+       except ValueError:
+               return
+       
+       rot_x.interpolation = rot_y.interpolation = rot_z.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
+       
+       for i, time in enumerate(key):
+               try:            x,y,z,w = keyValue[i]
+               except: continue
+               
+               mtx = translateRotation((x,y,z,w))
+               eul = mtx.toEuler()
+               rot_x.append((time,eul.x/10.0))
+               rot_y.append((time,eul.y/10.0))
+               rot_z.append((time,eul.z/10.0))
+
+# Untested!
+def translateScalarInterpolator(node, ipo, ancestry):
+       key = node.getFieldAsArray('key', 0, ancestry)
+       keyValue = node.getFieldAsArray('keyValue', 4, ancestry)
+       
+       try:
+               sca_x = ipo.addCurve('ScaleX')
+               sca_y = ipo.addCurve('ScaleY')
+               sca_z = ipo.addCurve('ScaleZ')
+       except ValueError:
+               return
+       
+       sca_x.interpolation = sca_y.interpolation = sca_z.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
+       
+       for i, time in enumerate(key):
+               try:            x,y,z = keyValue[i]
+               except: continue
+               sca_x.append((time,x/10.0))
+               sca_y.append((time,y/10.0))
+               sca_z.append((time,z/10.0))
+
+def translateTimeSensor(node, ipo, ancestry):
+       '''
+       Apply a time sensor to an IPO, VRML has many combinations of loop/start/stop/cycle times
+       to give different results, for now just do the basics
+       '''
+       
+       time_cu = ipo.addCurve('Time')
+       time_cu.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
+       
+       cycleInterval = node.getFieldAsFloat('cycleInterval', None, ancestry)
+       
+       startTime = node.getFieldAsFloat('startTime', 0.0, ancestry)
+       stopTime = node.getFieldAsFloat('stopTime', 250.0, ancestry)
+               
+       if cycleInterval != None:
+               stopTime = startTime+cycleInterval
+       
+       loop = node.getFieldAsBool('loop', False, ancestry)
+       
+       time_cu.append((1+startTime, 0.0))
+       time_cu.append((1+stopTime, 1.0/10.0))# anoying, the UI uses /10
+       
+       
+       if loop:
+               time_cu.extend = Blender.IpoCurve.ExtendTypes.CYCLIC # or - EXTRAP, CYCLIC_EXTRAP, CONST, 
+
+
+def importRoute(node, ancestry):
+       '''
+       Animation route only at the moment
+       '''
+       
+       if not hasattr(node, 'fields'):
+               return
+       
+       routeIpoDict = node.getRouteIpoDict()
+       
+       def getIpo(id):
+               try: ipo =      routeIpoDict[id]
+               except: ipo = routeIpoDict[id] = bpy.data.ipos.new('web3d_ipo', 'Object')
+               return ipo
+       
+       # for getting definitions
+       defDict = node.getDefDict()
+       '''
+       Handles routing nodes to eachother
+       
+ROUTE vpPI.value_changed TO champFly001.set_position
+ROUTE vpOI.value_changed TO champFly001.set_orientation
+ROUTE vpTs.fraction_changed TO vpPI.set_fraction
+ROUTE vpTs.fraction_changed TO vpOI.set_fraction
+ROUTE champFly001.bindTime TO vpTs.set_startTime
+       '''
+       
+       #from_id, from_type = node.id[1].split('.')
+       #to_id, to_type = node.id[3].split('.')
+       
+       #value_changed
+       set_position_node = None
+       set_orientation_node = None
+       time_node = None
+       
+       for field in node.fields:
+               if field and field[0]=='ROUTE':
+                       try:
+                               from_id, from_type = field[1].split('.')
+                               to_id, to_type = field[3].split('.')
+                       except:
+                               print "Warning, invalid ROUTE", field
+                               continue
+                       
+                       if from_type == 'value_changed':
+                               if to_type == 'set_position':
+                                       ipo = getIpo(to_id)
+                                       set_data_from_node = defDict[from_id]
+                                       translatePositionInterpolator(set_data_from_node, ipo, ancestry)
+                               
+                               if to_type in ('set_orientation', 'rotation'):
+                                       ipo = getIpo(to_id)
+                                       set_data_from_node = defDict[from_id]
+                                       translateOrientationInterpolator(set_data_from_node, ipo, ancestry)
+                               
+                               if to_type == 'set_scale':
+                                       ipo = getIpo(to_id)
+                                       set_data_from_node = defDict[from_id]
+                                       translateScalarInterpolator(set_data_from_node, ipo, ancestry)
+                               
+                       elif from_type =='bindTime':
+                               ipo = getIpo(from_id)
+                               time_node = defDict[to_id]
+                               translateTimeSensor(time_node, ipo, ancestry)
+                       
+               
+
+
+def load_web3d(path, PREF_FLAT=False, PREF_CIRCLE_DIV=16, HELPER_FUNC = None):
+       
+       # Used when adding blender primitives
+       GLOBALS['CIRCLE_DETAIL'] = PREF_CIRCLE_DIV
+       
+       #root_node = vrml_parse('/_Cylinder.wrl')
+       if path.lower().endswith('.x3d'):
+               root_node, msg = x3d_parse(path)
+       else:
+               root_node, msg = vrml_parse(path)
+       
+       if not root_node:
+               if Blender.mode == 'background':
+                       print msg
+               else:
+                       Blender.Draw.PupMenu(msg)
+               return
+       
+       
+       # fill with tuples - (node, [parents-parent, parent])
+       all_nodes = root_node.getSerialized([], [])
+       
+       for node, ancestry in all_nodes:
+               #if 'castle.wrl' not in node.getFilename():
+               #       continue
+               
+               spec = node.getSpec()
+               '''
+               prefix = node.getPrefix()
+               if prefix=='PROTO':
+                       pass
+               else
+               '''
+               if HELPER_FUNC and HELPER_FUNC(node, ancestry):
+                       # Note, include this function so the VRML/X3D importer can be extended
+                       # by an external script. - gets first pick 
+                       pass
+               if spec=='Shape':
+                       importShape(node, ancestry)
+               elif spec in ('PointLight', 'DirectionalLight', 'SpotLight'):
+                       importLamp(node, spec, ancestry)
+               elif spec=='Viewpoint':
+                       importViewpoint(node, ancestry)
+               elif spec=='Transform':
+                       # Only use transform nodes when we are not importing a flat object hierarchy
+                       if PREF_FLAT==False:
+                               importTransform(node, ancestry)
+                       '''
+               # These are delt with later within importRoute
+               elif spec=='PositionInterpolator':
+                       ipo = bpy.data.ipos.new('web3d_ipo', 'Object')
+                       translatePositionInterpolator(node, ipo)
+                       '''
+       
+       
+       
+       # After we import all nodes, route events - anim paths
+       for node, ancestry in all_nodes:
+               importRoute(node, ancestry)
+       
+       for node, ancestry in all_nodes:
+               if node.isRoot():
+                       # we know that all nodes referenced from will be in 
+                       # routeIpoDict so no need to run node.getDefDict() for every node.
+                       routeIpoDict = node.getRouteIpoDict()
+                       defDict = node.getDefDict()
+                       
+                       for key, ipo in routeIpoDict.iteritems():
+                               
+                               # Assign anim curves
+                               node = defDict[key]
+                               if node.blendObject==None: # Add an object if we need one for animation
+                                       node.blendObject = bpy.data.scenes.active.objects.new('Empty', 'AnimOb') # , name)
+                                       
+                               node.blendObject.setIpo(ipo)
+
+       
+       
+       # Add in hierarchy
+       if PREF_FLAT==False:
+               child_dict = {}
+               for node, ancestry in all_nodes:
+                       if node.blendObject:
+                               blendObject = None
+                               
+                               # Get the last parent
+                               i = len(ancestry)
+                               while i:
+                                       i-=1
+                                       blendObject = ancestry[i].blendObject
+                                       if blendObject:
+                                               break
+                               
+                               if blendObject:
+                                       # Parent Slow, - 1 liner but works
+                                       # blendObject.makeParent([node.blendObject], 0, 1)
+                                       
+                                       # Parent FAST
+                                       try:    child_dict[blendObject].append(node.blendObject)
+                                       except: child_dict[blendObject] = [node.blendObject]
+               
+               # Parent FAST
+               for parent, children in child_dict.iteritems():
+                       parent.makeParent(children, 0, 1)
+               
+               # update deps
+               bpy.data.scenes.active.update(1)
+               del child_dict
+
+
+def load_ui(path):
+       Draw = Blender.Draw
+       PREF_HIERARCHY= Draw.Create(0)
+       PREF_CIRCLE_DIV= Draw.Create(16)
+       
+       # Get USER Options
+       pup_block= [\
+       'Import...',\
+       ('Hierarchy', PREF_HIERARCHY, 'Import transform nodes as empties to create a parent/child hierarchy'),\
+       ('Circle Div:', PREF_CIRCLE_DIV, 3, 128, 'Number of divisions to use for circular primitives')
+       ]
+       
+       if not Draw.PupBlock('Import X3D/VRML...', pup_block):
+               return
+       
+       Window.WaitCursor(1)
+       
+       load_web3d(path,\
+         (not PREF_HIERARCHY.val),\
+         PREF_CIRCLE_DIV.val,\
+       )
+       
+       Window.WaitCursor(0)
+       
+
+if __name__ == '__main__':
+       Window.FileSelector(load_ui, 'Import X3D/VRML97')
+       
+
+# Testing stuff
+
+# load_web3d('/test.x3d')
+# load_web3d('/_Cylinder.x3d')
+
+# Testing below
+# load_web3d('m:\\root\\Desktop\\_Cylinder.wrl')
+# load_web3d('/_Cylinder.wrl')
+# load_web3d('/fe/wrl/Vrml/EGS/BCKGD.WRL')
+
+# load_web3d('/fe/wrl/Vrml/EGS/GRNDPLNE.WRL')
+# load_web3d('/fe/wrl/Vrml/EGS/INDEXFST.WRL')
+# load_web3d('/fe/wrl/panel1c.wrl')
+# load_web3d('/test.wrl')
+# load_web3d('/fe/wrl/dulcimer.wrl')
+# load_web3d('/fe/wrl/rccad/Ju-52.wrl') # Face index out of range
+# load_web3d('/fe/wrl/16lat.wrl') # spotlight
+# load_web3d('/fe/wrl/Vrml/EGS/FOG.WRL') # spotlight
+# load_web3d('/fe/wrl/Vrml/EGS/LOD.WRL') # vcolor per face
+
+# load_web3d('/fe/wrl/new/daybreak_final.wrl') # no faces in mesh, face duplicate error
+# load_web3d('/fe/wrl/new/earth.wrl')
+# load_web3d('/fe/wrl/new/hendrix.ei.dtu.dk/vrml/talairach/fourd/TalaDruryRight.wrl') # define/use fields
+# load_web3d('/fe/wrl/new/imac.wrl') # extrusion and define/use fields, face index is a float somehow
+# load_web3d('/fe/wrl/new/www.igs.net/~mascott/vrml/vrml2/mcastle.wrl') 
+# load_web3d('/fe/wrl/new/www.igs.net/~mascott/vrml/vrml2/tower.wrl') 
+# load_web3d('/fe/wrl/new/www.igs.net/~mascott/vrml/vrml2/temple.wrl') 
+# load_web3d('/fe/wrl/brain.wrl')  # field define test 'a IS b'
+# load_web3d('/fe/wrl/new/coaster.wrl')  # fields that are confusing to read.
+
+# X3D 
+
+# load_web3d('/fe/x3d/www.web3d.org/x3d/content/examples/Basic/StudentProjects/PlayRoom.x3d') # invalid UVs
+
+
+
+def test():
+       import os
+       
+       files = os.popen('find /fe/wrl -iname "*.wrl"').readlines()
+       # files = os.popen('find /fe/x3d -iname "*.x3d"').readlines()
+       # files = os.popen('find   /fe/x3d/X3dExamplesSavage   -iname "*.x3d"').readlines()
+
+       files.sort()
+       tot = len(files)
+       for i, f in enumerate(files):
+               if i < 124 or i > 1000000:
+                       continue
+               
+               #if i != 1068:
+               #       continue
+               
+               #if i != 12686:
+               #       continue
+               
+               f = f.strip()
+               print f, i, tot
+               sce = bpy.data.scenes.new(str(i) + '_' + f.split('/')[-1])
+               bpy.data.scenes.active = sce
+               # Window.
+               load_web3d(f, PREF_FLAT=True)
+       
+# test()