PyVRML97 2.3.4b3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,579 @@
1
+ """object for linearizing a scene graph to VRML97
2
+ """
3
+
4
+ from __future__ import unicode_literals
5
+
6
+ try:
7
+ from cStringIO import StringIO
8
+ except ImportError:
9
+ from io import StringIO
10
+ from vrml import arrays
11
+ from vrml.protofunctions import *
12
+ from vrml import node
13
+
14
+ defaults = {
15
+ 'subelspacer': ', ',
16
+ 'courtesyspace': ' ',
17
+ 'curindent': '',
18
+ 'indent': '\t',
19
+ 'numsep': ',',
20
+ 'full_element_separator': '\n',
21
+ 'mffieldsep': '\n',
22
+ # EndComments say: if more than this many lines are between node start
23
+ # and end, put a comment at the closing bracket saying what node/proto is
24
+ # being closed.
25
+ 'EndComments': 10,
26
+ }
27
+ minimal1 = {
28
+ 'subelspacer': ', ',
29
+ 'numsep': ' ',
30
+ 'curindent': '',
31
+ 'indent': ' ',
32
+ 'full_element_separator': '\n',
33
+ 'courtesyspace': '',
34
+ 'mffieldsep': ' ',
35
+ }
36
+
37
+
38
+ def namekey(node):
39
+ return node.name
40
+
41
+
42
+ def linearise(value, linvalues=defaults, **namedargs):
43
+ """Linearise the given (node) value to a string"""
44
+ l = Lineariser(linvalues, **namedargs)
45
+ return l.linear(value)
46
+
47
+
48
+ class Lineariser:
49
+ '''
50
+ A data structure & methods for linearising
51
+ sceneGraphs, nodes, scripts and prototypes.
52
+ Should be used only once as member vars are not
53
+ cleared after the initial linearisation.
54
+ '''
55
+
56
+ def __init__(self, linvalues=None, alreadydone=None, *args, **namedargs):
57
+ if linvalues is None:
58
+ linvalues = defaults
59
+ if namedargs:
60
+ linvalues = linvalues.copy()
61
+ linvalues.update(namedargs)
62
+ self.linvalues = linvalues
63
+ if alreadydone is None:
64
+ self.alreadydone = {}
65
+ else:
66
+ self.alreadydone = alreadydone
67
+
68
+ def linear(
69
+ self,
70
+ clientNode,
71
+ buffer=None,
72
+ skipProtos=None,
73
+ skipUnusedProtos=None,
74
+ *args,
75
+ **namedargs
76
+ ):
77
+ '''
78
+ Linearise a node, script, or scenegraph
79
+ '''
80
+ # prototypes in this dictionary will not be linearised
81
+ self.skipProtos = {}
82
+ # skipUnusedProtos skips the "prototype collection" linearisation step
83
+ # this has the effect of not outputing any prototype which is not actually
84
+ # used in the file. By default is "off", that is, all protos are linearised
85
+ self.skipUnusedProtos = skipUnusedProtos
86
+ # protobuffer is a seperate buffer into which the prototype definitions are stored
87
+ self.protobuffer = StringIO()
88
+ self.protobuffer.write('#VRML V2.0 utf8\n')
89
+ # protoalreadydone is used in place of the scenegraph-specific
90
+ # node alreadydone. This allows us to push all protos up to the
91
+ # top level of the hierarchy (thus making the process of linearisation much simpler)
92
+ self.protoalreadydone = {}
93
+ # main working algo...
94
+ self.typecache = {
95
+ 'Script': self._Script,
96
+ 'NULL': self._nullNode,
97
+ 'sceneGraph': self._sceneGraph,
98
+ }
99
+ self.buffer = buffer or StringIO()
100
+ self.alreadydone.clear()
101
+ self.cursceneGraph = (
102
+ []
103
+ ) # used to look up whether we need to output a prototype...
104
+ self.curproto = []
105
+ self.indentationlevel = 0
106
+ if type(clientNode) in (list, tuple):
107
+ for child in clientNode:
108
+ self._linear(child)
109
+ self.buffer.write('\n')
110
+ else:
111
+ self._linear(clientNode)
112
+ del self.typecache # to clear references to this node...
113
+ self.alreadydone.clear()
114
+ # side effect has filled up protobuffer for us
115
+ rval = self.protobuffer.getvalue() + self.buffer.getvalue()
116
+ self.buffer.close()
117
+ self.protobuffer.close()
118
+ return rval
119
+
120
+ ### High-level constructs...
121
+ def _sceneGraph(self, clientNode):
122
+ '''
123
+ A little niave, the sceneGraph just outputs everything
124
+ in its prototypes, then everything in its childlist, then all its ROUTES
125
+ '''
126
+ [self._preroute(clientNode, route) for route in clientNode.routes]
127
+ if clientNode is None:
128
+ startind = self.buffer.tell()
129
+ if len(self.cursceneGraph) == 0: # new file
130
+ self.buffer.write('#VRML V2.0 utf8\n')
131
+ self.alreadydone[id(clientNode)] = (
132
+ startind,
133
+ self.buffer.tell(),
134
+ ) # register this scenegraph's extents
135
+ return None
136
+ startind = self._canUse(clientNode)
137
+ if type(startind) != int: # this node has already been declared
138
+ self.buffer.write(startind)
139
+ return None
140
+ # use a seperate Node alreadydone for each sceneGraph
141
+ # so that we don't have cross-barrier USEs occuring
142
+ oldalreadydone = self.alreadydone
143
+ self.alreadydone = {}
144
+
145
+ # localise buffer as we will be accessing it many times
146
+ buffer = self.buffer
147
+ # header now part of the protobuffer
148
+ # if not self.cursceneGraph: # top level
149
+ # buffer.write( '#VRML V2.0 utf8\n' ) # should only do this when it's the top-level object
150
+ self.cursceneGraph.append(clientNode)
151
+
152
+ # linearise the "free" prototypes (any actually registered with the sceneGraph)
153
+ if (
154
+ not self.skipUnusedProtos
155
+ ): # are supposed to linearise all prototypes currently in the sceneGraph, regardless of whether they are used
156
+ for proto in clientNode.protoTypes.values():
157
+ if not id(proto) in self.protoalreadydone:
158
+ self._proto(proto)
159
+ # linearise the node/script children, they will include their prototypes if they are not already done
160
+ for child in clientNode.children:
161
+ self._linear(child)
162
+ buffer.write(self.linvalues['full_element_separator'])
163
+ # linearise the routes
164
+ for route in clientNode.routes:
165
+ # should check here to make sure the ROUTEs are valid
166
+ self._route(route)
167
+ # buffer.write( '%(full_element_separator)sROUTE %%s.%%s TO %%s.%%s'%self.linvalues%route )
168
+
169
+ # restore original alreadydone dictionary
170
+ self.alreadydone = oldalreadydone
171
+ self.alreadydone[id(clientNode)] = (
172
+ startind,
173
+ buffer.tell(),
174
+ ) # register this scenegraph's extents
175
+ del self.cursceneGraph[-1]
176
+ return None
177
+
178
+ def _proto(self, clientNode):
179
+ """Linearise a prototype, return whether the prototype is actually linearised"""
180
+ # check that we haven't yet done this prototype, register the fact that we've already started it
181
+ if type(clientNode) != type:
182
+ return
183
+
184
+ clientName = protoName(clientNode)
185
+
186
+ if builtin(clientNode):
187
+ # this prototype should not be linearised
188
+ return 0
189
+ if id(clientNode) in self.protoalreadydone:
190
+ # this precise prototype has been linearised already...
191
+ return 1
192
+ elif self.protoalreadydone.get(name(clientNode)):
193
+ # another prototype with the same name has already been linearised
194
+ self.protoalreadydone[id(clientNode)] = 1
195
+ return 1
196
+ self.curproto.append(clientNode)
197
+ self.protoalreadydone[clientName] = self.protoalreadydone[id(clientNode)] = 1
198
+
199
+ # we don't want the prototypes constantly moving inward :)
200
+ oldindent = self.indentationlevel
201
+ self._indent(0)
202
+ oldbuffer = self.buffer
203
+ buffer = self.buffer = StringIO() # local buffer only for this particular proto
204
+
205
+ # write header (PROTO x [, EXTERNPROTO x [ )
206
+ # TODO: the externalURL descriptor no longer works, likely because the proto node
207
+ # is now a type, so it is trying to access an instance variable
208
+ externalURL = clientNode.externalURL
209
+ if hasattr(externalURL, '__get__'):
210
+ externalURL = clientNode.externalURL.__get__(clientNode)
211
+ if externalURL:
212
+ buffer.write('EXTERNPROTO %s [' % (clientName,))
213
+ else:
214
+ buffer.write('PROTO %s [' % (clientName,))
215
+ # write the declaration...
216
+ self._indent()
217
+ self._eventDict(clientNode)
218
+ self._fieldDict(clientNode, requireDefault=1) # clientNode.__gi__ == "PROTO")
219
+ self._dedent()
220
+ linvalues = self.linvalues
221
+ if externalURL:
222
+ buffer.write('\n] ')
223
+ from vrml import fieldtypes
224
+
225
+ buffer.write(fieldtypes.MFString_vrmlstr(externalURL, self))
226
+ buffer.write('\n')
227
+ else:
228
+ buffer.write('\n] {\n')
229
+ # the following will write everything into the current proto's buffer
230
+ # references to prototypes not already linearised will cause a recursive
231
+ # call to proto that will write those into the protobuffer before returning
232
+ sg = getSceneGraph(clientNode)
233
+ if sg is not None:
234
+ self._sceneGraph(sg)
235
+ if 'EndComments' in linvalues and linvalues['EndComments'] * 60 < (
236
+ (buffer.tell())
237
+ ):
238
+ buffer.write('\n}#End PROTO %s\n' % (clientName))
239
+ else:
240
+ buffer.write('\n}\n')
241
+ self.alreadydone[id(clientNode)] = (
242
+ self.protobuffer.tell(),
243
+ self.protobuffer.tell() + buffer.tell(),
244
+ )
245
+ # note that we _always_ write out the prototype to the
246
+ # prototype-specific buffer! We do not write them into
247
+ # the main buffer. I suppose we could, but most of the
248
+ # time you want the prototypes all at the front of the
249
+ # file anyway.
250
+ self.protobuffer.write(buffer.getvalue())
251
+ # clear out the memory used by the buffer
252
+ buffer.close()
253
+ # return to the original buffer
254
+ self.buffer = oldbuffer
255
+ self._indent(oldindent)
256
+ # note that startind is irrelevant for prototypes,
257
+ # as they cannot be repeated, only multiply referenced.
258
+ self.curproto.pop()
259
+ return None
260
+
261
+ def _Node(self, clientNode, *args, **namedargs):
262
+ '''Linearise an individual node'''
263
+ # if we don't already have this nodes prototype in the
264
+ # root namespace, insert it there. For now we don't allow
265
+ # nested namespaces. This is a serious limitation and should
266
+ # be fixed at some point in time.
267
+ self._proto(getPrototype(clientNode))
268
+ buffer = self.buffer
269
+ startind = self._canUse(clientNode)
270
+ if type(startind) != int: # this node has already been declared
271
+ buffer.write(startind)
272
+ return None
273
+ # now calculate the representation of this node...
274
+ defName = self._defName(clientNode)
275
+ namedargs['linvalues'] = linvalues = self.linvalues
276
+ namedargs['alreadydone'] = self.alreadydone
277
+ buffer.write(
278
+ '%s%s {'
279
+ % (
280
+ defName,
281
+ protoName(clientNode),
282
+ )
283
+ )
284
+ position = buffer.tell()
285
+ self._indent()
286
+ self._attrDict(clientNode)
287
+
288
+ # write the node-ending comment
289
+ if buffer.tell() == position:
290
+ buffer.write("%(courtesyspace)s}" % linvalues)
291
+ elif 'EndComments' in linvalues and linvalues['EndComments'] * 60 < (
292
+ (buffer.tell() - startind)
293
+ ):
294
+ DEF = name(clientNode)
295
+ PROTO = protoName(clientNode)
296
+ buffer.write(
297
+ '%(full_element_separator)s%(curindent)s} #EndNode %%s'
298
+ % linvalues
299
+ % (DEF or PROTO)
300
+ )
301
+ else:
302
+ buffer.write('%(full_element_separator)s%(curindent)s}' % linvalues)
303
+ self._dedent()
304
+ self.alreadydone[id(clientNode)] = startind, buffer.tell()
305
+ return None
306
+
307
+ def _Script(self, clientNode):
308
+ '''
309
+ Scripts should be output in the following format:
310
+ DEF defName Script {
311
+ fields
312
+ events
313
+ attributes
314
+ }
315
+ Both fields or attributes can have "IS's", and possibly
316
+ the attributes as well.
317
+ '''
318
+ buffer = self.buffer
319
+ startind = self._canUse(clientNode)
320
+ if type(startind) != int: # this node has already been declared
321
+ buffer.write(startind)
322
+ return None
323
+ # Note: we assume that defNames are being stored in the Node as well as the sceneGraph, if not, will need to do a reverse lookup there
324
+ DEF = self._defName(clientNode)
325
+ buffer.write('%s Script {' % (DEF,))
326
+ self._indent()
327
+ self._indent()
328
+ linvalues = self.linvalues
329
+
330
+ self._eventDict(getPrototype(clientNode))
331
+ self._fieldDict(
332
+ getPrototype(clientNode),
333
+ requireDefault=1,
334
+ skipFields=(' DEF', 'url', 'directOutput', 'mustEvaluate'),
335
+ )
336
+ self._dedent()
337
+ self._attrDict(clientNode)
338
+ PROTO = protoName(clientNode)
339
+ buffer.write(
340
+ '%(full_element_separator)s%(curindent)s}#%%s' % linvalues % (DEF or PROTO)
341
+ )
342
+ self._dedent()
343
+ self.alreadydone[id(clientNode)] = startind, buffer.tell()
344
+ return None
345
+
346
+ def _attrDict(self, object):
347
+ """Write out the attribute dictionary for an object"""
348
+ buffer = self.buffer
349
+ linvalues = self.linvalues
350
+ if self.curproto:
351
+ set = node.ismaps(self.curproto[-1])
352
+ isMaps = {}
353
+ for fieldName, fieldList in set.items():
354
+ for n, field in fieldList:
355
+ if n is object:
356
+ isMaps[field] = fieldName
357
+ else:
358
+ isMaps = {}
359
+ if protoName(object) == "Script":
360
+ items = [
361
+ field
362
+ for field in getFields(object)
363
+ if field.name in ('url', 'mustEvaluate', 'directOutput')
364
+ ]
365
+ else:
366
+ items = [
367
+ field
368
+ for field in getFields(object)
369
+ if field.name and field.name[0] != ' '
370
+ ]
371
+ items.sort()
372
+ for field in items:
373
+ # following slows us down, but prevents the chaff from showing up...
374
+ val = field.fget(object)
375
+ default = field.getDefault()
376
+ if field.name in isMaps:
377
+ buffer.write(
378
+ '%(full_element_separator)s%(curindent)s%(indent)s%%s IS %%s\t'
379
+ % linvalues
380
+ % (field.name, isMaps.get(field.name))
381
+ )
382
+ elif (
383
+ default is not None and not arrays.safeCompare(default, val)
384
+ ) or default is None:
385
+ buffer.write(
386
+ '%(full_element_separator)s%(curindent)s%(indent)s%%s\t'
387
+ % linvalues
388
+ % (field.name,)
389
+ )
390
+ self._sffield(val, field)
391
+
392
+ def _eventDict(self, clientNode):
393
+ '''
394
+ Event Dictionaries have two possible sources of information,
395
+ the eventDict and the isNames dictionary. The first provides
396
+ name, type, out, the second provides any IS bindings which
397
+ need to be created.
398
+ '''
399
+ # need to get the IS/USE for the field if available
400
+ buffer = self.buffer
401
+
402
+ fields = sorted(getFields(clientNode, events=1), key=namekey)
403
+
404
+ for field in [f for f in fields if (f.name and f.name[0] != ' ')]:
405
+ buffer.write('%(full_element_separator)s%(curindent)s' % (self.linvalues))
406
+ field.eventVrmlstr(self)
407
+ # XXX do IS-mapping here!
408
+
409
+ def _fieldDict(self, clientNode, requireDefault=1, skipFields=('DEF',)):
410
+ buffer = self.buffer
411
+ fields = [
412
+ field
413
+ for field in getFields(clientNode)
414
+ if (
415
+ (field.name not in skipFields) and field.name and (field.name[0] != ' ')
416
+ )
417
+ ]
418
+ fields.sort(key=namekey)
419
+ for field in fields:
420
+ buffer.write('%(full_element_separator)s%(curindent)s' % (self.linvalues))
421
+ field.fieldVrmlstr(self)
422
+
423
+ def _fieldref(self, clientNode, *args, **namedargs):
424
+ self.buffer.write('IS %s' % clientNode.declaredName)
425
+ return None
426
+
427
+ def _preroute(self, sceneGraph, clientNode):
428
+ """Pre-scans all routes, forces all routed nodes to have DEF names"""
429
+ for child in (clientNode.source, clientNode.destination):
430
+ DEF = defName(child)
431
+ if not DEF:
432
+ count = 0
433
+ PROTO = protoName(child)
434
+ while 1:
435
+ name = "%s_%s" % (PROTO, count)
436
+ if sceneGraph.getDEF(name) is None:
437
+ sceneGraph.regDefName(name, child)
438
+ break
439
+ count += 1
440
+
441
+ def _route(self, clientNode):
442
+ '''Linearise a route'''
443
+ # should check here to make sure the ROUTEs are valid
444
+ buffer = self.buffer
445
+
446
+ sourcenode = defName(clientNode.source)
447
+ destinationnode = defName(clientNode.destination)
448
+ values = (
449
+ sourcenode,
450
+ clientNode.sourceField,
451
+ destinationnode,
452
+ clientNode.destinationField,
453
+ )
454
+ buffer.write(
455
+ '%(full_element_separator)sROUTE %%s.%%s TO %%s.%%s'
456
+ % self.linvalues
457
+ % values
458
+ )
459
+
460
+ def _sffield(self, anyobj, field, *args, **namedargs):
461
+ '''
462
+ Any to String takes an object and checks how it should
463
+ be linearised given that it is supposed to become a fieldType
464
+ This is done by first determining if the field has a __vrmlStr__
465
+ attribute. If it doesn't, a standard coerce_to is called with
466
+ the particular fieldType as the source. and 'String' as the
467
+ target.
468
+ This is necessary because the SFNode field can have any of Scripts,
469
+ Nodes, ProtoTypes and ExternProtos (well, not according to the
470
+ parsers, but someone might attempt it).
471
+ '''
472
+ try:
473
+ if field is node.RootScenegraphNode:
474
+ return
475
+ pName = protoName(anyobj)
476
+ handler = self.typecache.get(pName)
477
+ if handler:
478
+ return handler(anyobj)
479
+ if isinstance(anyobj, list):
480
+ return self._mfnode(anyobj)
481
+ return self._Node(anyobj)
482
+ except AttributeError:
483
+ if hasattr(field, 'vrmlstr'):
484
+ result = getattr(field, 'vrmlstr')(anyobj, self)
485
+ if result is not None:
486
+ self.buffer.write(result)
487
+ elif hasattr(self, field.typeName()):
488
+ result = getattr(self, field.typeName())(anyobj)
489
+ if result is not None:
490
+ self.buffer.write(result)
491
+ else:
492
+ raise TypeError(
493
+ '''Unknown fieldType %s, cannot convert to string''' % field
494
+ )
495
+
496
+ ### Utility functions...
497
+ def _dedent(self):
498
+ self.indentationlevel = self.indentationlevel - 1
499
+ self.linvalues['curindent'] = self.linvalues['indent'] * self.indentationlevel
500
+
501
+ def _indent(self, exact=None):
502
+ if exact is not None:
503
+ self.indentationlevel = exact
504
+ else:
505
+ self.indentationlevel = self.indentationlevel + 1
506
+ self.linvalues['curindent'] = self.linvalues['indent'] * self.indentationlevel
507
+
508
+ def _canUse(self, clientNode):
509
+ if id(clientNode) in self.alreadydone:
510
+ DEF = defName(clientNode)
511
+ if DEF:
512
+ return 'USE ' + DEF
513
+ # else have to linearise again, should warn the user
514
+ else:
515
+ keyvals = self.alreadydone[id(clientNode)]
516
+ index = self.buffer.tell()
517
+ try:
518
+ start, stop = keyvals
519
+ self.buffer.seek(start)
520
+ val = self.buffer.read(stop - start)
521
+ self.buffer.seek(index)
522
+ return (
523
+ '#WARNING HERE -- USE of node with no DEF name, Node duplicated\n'
524
+ + val
525
+ )
526
+ except TypeError:
527
+ return '''#ERROR HERE -- USE of a parent node that has no DEF name USE ignored'''
528
+ else:
529
+ ind = self.alreadydone[id(clientNode)] = self.buffer.tell()
530
+ return ind
531
+
532
+ def _nullNode(self, clientNode):
533
+ self.buffer.write('NULL')
534
+
535
+ def _defName(self, clientNode):
536
+ DEF = defName(clientNode)
537
+ if DEF:
538
+ return 'DEF %s ' % (DEF)
539
+ else:
540
+ return ''
541
+
542
+ def _linear(self, clientNode):
543
+ '''Linearise a particular client node of whatever type by dispatching to
544
+ appropriate method...'''
545
+ if type(clientNode) == type:
546
+ method = self._proto
547
+ else:
548
+ name = protoName(clientNode)
549
+ method = self.typecache.get(name, self._Node)
550
+ return method(clientNode)
551
+
552
+ ### Field-type handlers...
553
+ def _mfnode(self, anyobj, *args, **namedargs):
554
+ '''
555
+ Really, this will handle any list of elements where all elements
556
+ have a __vrmlStr__ method, but since most of those are nodes, we'll
557
+ keep the name for now.
558
+ format:
559
+ [(mffieldsep)
560
+ (curindent)(indent)child
561
+ ...
562
+ (curindent)]
563
+ or:
564
+ [ ]
565
+ '''
566
+ buffer = self.buffer
567
+ linvalues = self.linvalues
568
+ if anyobj: # first test to see if there's any point doing the processing
569
+ self._indent()
570
+ buffer.write('[ ')
571
+ for el in anyobj:
572
+ buffer.write(
573
+ '%(full_element_separator)s%(curindent)s%(indent)s' % linvalues
574
+ )
575
+ self._linear(el)
576
+ buffer.write('%(full_element_separator)s%(curindent)s]' % linvalues)
577
+ self._dedent()
578
+ else:
579
+ buffer.write(' [%(courtesyspace)s]' % linvalues)