diff options
author | Campbell Barton <ideasman42@gmail.com> | 2011-01-10 16:16:04 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2011-01-10 16:16:04 +0300 |
commit | ff204a1e4d098318fbd085ed587212ceaf42335b (patch) | |
tree | 9f079f37038a2a0d27bcc7d36df840d0f65ca1db /release | |
parent | f5c203af73fb86b08022096598241d28312fbae3 (diff) |
x3d import tabs -> spaces
Diffstat (limited to 'release')
-rw-r--r-- | release/scripts/op/io_scene_x3d/import_x3d.py | 4754 |
1 files changed, 2368 insertions, 2386 deletions
diff --git a/release/scripts/op/io_scene_x3d/import_x3d.py b/release/scripts/op/io_scene_x3d/import_x3d.py index a5547506dc7..d1923d17848 100644 --- a/release/scripts/op/io_scene_x3d/import_x3d.py +++ b/release/scripts/op/io_scene_x3d/import_x3d.py @@ -1,78 +1,60 @@ -#!BPY -""" -Name: 'X3D & VRML97 (.x3d / wrl)...' -Blender: 248 -Group: 'Import' -Tooltip: 'Load an X3D or VRML97 file' -""" - -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### 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 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. # -# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # -# 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" +# ##### END GPL LICENSE BLOCK ##### -__bpydoc__ = """\ -This script is an importer for the X3D and VRML97 file formats. -""" +# <pep8 compliant> DEBUG = False # This should work without a blender at all try: - from Blender.sys import exists + from Blender.sys import exists except: - from os.path import exists + from os.path import exists def baseName(path): - return path.split('/')[-1].split('\\')[-1] + return path.split('/')[-1].split('\\')[-1] def dirName(path): - return path[:-len(baseName(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 + + 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 +# transform are relative # order dosnt matter for loc/size/rot # right handed rotation # angles are in radians @@ -84,121 +66,121 @@ def imageConvertCompat(path): 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] + ''' + 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 # [] @@ -208,1077 +190,1077 @@ NODE_REFERENCE = 3 # USE foobar 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 + # 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 + + 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 + ''' + 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 + __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 + 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 + ''' + 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 @@ -1286,101 +1268,101 @@ def vrml_parse(path): # 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 __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, '' + ''' + 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, '' @@ -1395,12 +1377,12 @@ 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) + #if i < 801: + # continue + + f = f.strip() + print f, i, tot + vrml_parse(f) ''' # NO BLENDER CODE ABOVE THIS LINE. @@ -1422,1112 +1404,1112 @@ 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])) + ''' 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 + 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 + 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 + 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 + + 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 + # 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 + # 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 + # 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 + # 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 + # 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 + # 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) + # 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 + # 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) ) + 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 + 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 + 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 + 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) ) + 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) ) + 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) ) + 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 - # 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)) + 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)) + 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)) + 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, + ''' + 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 - + ''' + 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) - - + ''' + + #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 + + # 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) - + 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') - + Window.FileSelector(load_ui, 'Import X3D/VRML97') + # Testing stuff @@ -2553,42 +2535,42 @@ if __name__ == '__main__': # 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/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 +# 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) - + 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() |