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.
vrml/node.py ADDED
@@ -0,0 +1,569 @@
1
+ """Base-class for scenegraph nodes
2
+
3
+ Requires Python 2.2.x, as it makes
4
+ extensive use of properties
5
+ """
6
+
7
+ from vrml import field, fieldtypes, weaklist, weakkeydictfix
8
+ from vrml import copier as copiermodule
9
+ from vrml import olist
10
+ from vrml.protofunctions import *
11
+ from pydispatch import dispatcher
12
+ import weakref
13
+
14
+
15
+ class Node(object):
16
+ """A generic scene graph node
17
+
18
+ Unlike earlier versions of the library,
19
+ this implementation of the Node class is
20
+ basically a regular python class. This is
21
+ possible because it uses the python 2.2.x
22
+ property/descriptor API extensively.
23
+
24
+ Technically this is a multiple-hierarchy DAG
25
+ node, as there can be any number of node
26
+ children attributes, and nodes may appear
27
+ multiple times in the hierarchy.
28
+
29
+ Attributes of note:
30
+ " DEF" field
31
+ a simple string field which stores the
32
+ DEF name of a node instance
33
+ " scenegraph" attribute
34
+ pointer to the node's implementation
35
+ scenegraph (at the moment, this is not
36
+ actually used for anything)
37
+ " PROTO" attribute
38
+ stores the PROTO name of the node
39
+ externalURL attribute
40
+ stores the MFString url for the node's
41
+ externproto definition if appropriate
42
+ toString method
43
+ convenience access to the lineariser
44
+ to give VRML97-formatted representation of
45
+ the node
46
+ """
47
+
48
+ DEF = fieldtypes.SFString(' DEF', 1, '')
49
+ # scenegraph = None # will be created below...
50
+ # rootSceneGraph = None # will be created below
51
+ externalURL = fieldtypes.MFString('externalURL', 1)
52
+ PROTO = ""
53
+
54
+ def __init__(self, **namedarguments):
55
+ """Initialise the node with appropriate named args
56
+
57
+ All properties/attributes must be specified with
58
+ named arguments, and the property/attribute must
59
+ exist within the Node's class/prototype.
60
+
61
+ This will raise AttributeError/ValueError/TypeError
62
+ if the values or the property names are inappropriate.
63
+
64
+ Note that all Node objects have the attribute/property
65
+ exposedField SFString DEF ""
66
+ defined. You may therefore specify a DEF name by
67
+ passing it as a named argument.
68
+ """
69
+ for key, value in namedarguments.items():
70
+ try:
71
+ f = getField(self, key)
72
+ except AttributeError:
73
+ raise AttributeError(
74
+ """Unrecognised attribute %r for node type %r"""
75
+ % (key, self.__class__.__name__)
76
+ )
77
+ else:
78
+ if not (hasattr(f, '__get__') and hasattr(f, '__set__')):
79
+ raise TypeError(
80
+ """Attempt to set a non-field attribute %s to %s for node type %s"""
81
+ % (key, value, self.__class__.__name__)
82
+ )
83
+ f.fset(self, value)
84
+
85
+ def __str__(self):
86
+ """Get a friendly representation of the Node"""
87
+ if 'DEF' in self.__dict__ and defName(self):
88
+ return """%s( DEF=%r @0x%X )""" % (
89
+ self.__class__.__name__,
90
+ defName(self),
91
+ id(self),
92
+ )
93
+ else:
94
+ return """%s( @0x%X )""" % (
95
+ self.__class__.__name__,
96
+ id(self),
97
+ )
98
+
99
+ def __repr__(self):
100
+ """Get a code-like representation of the Node
101
+
102
+ Basically every attribute except for sub-nodes values
103
+ are returned as a full representation.
104
+ """
105
+ attributes = []
106
+ for field in getFields(self):
107
+ if field.name in self.__dict__:
108
+ value = getattr(self, field.name)
109
+ if isinstance(value, Node):
110
+ representation = str(value)
111
+ else:
112
+ representation = repr(value)
113
+ attributes.append(
114
+ """%s = %s"""
115
+ % (
116
+ field.name,
117
+ representation,
118
+ )
119
+ )
120
+
121
+ return """%s(\n\t%s\n)""" % (
122
+ self.__class__.__name__,
123
+ ",\n\t".join(attributes),
124
+ )
125
+
126
+ def copy(self, copier=None):
127
+ """Copy this node for copier"""
128
+ if copier is None:
129
+ copier = copiermodule.Copier()
130
+ previous = copier.use(self)
131
+ if previous is not None:
132
+ return previous
133
+ dictionary = {}
134
+ for field in getFields(self):
135
+ if field.fhas(self):
136
+ dictionary[field.name] = field.copy(self, copier)
137
+ newNode = type(self).__new__(type(self))
138
+ newNode.__dict__.update(dictionary)
139
+ copier.use(self, newNode)
140
+ return newNode
141
+
142
+ def toString(self, **namedargs):
143
+ '''Generate a VRML 97-syntax string representing this Prototype
144
+ **namedargs -- key:value
145
+ passed arguments for the linearisation object
146
+ see lineariser4.Lineariser
147
+ '''
148
+ from vrml.vrml97 import linearise
149
+
150
+ return linearise.linearise(self, **namedargs)
151
+
152
+
153
+ class PrototypedNode(object):
154
+ """Prototyped node mix-in
155
+
156
+ Note the presence of a " scenegraph" property
157
+ for the node created below (due to mutual dependencies).
158
+ This is filled by the instantiate method to provide the
159
+ actual implementation of the node.
160
+ """
161
+
162
+ def __init__(self, *arguments, **namedarguments):
163
+ """Initialise the node with appropriate named args
164
+
165
+ Also attempts to instantiate the sub-node scenegraph
166
+ for the PrototypedNode
167
+ """
168
+ PrototypedNode.instantiate(self)
169
+ super(PrototypedNode, self).__init__(*arguments, **namedarguments)
170
+
171
+ def instantiate(self):
172
+ """Make a copy of the class scenegraph-template for this node
173
+
174
+ Also needs to bind IS mappings/routes for the template,
175
+ and negotiate not-yet-loaded external prototypes and
176
+ the like.
177
+ """
178
+ from vrml import route
179
+
180
+ isMappings = None
181
+ for cls in self.__class__.__mro__[:-1]:
182
+ template = PrototypedNode.scenegraph.fget(cls)
183
+ if template:
184
+ # use ismaps for the instantiated scenegraph
185
+ isMappings = list(ismaps(cls).items())
186
+ break
187
+ if isMappings is None:
188
+ # no scenegraph defined...
189
+ from vrml.vrml97 import scenegraph
190
+
191
+ template = scenegraph.SceneGraph()
192
+ PrototypedNode.scenegraph.fset(cls, template)
193
+ isMappings = []
194
+ # raise ValueError( """Attempting to instantiate a prototyped node with no scenegraph defined: %s"""%( self,))
195
+
196
+ copier = copiermodule.Copier()
197
+ sg = template.copy(copier)
198
+ for fieldName, mappings in isMappings:
199
+ sourceField = getField(self, fieldName)
200
+ for destination, destinationField in mappings:
201
+ r = route.IS(
202
+ source=self,
203
+ sourceField=fieldName,
204
+ destination=destination,
205
+ destinationField=destinationField,
206
+ )
207
+ sg.routes.append(r)
208
+ if hasattr(sourceField, 'getDefault'):
209
+ default = sourceField.getDefault()
210
+ try:
211
+ getField(destination, destinationField).fset(
212
+ destination,
213
+ default,
214
+ notify=0,
215
+ )
216
+ except AttributeError:
217
+ pass
218
+ PrototypedNode.scenegraph.fset(self, sg)
219
+
220
+ def renderedChildren(self, types=None):
221
+ """Get the rendered children of the scenegraph"""
222
+ if types:
223
+ return [
224
+ node
225
+ for node in PrototypedNode.scenegraph.fget(self).children
226
+ if isinstance(node, types)
227
+ ]
228
+ else:
229
+ return PrototypedNode.scenegraph.fget(self).children
230
+
231
+
232
+ def prototype(
233
+ name,
234
+ fields=(),
235
+ sceneGraph=None,
236
+ externalURL=None,
237
+ baseClasses=(PrototypedNode, Node),
238
+ ):
239
+ """Build a new prototype class
240
+
241
+ name -- string name
242
+ fields -- sequence of vrml.field objects
243
+ sceneGraph -- the source scenegraph for prototyped nodes
244
+ externalURL -- MFString URL or None
245
+ baseClasses -- base classes for the new class
246
+ """
247
+ environment = {
248
+ 'PROTO': name,
249
+ }
250
+ for field in fields:
251
+ environment[field.name] = field
252
+ returnValue = type(
253
+ name,
254
+ baseClasses,
255
+ environment,
256
+ )
257
+ if sceneGraph is not None:
258
+ setSceneGraph(returnValue, sceneGraph)
259
+ if externalURL is not None:
260
+ setExternalURL(returnValue, externalURL)
261
+ return returnValue
262
+
263
+
264
+ class NullNode(Node):
265
+ '''NULL SFNode value
266
+ There should only be a single NULL instance for
267
+ any particular system. It should, for all intents and
268
+ purposes just sit there inertly
269
+ '''
270
+
271
+ PROTO = 'NULL'
272
+
273
+ def __repr__(self):
274
+ """Get code-like representation of NULL node"""
275
+ return '<NULL vrml SFNode>'
276
+
277
+ def __nonzero__(self):
278
+ """Make the NULL node evaluate to false"""
279
+ return False
280
+
281
+ __bool__ = __nonzero__
282
+
283
+ def __eq__(self, other):
284
+ """Compare the NULL node to other objects"""
285
+ try:
286
+ if protoName(self) == protoName(other):
287
+ return 0
288
+ except (AttributeError, TypeError, ValueError):
289
+ return -1 # could be 1, doesn't really matter
290
+
291
+ def clone(self):
292
+ """Replicate the null object (return another pointer to it)"""
293
+ return self
294
+
295
+ def __str__(self):
296
+ """Get a human-friendly representation of the NULL node"""
297
+ return "NULL"
298
+
299
+
300
+ NULL = NullNode()
301
+
302
+
303
+ class _SFNode(object):
304
+ """Base-class for SFNode-type fields
305
+
306
+ The optionally restricted SFNode field type
307
+ allows a node to hold a reference to another node
308
+ within the directed acyclic graph.
309
+
310
+ There are two primary attributes:
311
+
312
+ requiredTypes -- a type or tuple of types that
313
+ are acceptable as values for the field
314
+ allowNULL -- whether to allow the NULL node as
315
+ a value for the field
316
+ """
317
+
318
+ nodes = 1
319
+ requiredTypes = ()
320
+ allowNULL = 1
321
+
322
+ def fset(self, client, value, notify=1):
323
+ """Set the client's value for this property
324
+
325
+ notify -- if true send a notification event
326
+
327
+ The SFNode tries to update the value's root
328
+ attribute to point to the root of the client
329
+ *iff* the value doesn't currently point at
330
+ a valid root. (That is, it only updates root
331
+ if there is no current root). This is done
332
+ without sending notify events.
333
+ """
334
+ value = super(_SFNode, self).fset(client, value, notify)
335
+ if value:
336
+ clientRoot = Node.rootSceneGraph.fget(client)
337
+ if clientRoot:
338
+ valueRoot = Node.rootSceneGraph.fget(value)
339
+ if not valueRoot:
340
+ Node.rootSceneGraph.fset(value, clientRoot, notify=0)
341
+ return value
342
+
343
+ def defaultDefault(self):
344
+ """Default SFNode value"""
345
+ return NULL
346
+
347
+ def coerce(self, value):
348
+ """Coerce value to an SFNode reference"""
349
+ if self.requiredTypes and isinstance(value, self.requiredTypes):
350
+ return value
351
+ elif value is None and self.allowNULL:
352
+ return NULL
353
+ elif isinstance(value, str):
354
+ raise ValueError(
355
+ """SFNode field %s was set to a string, not currently supported: %s"""
356
+ % (self, value[:30])
357
+ )
358
+ elif isinstance(value, field.SEQUENCE_TYPES) and len(value) == 1:
359
+ return self.coerce(value[0])
360
+ elif not self.requiredTypes:
361
+ return value
362
+ else:
363
+ raise ValueError(
364
+ """Attempted to set value for an %s field which is not compatible: %s, needed instance of %s"""
365
+ % (self.name, repr(value), self.requiredTypes)
366
+ )
367
+
368
+ def vrmlstr(self, value, lineariser):
369
+ """Convert the given value to a VRML97 representation"""
370
+ return lineariser._linear(value)
371
+
372
+
373
+ class SFNode(_SFNode, field.Field):
374
+ """(Restricted) SFNode type
375
+
376
+ This is the publically available SFNode type,
377
+ a sub-class of _SFNode and field.Field
378
+ """
379
+
380
+
381
+ SFNode.requiredTypes = (Node,)
382
+
383
+
384
+ class SFNodeEvt(_SFNode, field.Event):
385
+ fieldType = 'SFNode'
386
+
387
+
388
+ field.register(SFNode)
389
+ field.register(SFNodeEvt)
390
+
391
+
392
+ class WeakSFNode(_SFNode, field.WeakField, field.Field):
393
+ """Weak-referenced SFNode field-type"""
394
+
395
+ fieldType = 'WeakSFNode'
396
+
397
+
398
+ class RootScenegraphNode(WeakSFNode):
399
+ fieldType = 'RootScenegraphNode'
400
+
401
+ def fset(self, client, value, notify=1):
402
+ """Set the root scenegraph node (recursively)
403
+
404
+ TODO: this will blow up on cyclic graphs!
405
+ """
406
+ result = super(RootScenegraphNode, self).fset(client, value, notify)
407
+ for field in getFields(client.__class__):
408
+ if (
409
+ isinstance(field, SFNode)
410
+ and not isinstance(field, RootScenegraphNode)
411
+ and not field is PrototypedNode.scenegraph
412
+ ):
413
+ try:
414
+ child = field.__get__(client)
415
+ except ValueError:
416
+ pass
417
+ else:
418
+ self.fset(child, value, notify=False)
419
+ elif isinstance(field, MFNode):
420
+ try:
421
+ for child in field.__get__(client):
422
+ self.fset(child, value, notify=False)
423
+ except AttributeError:
424
+ pass
425
+ elif field.name == ' DEF':
426
+ try:
427
+ DEF = field.__get__(client)
428
+ value.regDefName(DEF, client)
429
+ except AttributeError:
430
+ pass
431
+ return result
432
+
433
+
434
+ field.register(WeakSFNode)
435
+ field.register(RootScenegraphNode)
436
+
437
+ PrototypedNode.scenegraph = SFNode(' scenegraph', 1, NULL)
438
+ Node.rootSceneGraph = RootScenegraphNode(' root', 1, NULL)
439
+ assert PrototypedNode.scenegraph.name == " scenegraph", PrototypedNode.scenegraph.name
440
+ assert Node.rootSceneGraph.name == " root", Node.rootSceneGraph.name
441
+
442
+
443
+ def _changeSender(nodeRef, field):
444
+ """Utility function to send node-change messages on olist updates"""
445
+
446
+ def onOListChange(sender, signal, value):
447
+ client = nodeRef()
448
+ if client:
449
+ dispatcher.send(
450
+ ('set', field),
451
+ client,
452
+ value=sender,
453
+ subsignal=signal,
454
+ subvalue=value,
455
+ )
456
+
457
+ return onOListChange
458
+
459
+
460
+ class _MFNode(object):
461
+ """(Restricted) MFNode field-type-definition"""
462
+
463
+ nodes = 1
464
+ defaultDefault = olist.OList
465
+ baseSFNode = SFNode('GeneralSFNode')
466
+ baseObjectType = olist.OList
467
+
468
+ def fset(self, client, value, notify=1):
469
+ """Set the client's value for this property
470
+
471
+ notify -- if true send a notification event
472
+
473
+ The MFNode tries to update the value's root
474
+ attribute to point to the root of the client
475
+ *iff* the value doesn't currently point at
476
+ a valid root. (That is, it only updates root
477
+ if there is no current root). This is done
478
+ without sending notify events.
479
+ """
480
+ previous = client.__dict__.get(self.name)
481
+ if previous is not None:
482
+ previous[:] = self.coerce(value)
483
+ value = previous
484
+ else:
485
+ value = super(_MFNode, self).fset(client, value, notify)
486
+ # register for updates to the list...
487
+ # we just send "changed" events for the field whenever
488
+ # there's an update to the list... a bit wasteful, as
489
+ # our clients might want to know about just the changed
490
+ # values, but for now...
491
+ value.setSender(client, field=self)
492
+ cs = _changeSender(weakref.ref(client), self)
493
+ dispatcher.connect(
494
+ cs,
495
+ sender=client,
496
+ signal=olist.OList.DEL_CHILD_EVT,
497
+ weak=False, # don't weakref receiver so it will hang around...
498
+ )
499
+ dispatcher.connect(
500
+ cs,
501
+ sender=client,
502
+ signal=olist.OList.NEW_CHILD_EVT,
503
+ weak=False, # don't weakref receiver so it will hang around...
504
+ )
505
+ if value:
506
+ clientRoot = Node.rootSceneGraph.fget(client)
507
+ if clientRoot:
508
+ for val in value:
509
+ valueRoot = Node.rootSceneGraph.fget(val)
510
+ if not valueRoot:
511
+ Node.rootSceneGraph.fset(val, clientRoot, notify=0)
512
+ return value
513
+
514
+ def coerce(self, value):
515
+ """Coerce value to an MFNode list-of-objects"""
516
+ SF = self.__class__.baseSFNode
517
+ if SF.requiredTypes and isinstance(value, SF.requiredTypes):
518
+ return self.baseObjectType([value])
519
+ elif not value:
520
+ return self.baseObjectType([])
521
+ elif isinstance(value, field.SEQUENCE_TYPES):
522
+ return self.baseObjectType([SF.coerce(item) for item in value])
523
+ else:
524
+ raise ValueError(
525
+ """Attempted to set value for an %s field which is not compatible: %s"""
526
+ % (self.name, repr(value))
527
+ )
528
+
529
+ def vrmlstr(self, value, lineariser):
530
+ """Convert the given value to a VRML97 representation"""
531
+ return lineariser._mfnode(value)
532
+
533
+ def copyValue(self, value, copier=None):
534
+ """Copy a value for copier"""
535
+ SF = self.__class__.baseSFNode
536
+ return [SF.copyValue(node, copier) for node in value]
537
+
538
+
539
+ ##class WeakMFNode( MFNode ):
540
+ ## """Weak-referencing version of an MFNode field-type"""
541
+ ## baseObjectType = weaklist.WeakList
542
+ ## fieldType = 'WeakMFNode'
543
+
544
+
545
+ class MFNode(_MFNode, field.Field):
546
+ """MFNode Field class"""
547
+
548
+
549
+ class MFNodeEvt(_MFNode, field.Event):
550
+ """MFNode Event class"""
551
+
552
+ fieldType = 'MFNode'
553
+
554
+
555
+ field.register(MFNode)
556
+ ##field.register( WeakMFNode )
557
+ field.register(MFNodeEvt)
558
+
559
+ ISMAPS = weakkeydictfix.WeakKeyDictionary()
560
+
561
+
562
+ def ismaps(node):
563
+ """Get the isMaps for the given node
564
+
565
+ Returns a field-name:(sub-node,field) mapping
566
+ Not currently functional
567
+ """
568
+ current = ISMAPS.setdefault(node, {})
569
+ return current
vrml/nodepath.py ADDED
@@ -0,0 +1,70 @@
1
+ """Representation and manipulation of scenegraph paths
2
+ """
3
+ from vrml import node, weaklist
4
+ try:
5
+ xrange
6
+ except NameError:
7
+ xrange = range
8
+
9
+ class NodePath( list ):
10
+ """Path within a scenegraph from root to particular node
11
+
12
+ Has minimal operations, most high-level functionality is
13
+ provided by sub-classes such as vrml.vrml97.nodepath and
14
+ OpenGLContext.scenegraph.nodepath
15
+ """
16
+ def __repr__( self ):
17
+ """Code-like representation of the node path
18
+
19
+ Note: this doesn't use super for determining
20
+ the base representation, as that might wind up
21
+ creating a name like:
22
+ WeakNodePath( WeakTuple( Node, Node,...))
23
+ """
24
+ return '%s(%s)'%(self.__class__.__name__, list.__repr__( self ))
25
+ def __str__( self ):
26
+ """Simple representation of a node-path for human consumption"""
27
+ return "%s(%s)"%(
28
+ self.__class__.__name__,
29
+ "->".join([
30
+ str( N )
31
+ for N in self
32
+ ]))
33
+ def common (self, other):
34
+ """Return the common root sub-path between ourselves and other
35
+
36
+ If there is no common sub-root, returns an empty path
37
+ """
38
+ result = []
39
+ for index in range( min(len(self),len(other))):
40
+ if self [index] is other [index]:
41
+ result.append (self [index])
42
+ else:
43
+ break
44
+ return self.__class__(result)
45
+ def __add__(self, other):
46
+ """Return a new path with other as tail"""
47
+ if isinstance( other, node.Node ):
48
+ other = [other]
49
+ return self.__class__( super(NodePath, self).__add__( other))
50
+ def __getslice__(self, start, stop):
51
+ """Return a new path with our items from start to stop"""
52
+ return self.__class__(super (NodePath, self).__getslice__(start, stop))
53
+ def __eq__( self, other ):
54
+ """Check whether we are equal to another path"""
55
+ if len(self) != len(other):
56
+ return 0
57
+ for index in range(len(self)):
58
+ if self [index] is not other [index]:
59
+ return 0
60
+ return 1
61
+
62
+ class WeakNodePath( NodePath, weaklist.WeakList ):
63
+ """Node path that uses weak-references to nodes
64
+
65
+ You hold strong references to these paths, then
66
+ wrap all uses of them with checks for
67
+ weakref.ReferenceError to check for dead paths.
68
+ """
69
+
70
+