Skip to content
Snippets Groups Projects
import_x3d.py 87.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    # <pep8 compliant>
    
    DEBUG = False
    
    # This should work without a blender at all
    
        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 os.path.exists(path_to):
    
                return path_to
    
        return path
    
    # notes
    # transform are relative
    # order dosnt matter for loc/size/rot
    # right handed rotation
    # angles are in radians
    # rotation first defines axis then ammount in radians
    
    
    # =============================== VRML Spesific
    
    def vrmlFormat(data):
        '''
        Keep this as a valid vrml file, but format in a way we can predict.
        '''
        # Strip all commends - # not in strings - warning multiline strings are ignored.
        def strip_comment(l):
            #l = ' '.join(l.split())
            l = l.strip()
    
            if l.startswith('#'):
                return ''
    
            i = l.find('#')
    
            if i == -1:
                return l
    
            # Most cases accounted for! if we have a comment at the end of the line do this...
            #j = l.find('url "')
            j = l.find('"')
    
            if j == -1:  # simple no strings
                return l[:i].strip()
    
            q = False
            for i, c in enumerate(l):
                if c == '"':
                    q = not q  # invert
    
                elif c == '#':
                    if q == False:
                        return l[:i - 1]
    
            return l
    
        data = '\n'.join([strip_comment(l) for l in data.split('\n')])  # remove all whitespace
    
        EXTRACT_STRINGS = True  # only needed when strings or filesnames containe ,[]{} chars :/
    
        if EXTRACT_STRINGS:
    
            # We need this so we can detect URL's
            data = '\n'.join([' '.join(l.split()) for l in data.split('\n')])  # remove all whitespace
    
            string_ls = []
    
            #search = 'url "'
            search = '"'
    
            ok = True
            last_i = 0
            while ok:
                ok = False
                i = data.find(search, last_i)
                if i != -1:
    
                    start = i + len(search)  # first char after end of search
                    end = data.find('"', start)
                    if end != -1:
                        item = data[start:end]
                        string_ls.append(item)
                        data = data[:start] + data[end:]
                        ok = True  # keep looking
    
                        last_i = (end - len(item)) + 1
                        # print(last_i, item, '|' + data[last_i] + '|')
    
        # done with messy extracting strings part
    
        # Bad, dont take strings into account
        '''
        data = data.replace('#', '\n#')
        data = '\n'.join([ll for l in data.split('\n') for ll in (l.strip(),) if not ll.startswith('#')]) # remove all whitespace
        '''
        data = data.replace('{', '\n{\n')
        data = data.replace('}', '\n}\n')
        data = data.replace('[', '\n[\n')
        data = data.replace(']', '\n]\n')
    
        data = data.replace(',', ' , ')  # make sure comma's separate
    
    
        if EXTRACT_STRINGS:
            # add strings back in
    
            search = '"'  # fill in these empty strings
    
            ok = True
            last_i = 0
            while ok:
                ok = False
                i = data.find(search + '"', last_i)
                # print(i)
                if i != -1:
                    start = i + len(search)  # first char after end of search
                    item = string_ls.pop(0)
                    # print(item)
                    data = data[:start] + item + data[start:]
    
                    last_i = start + len(item) + 1
    
                    ok = True
    
        # More annoying obscure cases where USE or DEF are placed on a newline
        # data = data.replace('\nDEF ', ' DEF ')
        # data = data.replace('\nUSE ', ' USE ')
    
        data = '\n'.join([' '.join(l.split()) for l in data.split('\n')])  # remove all whitespace
    
        # Better to parse the file accounting for multiline arrays
        '''
        data = data.replace(',\n', ' , ') # remove line endings with commas
        data = data.replace(']', '\n]\n') # very very annoying - but some comma's are at the end of the list, must run this again.
        '''
    
        return [l for l in data.split('\n') if l]
    
    NODE_NORMAL = 1  # {}
    NODE_ARRAY = 2  # []
    NODE_REFERENCE = 3  # USE foobar
    # NODE_PROTO = 4 #
    
    lines = []
    
    
    def getNodePreText(i, words):
        # print(lines[i])
        use_node = False
        while len(words) < 5:
    
            if i >= len(lines):
                break
                '''
            elif lines[i].startswith('PROTO'):
                return NODE_PROTO, i+1
                '''
            elif lines[i] == '{':
                # words.append(lines[i]) # no need
                # print("OK")
                return NODE_NORMAL, i + 1
            elif lines[i].count('"') % 2 != 0:  # odd number of quotes? - part of a string.
                # print('ISSTRING')
                break
            else:
                new_words = lines[i].split()
                if 'USE' in new_words:
                    use_node = True
    
                words.extend(new_words)
                i += 1
    
            # Check for USE node - no {
            # USE #id - should always be on the same line.
            if use_node:
                # print('LINE', i, words[:words.index('USE')+2])
                words[:] = words[:words.index('USE') + 2]
                if lines[i] == '{' and lines[i + 1] == '}':
                    # USE sometimes has {} after it anyway
                    i += 2
                return NODE_REFERENCE, i
    
        # print("error value!!!", words)
        return 0, -1
    
    
    def is_nodeline(i, words):
    
        if not lines[i][0].isalpha():
            return 0, 0
    
        #if lines[i].startswith('field'):
        #   return 0, 0
    
        # Is this a prototype??
        if lines[i].startswith('PROTO'):
            words[:] = lines[i].split()
            return NODE_NORMAL, i + 1  # TODO - assumes the next line is a '[\n', skip that
        if lines[i].startswith('EXTERNPROTO'):
            words[:] = lines[i].split()
            return NODE_ARRAY, i + 1  # TODO - assumes the next line is a '[\n', skip that
    
        '''
        proto_type, new_i = is_protoline(i, words, proto_field_defs)
        if new_i != -1:
            return proto_type, new_i
        '''
    
        # Simple "var [" type
        if lines[i + 1] == '[':
            if lines[i].count('"') % 2 == 0:
                words[:] = lines[i].split()
                return NODE_ARRAY, i + 2
    
        node_type, new_i = getNodePreText(i, words)
    
        if not node_type:
            if DEBUG:
                print("not node_type", lines[i])
            return 0, 0
    
        # Ok, we have a { after some values
        # Check the values are not fields
        for i, val in enumerate(words):
    
            if i != 0 and words[i - 1] in {'DEF', 'USE'}:
    
                # ignore anything after DEF, it is a ID and can contain any chars.
                pass
    
            elif val[0].isalpha() and val not in {'TRUE', 'FALSE'}:
    
                pass
            else:
                # There is a number in one of the values, therefor we are not a node.
                return 0, 0
    
        #if node_type==NODE_REFERENCE:
        #   print(words, "REF_!!!!!!!")
        return node_type, new_i
    
    
    def is_numline(i):
        '''
        Does this line start with a number?
        '''
    
        # Works but too slow.
        '''
        l = lines[i]
        for w in l.split():
            if w==',':
                pass
            else:
                try:
                    float(w)
                    return True
    
                except:
                    return False
    
        return False
        '''
    
        l = lines[i]
    
        line_start = 0
    
        if l.startswith(', '):
            line_start += 2
    
        line_end = len(l) - 1
        line_end_new = l.find(' ', line_start)  # comma's always have a space before them
    
        if line_end_new != -1:
            line_end = line_end_new
    
        try:
            float(l[line_start:line_end])  # works for a float or int
            return True
        except:
            return False
    
    
    class vrmlNode(object):
        __slots__ = ('id',
                     'fields',
                     'proto_node',
                     'proto_field_defs',
                     'proto_fields',
                     'node_type',
                     'parent',
                     'children',
                     'parent',
                     'array_data',
                     'reference',
                     'lineno',
                     'filename',
                     'blendObject',
                     'DEF_NAMESPACE',
                     'ROUTE_IPO_NAMESPACE',
                     'PROTO_NAMESPACE',
                     'x3dNode')
    
        def __init__(self, parent, node_type, lineno):
            self.id = None
            self.node_type = node_type
            self.parent = parent
            self.blendObject = None
            self.x3dNode = None  # for x3d import only
            if parent:
                parent.children.append(self)
    
            self.lineno = lineno
    
            # This is only set from the root nodes.
            # Having a filename also denotes a root node
            self.filename = None
            self.proto_node = None  # proto field definition eg: "field SFColor seatColor .6 .6 .1"
    
            # Store in the root node because each inline file needs its own root node and its own namespace
            self.DEF_NAMESPACE = None
            self.ROUTE_IPO_NAMESPACE = None
            '''
            self.FIELD_NAMESPACE = None
            '''
    
            self.PROTO_NAMESPACE = None
    
            self.reference = None
    
            if node_type == NODE_REFERENCE:
                # For references, only the parent and ID are needed
                # the reference its self is assigned on parsing
                return
    
            self.fields = []  # fields have no order, in some cases rool level values are not unique so dont use a dict
    
            self.proto_field_defs = []  # proto field definition eg: "field SFColor seatColor .6 .6 .1"
            self.proto_fields = []  # proto field usage "diffuseColor IS seatColor"
            self.children = []
            self.array_data = []  # use for arrays of data - should only be for NODE_ARRAY types
    
        # Only available from the root node
        '''
        def getFieldDict(self):
            if self.FIELD_NAMESPACE != None:
                return self.FIELD_NAMESPACE
            else:
                return self.parent.getFieldDict()
        '''
        def getProtoDict(self):
            if self.PROTO_NAMESPACE != None:
                return self.PROTO_NAMESPACE
            else:
                return self.parent.getProtoDict()
    
        def getDefDict(self):
            if self.DEF_NAMESPACE != None:
                return self.DEF_NAMESPACE
            else:
                return self.parent.getDefDict()
    
        def getRouteIpoDict(self):
            if self.ROUTE_IPO_NAMESPACE != None:
                return self.ROUTE_IPO_NAMESPACE
            else:
                return self.parent.getRouteIpoDict()
    
        def setRoot(self, filename):
            self.filename = filename
            # self.FIELD_NAMESPACE =        {}
            self.DEF_NAMESPACE = {}
            self.ROUTE_IPO_NAMESPACE = {}
            self.PROTO_NAMESPACE = {}
    
        def isRoot(self):
    
                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() is None and child.getExternprotoName() is 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()  # in case 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:
                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()  # in case we're an instance
    
    
            f = self_real.getFieldName(field, ancestry)
    
                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()  # in case we're an instance
    
    
            f = self_real.getFieldName(field, ancestry)
    
                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()  # in case we're an instance
    
    
            f = self_real.getFieldName(field, ancestry)
    
                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()  # in case we're an instance
    
    
            f = self_real.getFieldName(field, ancestry)
    
                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()  # in case we're an instance
    
    
            f = self_real.getFieldName(field, ancestry)
    
                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
            '''
    
            def array_as_number(array_string):
                array_data = []
                try:
                    array_data = [int(val) for val in array_string]
                except:
                    try:
                        array_data = [float(val) for val in array_string]
                    except:
                        print('\tWarning, could not parse array data from field')
    
                return array_data
    
    
            self_real = self.getRealNode()  # in case 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 is 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)
    
                    return []
    
                array_data = array_data.replace(',', ' ')
                data_split = array_data.split()
    
                array_data = array_as_number(data_split)
    
            elif type(child_array) == list:
                # x3d creates these
                data_split = [w.strip(",") for w in child_array]
    
                array_data = array_as_number(data_split)
            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 already 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()  # in case 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(bpy.path.resolve_ncase(urls[-1]))
    
    
                    urls.append(os.path.join(os.path.dirname(self.getFilename()), url))
    
                    urls.append(bpy.path.resolve_ncase(urls[-1]))
    
    
                    urls.append(os.path.join(os.path.dirname(self.getFilename()), os.path.basename(url)))
    
                    urls.append(bpy.path.resolve_ncase(urls[-1]))
    
                    try:
    
                        url = [url for url in urls if os.path.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)