forked from bartvdbraak/blender
427 lines
16 KiB
Python
427 lines
16 KiB
Python
from TextTools import TextTools
|
|
|
|
from simpleparse import generator
|
|
|
|
import scenegraph as proto
|
|
import strop as string
|
|
|
|
IMPORT_PARSE_TIME = 0.4
|
|
PROGRESS_DEPTH = 5
|
|
|
|
class UnfinishedError(Exception):
|
|
pass
|
|
|
|
class Parser:
|
|
def __init__( self, data ):
|
|
self.data = data
|
|
self.position = 0
|
|
self.result = proto.sceneGraph()
|
|
self.finalised = None
|
|
self.sceneGraphStack = [self.result]
|
|
self.prototypeStack = []
|
|
self.nodeStack = []
|
|
self.fieldTypeStack = []
|
|
self.readHeader()
|
|
self.depth = 0
|
|
self.progresscount = 0
|
|
def _lines( self, index=None ):
|
|
if index is None:
|
|
index = self.position
|
|
return TextTools.countlines (self.data[:index])
|
|
def parse( self, progressCallback=None ):
|
|
datalength = float( len( self.data ))
|
|
while self.readNext():
|
|
if progressCallback:
|
|
if not progressCallback(IMPORT_PARSE_TIME * self.position/datalength ):
|
|
raise UnfinishedError(
|
|
"Did not complete parsing, cancelled by user. Stopped at line %s" %(self._lines())
|
|
)
|
|
if self.position < len( self.data ):
|
|
raise UnfinishedError(
|
|
'''Unable to complete parsing of file, stopped at line %s:\n%s...'''%(self._lines(), self.data[self.position:self.position+120])
|
|
)
|
|
return self.result
|
|
def readHeader( self ):
|
|
'''Read the file header'''
|
|
success, tags, next = TextTools.tag( self.data, HEADERPARSER, self.position )
|
|
if success:
|
|
self.datalength = len( self.data )
|
|
#print "header ok"
|
|
return success
|
|
else:
|
|
try:
|
|
self.decompress()
|
|
success, tags, next = TextTools.tag( self.data, HEADERPARSER, self.position )
|
|
self.datalength = len( self.data )
|
|
return success
|
|
except:
|
|
raise ValueError( "Could not find VRML97 header in file!" )
|
|
def readNext( self):
|
|
'''Read the next root-level construct'''
|
|
success, tags, next = TextTools.tag( self.data, ROOTITEMPARSER, self.position )
|
|
## print 'readnext', success
|
|
if self.position >= self.datalength:
|
|
print 'reached file end'
|
|
return None
|
|
if success:
|
|
# print ' successful parse'
|
|
self.position = next
|
|
map (self.rootItem_Item, tags )
|
|
return success
|
|
else:
|
|
return None
|
|
def rootItem (self, (type, start, stop, (item,))):
|
|
''' Process a single root item '''
|
|
self.rootItem_Item( item )
|
|
def rootItem_Item( self, item ):
|
|
result = self._dispatch(item)
|
|
if result is not None:
|
|
## print "non-null result"
|
|
## print id( self.sceneGraphStack[-1] ), id(self.result )
|
|
self.sceneGraphStack[-1].children.append( result )
|
|
def _getString (self, (tag, start, stop, sublist)):
|
|
''' Return the raw string for a given interval in the data '''
|
|
return self.data [start: stop]
|
|
|
|
def _dispatch (self, (tag, left, right, sublist)):
|
|
''' Dispatch to the appropriate processing function based on tag value '''
|
|
## print "dispatch", tag
|
|
self.depth += 1
|
|
if self.depth < PROGRESS_DEPTH:
|
|
self.progresscount += 1
|
|
try:
|
|
meth = getattr (self, tag)
|
|
except AttributeError:
|
|
raise AttributeError("Unknown parse tag '%s' found! Check the parser definition!" % (tag))
|
|
ret = meth( (tag, left, right, sublist) )
|
|
self.depth -= 1
|
|
return ret
|
|
|
|
def Proto(self, (tag, start, stop, sublist)):
|
|
''' Create a new prototype in the current sceneGraph '''
|
|
# first entry is always ID
|
|
ID = self._getString ( sublist [0])
|
|
print "PROTO",ID
|
|
newNode = proto.Prototype (ID)
|
|
## print "\t",newNode
|
|
setattr ( self.sceneGraphStack [-1].protoTypes, ID, newNode)
|
|
self.prototypeStack.append( newNode )
|
|
# process the rest of the entries with the given stack
|
|
map ( self._dispatch, sublist [1:] )
|
|
self.prototypeStack.pop( )
|
|
def fieldDecl(self,(tag, left, right, (exposure, datatype, name, field))):
|
|
''' Create a new field declaration for the current prototype'''
|
|
# get the definition in recognizable format
|
|
exposure = self._getString (exposure) == "exposedField"
|
|
datatype = self._getString (datatype)
|
|
name = self._getString (name)
|
|
# get the vrml value for the field
|
|
self.fieldTypeStack.append( datatype )
|
|
field = self._dispatch (field)
|
|
self.fieldTypeStack.pop( )
|
|
self.prototypeStack[-1].addField ((name, datatype, exposure), field)
|
|
def eventDecl(self,(tag, left, right, (direction, datatype, name))):
|
|
# get the definition in recognizable format
|
|
direction = self._getString (direction) == "eventOut"
|
|
datatype = self._getString (datatype)
|
|
name = self._getString (name)
|
|
# get the vrml value for the field
|
|
self.prototypeStack[-1].addEvent((name, datatype, direction))
|
|
def decompress( self ):
|
|
pass
|
|
def ExternProto( self, (tag, start, stop, sublist)):
|
|
''' Create a new external prototype from a tag list'''
|
|
# first entry is always ID
|
|
ID = self._getString ( sublist [0])
|
|
newNode = proto.Prototype (ID)
|
|
setattr ( self.sceneGraphStack [-1].protoTypes, ID, newNode)
|
|
self.prototypeStack.append( newNode )
|
|
# process the rest of the entries with the given stack
|
|
map ( self._dispatch, sublist [1:] )
|
|
self.prototypeStack.pop( )
|
|
def ExtProtoURL( self, (tag, start, stop, sublist)):
|
|
''' add the url to the external prototype '''
|
|
## print sublist
|
|
values = self.MFString( sublist )
|
|
self.prototypeStack[-1].url = values
|
|
return values
|
|
def extFieldDecl(self, (tag, start, stop, (exposure, datatype, name))):
|
|
''' An external field declaration, no default value '''
|
|
# get the definition in recognizable format
|
|
exposure = self._getString (exposure) == "exposedField"
|
|
datatype = self._getString (datatype)
|
|
name = self._getString (name)
|
|
# get the vrml value for the field
|
|
self.prototypeStack[-1].addField ((name, datatype, exposure))
|
|
def ROUTE(self, (tag, start, stop, names )):
|
|
''' Create a new route object, add the current sceneGraph '''
|
|
names = map(self._getString, names)
|
|
self.sceneGraphStack [-1].addRoute( names )
|
|
def Node (self, (tag, start, stop, sublist)):
|
|
''' Create new node, returning the value to the caller'''
|
|
## print 'node'
|
|
|
|
if sublist[0][0] == 'name':
|
|
name = self._getString ( sublist [0])
|
|
ID = self._getString ( sublist [1])
|
|
rest = sublist [2:]
|
|
else:
|
|
name = ""
|
|
ID = self._getString ( sublist [0])
|
|
rest = sublist [1:]
|
|
try:
|
|
prototype = getattr ( self.sceneGraphStack [-1].protoTypes, ID)
|
|
except AttributeError:
|
|
#raise NameError ('''Prototype %s used without declaration! %s:%s'''%(ID, start, stop) )
|
|
print ('''### Prototype %s used without declaration! %s:%s'''%(ID, start, stop) )
|
|
|
|
return None
|
|
newNode = prototype(name)
|
|
if name:
|
|
self.sceneGraphStack [-1].regDefName( name, newNode )
|
|
self.nodeStack.append (newNode)
|
|
map (self._dispatch, rest)
|
|
self.nodeStack.pop ()
|
|
## print 'node finished'
|
|
return newNode
|
|
def Attr(self, (tag, start, stop, (name, value))):
|
|
''' An attribute of a node or script '''
|
|
name = self._getString ( name )
|
|
self.fieldTypeStack.append( self.nodeStack[-1].PROTO.getField( name ).type )
|
|
value = self._dispatch( value )
|
|
self.fieldTypeStack.pop()
|
|
if hasattr( self.nodeStack[-1], "__setattr__" ):
|
|
self.nodeStack[-1].__setattr__( name, value, raw=1 )
|
|
else:
|
|
# use slower coercing versions...
|
|
setattr( self.nodeStack[-1], name, value )
|
|
def Script( self, (tag, start, stop, sublist)):
|
|
''' A script node (can be a root node)'''
|
|
# what's the DEF name...
|
|
if sublist and sublist[0][0] == 'name':
|
|
name = self._getString ( sublist [0])
|
|
rest = sublist [1:]
|
|
else:
|
|
name = ""
|
|
rest = sublist
|
|
# build the script node...
|
|
newNode = proto.Script( name )
|
|
# register with sceneGraph
|
|
if name:
|
|
self.sceneGraphStack [-1].regDefName( name, newNode )
|
|
self.nodeStack.append (newNode)
|
|
map( self._dispatch, rest )
|
|
self.nodeStack.pop ()
|
|
return newNode
|
|
def ScriptEventDecl( self,(tag, left, right, sublist)):
|
|
# get the definition in recognizable format
|
|
direction, datatype, name = sublist[:3] # must have at least these...
|
|
direction = self._getString (direction) == "eventOut"
|
|
datatype = self._getString (datatype)
|
|
name = self._getString (name)
|
|
# get the vrml value for the field
|
|
self.nodeStack[-1].PROTO.addEvent((name, datatype, direction))
|
|
if sublist[3:]:
|
|
# will this work???
|
|
setattr( self.nodeStack[-1], name, self._dispatch( sublist[3] ) )
|
|
def ScriptFieldDecl(self,(tag, left, right, (exposure, datatype, name, field))):
|
|
''' Create a new field declaration for the current prototype'''
|
|
# get the definition in recognizable format
|
|
exposure = self._getString (exposure) == "exposedField"
|
|
datatype = self._getString (datatype)
|
|
name = self._getString (name)
|
|
# get the vrml value for the field
|
|
self.fieldTypeStack.append( datatype )
|
|
field = self._dispatch (field)
|
|
self.fieldTypeStack.pop( )
|
|
self.nodeStack[-1].PROTO.addField ((name, datatype, exposure))
|
|
setattr( self.nodeStack[-1], name, field )
|
|
def SFNull(self, tup):
|
|
''' Create a reference to the SFNull node '''
|
|
## print 'hi'
|
|
return proto.NULL
|
|
def USE( self, (tag, start, stop, (nametuple,) )):
|
|
''' Create a reference to an already defined node'''
|
|
name = self._getString (nametuple)
|
|
if self.depth < PROGRESS_DEPTH:
|
|
self.progresscount += 1
|
|
try:
|
|
node = self.sceneGraphStack [-1].defNames [name]
|
|
return node
|
|
except KeyError:
|
|
raise NameError ('''USE without DEF for node %s %s:%s'''%(name, start, stop))
|
|
def IS(self, (tag, start, stop, (nametuple,))):
|
|
''' Create a field reference '''
|
|
name = self._getString (nametuple)
|
|
if not self.prototypeStack [-1].getField (name):
|
|
raise Exception (''' Attempt to create IS mapping of non-existent field %s %s:%s'''%(name, start, stop))
|
|
return proto.IS(name)
|
|
def Field( self, (tag, start, stop, sublist)):
|
|
''' A field value (of any type) '''
|
|
|
|
if sublist and sublist[0][0] in ('USE','Script','Node','SFNull'):
|
|
if self.fieldTypeStack[-1] == 'SFNode':
|
|
return self._dispatch( sublist[0] )
|
|
else:
|
|
return map( self._dispatch, sublist )
|
|
elif self.fieldTypeStack[-1] == 'MFNode':
|
|
return []
|
|
else:
|
|
# is a simple data type...
|
|
function = getattr( self, self.fieldTypeStack[-1] )
|
|
try:
|
|
return function( sublist )
|
|
except ValueError:
|
|
traceback.print_exc()
|
|
print sublist
|
|
raise
|
|
|
|
def SFBool( self, (tup,) ):
|
|
'''Boolean, in Python tradition is either 0 or 1'''
|
|
return self._getString(tup) == 'TRUE'
|
|
def SFFloat( self, (x,) ):
|
|
return string.atof( self._getString(x) )
|
|
SFTime = SFFloat
|
|
def SFInt32( self, (x,) ):
|
|
return string.atoi( self._getString(x), 0 ) # allow for non-decimal numbers
|
|
def SFVec3f( self, (x,y,z) ):
|
|
return map( string.atof, map(self._getString, (x,y,z)) )
|
|
def SFVec2f( self, (x,y) ):
|
|
return map( string.atof, map(self._getString, (x,y)) )
|
|
def SFColor( self, (r,g,b) ):
|
|
return map( string.atof, map(self._getString, (r,g,b)) )
|
|
def SFRotation( self, (x,y,z,a) ):
|
|
return map( string.atof, map(self._getString, (x,y,z,a)) )
|
|
|
|
def MFInt32( self, tuples ):
|
|
result = []
|
|
# localisation
|
|
atoi = string.atoi
|
|
append = result.append
|
|
data = self.data
|
|
for tag, start, stop, children in tuples:
|
|
append( atoi( data[start:stop], 0) )
|
|
return result
|
|
SFImage = MFInt32
|
|
def MFFloat( self, tuples ):
|
|
result = []
|
|
# localisation
|
|
atof = string.atof
|
|
append = result.append
|
|
data = self.data
|
|
for tag, start, stop, children in tuples:
|
|
append( atof( data[start:stop]) )
|
|
return result
|
|
MFTime = MFFloat
|
|
def MFVec3f( self, tuples, length=3, typename='MFVec3f'):
|
|
result = []
|
|
# localisation
|
|
atof = string.atof
|
|
data = self.data
|
|
while tuples:
|
|
newobj = []
|
|
for tag, start, stop, children in tuples[:length]:
|
|
newobj.append( atof(data[start:stop] ))
|
|
if len(newobj) != length:
|
|
raise ValueError(
|
|
'''Incorrect number of elements in %s field at line %s'''%(typename, self._lines(stop))
|
|
)
|
|
result.append( newobj )
|
|
del tuples[:length]
|
|
return result
|
|
def MFVec2f( self, tuples):
|
|
return self.MFVec3f( tuples, length=2, typename='MFVec2f')
|
|
def MFRotation( self, tuples ):
|
|
return self.MFVec3f( tuples, length=4, typename='MFRotation')
|
|
def MFColor( self, tuples ):
|
|
return self.MFVec3f( tuples, length=3, typename='MFColor')
|
|
|
|
def MFString( self, tuples ):
|
|
bigresult = []
|
|
for (tag, start, stop, sublist) in tuples:
|
|
result = []
|
|
for element in sublist:
|
|
if element[0] == 'CHARNODBLQUOTE':
|
|
result.append( self.data[element[1]:element[2]] )
|
|
elif element[0] == 'ESCAPEDCHAR':
|
|
result.append( self.data[element[1]+1:element[2]] )
|
|
elif element[0] == 'SIMPLEBACKSLASH':
|
|
result.append( '\\' )
|
|
bigresult.append( string.join( result, "") )
|
|
return bigresult
|
|
## result = []
|
|
## for tuple in tuples:
|
|
## result.append( self.SFString( tuple) )
|
|
## return result
|
|
def SFString( self, tuples ):
|
|
'''Return the (escaped) string as a simple Python string'''
|
|
if tuples:
|
|
(tag, start, stop, sublist) = tuples[0]
|
|
if len( tuples ) > 1:
|
|
print '''Warning: SFString field has more than one string value''', self.data[tuples[0][1]:tuples[-1][2]]
|
|
result = []
|
|
for element in sublist:
|
|
if element[0] == 'CHARNODBLQUOTE':
|
|
result.append( self.data[element[1]:element[2]] )
|
|
elif element[0] == 'ESCAPEDCHAR':
|
|
result.append( self.data[element[1]+1:element[2]] )
|
|
elif element[0] == 'SIMPLEBACKSLASH':
|
|
result.append( '\\' )
|
|
return string.join( result, "")
|
|
else:
|
|
raise ValueError( "NULL SFString parsed???!!!" )
|
|
def vrmlScene( self, (tag, start, stop, sublist)):
|
|
'''A (prototype's) vrml sceneGraph'''
|
|
newNode = proto.sceneGraph (root=self.sceneGraphStack [-1])
|
|
self.sceneGraphStack.append (newNode)
|
|
#print 'setting proto sceneGraph', `newNode`
|
|
self.prototypeStack[-1].sceneGraph = newNode
|
|
results = filter (None, map (self._dispatch, sublist))
|
|
if results:
|
|
# items which are not auto-magically inserted into their parent
|
|
for result in results:
|
|
newNode.children.append( result)
|
|
self.sceneGraphStack.pop()
|
|
|
|
PARSERDECLARATION = r'''header := -[\n]*
|
|
rootItem := ts,(Proto/ExternProto/ROUTE/('USE',ts,USE,ts)/Script/Node),ts
|
|
vrmlScene := rootItem*
|
|
Proto := 'PROTO',ts,nodegi,ts,'[',ts,(fieldDecl/eventDecl)*,']', ts, '{', ts, vrmlScene,ts, '}', ts
|
|
fieldDecl := fieldExposure,ts,dataType,ts,name,ts,Field,ts
|
|
fieldExposure := 'field'/'exposedField'
|
|
dataType := 'SFBool'/'SFString'/'SFFloat'/'SFTime'/'SFVec3f'/'SFVec2f'/'SFRotation'/'SFInt32'/'SFImage'/'SFColor'/'SFNode'/'MFBool'/'MFString'/'MFFloat'/'MFTime'/'MFVec3f'/'MFVec2f'/'MFRotation'/'MFInt32'/'MFColor'/'MFNode'
|
|
eventDecl := eventDirection, ts, dataType, ts, name, ts
|
|
eventDirection := 'eventIn'/'eventOut'
|
|
ExternProto := 'EXTERNPROTO',ts,nodegi,ts,'[',ts,(extFieldDecl/eventDecl)*,']', ts, ExtProtoURL
|
|
extFieldDecl := fieldExposure,ts,dataType,ts,name,ts
|
|
ExtProtoURL := '['?,(ts,SFString)*, ts, ']'?, ts # just an MFString by another name :)
|
|
ROUTE := 'ROUTE',ts, name,'.',name, ts, 'TO', ts, name,'.',name, ts
|
|
Node := ('DEF',ts,name,ts)?,nodegi,ts,'{',ts,(Proto/ExternProto/ROUTE/Attr)*,ts,'}', ts
|
|
Script := ('DEF',ts,name,ts)?,'Script',ts,'{',ts,(ScriptFieldDecl/ScriptEventDecl/Proto/ExternProto/ROUTE/Attr)*,ts,'}', ts
|
|
ScriptEventDecl := eventDirection, ts, dataType, ts, name, ts, ('IS', ts, IS,ts)?
|
|
ScriptFieldDecl := fieldExposure,ts,dataType,ts,name,ts,(('IS', ts,IS,ts)/Field),ts
|
|
SFNull := 'NULL', ts
|
|
|
|
# should really have an optimised way of declaring a different reporting name for the same production...
|
|
USE := name
|
|
IS := name
|
|
nodegi := name
|
|
Attr := name, ts, (('IS', ts,IS,ts)/Field), ts
|
|
Field := ( '[',ts,((SFNumber/SFBool/SFString/('USE',ts,USE,ts)/Script/Node),ts)*, ']', ts )/((SFNumber/SFBool/SFNull/SFString/('USE',ts,USE,ts)/Script/Node),ts)+
|
|
|
|
name := -[][0-9{}\000-\020"'#,.\\ ], -[][{}\000-\020"'#,.\\ ]*
|
|
SFNumber := [-+]*, ( ('0',[xX],[0-9]+) / ([0-9.]+,([eE],[-+0-9.]+)?))
|
|
SFBool := 'TRUE'/'FALSE'
|
|
SFString := '"',(CHARNODBLQUOTE/ESCAPEDCHAR/SIMPLEBACKSLASH)*,'"'
|
|
CHARNODBLQUOTE := -[\134"]+
|
|
SIMPLEBACKSLASH := '\134'
|
|
ESCAPEDCHAR := '\\"'/'\134\134'
|
|
<ts> := ( [ \011-\015,]+ / ('#',-'\012'*,'\n')+ )*
|
|
'''
|
|
|
|
|
|
PARSERTABLE = generator.buildParser( PARSERDECLARATION )
|
|
HEADERPARSER = PARSERTABLE.parserbyname( "header" )
|
|
ROOTITEMPARSER = PARSERTABLE.parserbyname( "rootItem" )
|
|
|