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/fieldtypes.py ADDED
@@ -0,0 +1,1491 @@
1
+ """Definitions of the standard VRML97 field-types
2
+
3
+ These are all of the "low-level" field-types
4
+ (i.e. not nodes) defined by VRML97. Each has
5
+ a canonical in-memory storage format so that
6
+ code can rely on that format when dealing with
7
+ the field values.
8
+
9
+ We use Numeric Python arrays whereever possible.
10
+ """
11
+
12
+ import operator
13
+ from vrml import field, csscolors, arrays
14
+ from ._bytes import unicode, long
15
+
16
+ try:
17
+ xrange
18
+ except NameError:
19
+ xrange = range
20
+ try:
21
+ reduce
22
+ except NameError:
23
+ from functools import reduce
24
+
25
+ import sys
26
+
27
+ MAX_INT = getattr(sys, 'maxint', None) or getattr(sys, 'maxsize', None)
28
+
29
+ DOUBLE_TYPE = arrays.typeCode(arrays.array([0], 'd'))
30
+ FLOAT_TYPE = arrays.typeCode(arrays.array([0], 'f'))
31
+ INT_TYPE = arrays.typeCode(arrays.array([0], 'i'))
32
+ UINT_TYPE = arrays.typeCode(arrays.array([0], 'I'))
33
+
34
+
35
+ def _collapse(inlist, isinstance=isinstance, ltype=list, maxint=MAX_INT):
36
+ '''
37
+ Destructively flatten a list hierarchy to a single level.
38
+ Non-recursive, and (as far as I can see, doesn't have any
39
+ glaring loopholes).
40
+ Further speedups and obfuscations by Tim Peters :)
41
+ '''
42
+ try:
43
+ # for every possible index
44
+ for ind in xrange(maxint):
45
+ # while that index currently holds a list
46
+ while isinstance(inlist[ind], ltype):
47
+ # expand that list into the index (and subsequent indicies)
48
+ inlist[ind : ind + 1] = inlist[ind]
49
+ # ind = ind+1
50
+ except IndexError:
51
+ pass
52
+ return inlist
53
+
54
+
55
+ def collapse(inlist):
56
+ '''
57
+ As _collapse, but works on a copy of the inlist
58
+ '''
59
+ return _collapse(list(inlist))
60
+
61
+
62
+ def _linvalues(lineariser):
63
+ """Get the linearisation values for a lineariser"""
64
+ if lineariser is None:
65
+ from vrml.vrml97 import linearise
66
+
67
+ return linearise.defaults
68
+ else:
69
+ return lineariser.linvalues
70
+
71
+
72
+ def SFString_vrmlstr(value, lineariser=None):
73
+ """Convert the given value to a VRML97 representation"""
74
+ return '"%s"' % ('\\"'.join('\\\\'.join(value.split('\\')).split('"')))
75
+
76
+
77
+ def MFString_vrmlstr(value, lineariser=None):
78
+ """Convert the given value to a VRML97 representation"""
79
+ if not value:
80
+ return "[ ]"
81
+ anyobject = [SFString_vrmlstr(v, lineariser) for v in value]
82
+ linvalues = _linvalues(lineariser)
83
+ length = reduce(operator.add, [len(obj) for obj in anyobject])
84
+ if length > 60:
85
+ sep = '%(subelspacer)s\n%(curindent)s%(indent)s' % linvalues
86
+ if len(anyobject) > 1:
87
+ result = ['[']
88
+ else:
89
+ result = []
90
+ for element in anyobject:
91
+ if result:
92
+ result.append(sep)
93
+ result.append(element)
94
+ if len(anyobject) > 1:
95
+ result.append('\n%(curindent)s]\n' % linvalues)
96
+ return "".join(result)
97
+ elif len(anyobject) > 1:
98
+ return '[ %s ]' % linvalues['subelspacer'].join(anyobject)
99
+ else:
100
+ return linvalues['subelspacer'].join(anyobject)
101
+
102
+
103
+ def SFFloat_vrmlstr(value, lineariser=None):
104
+ """Convert floats to (compact) VRML97 representation"""
105
+ rpr = str(value)
106
+ if rpr == '0.0':
107
+ return '0'
108
+ elif rpr[:2] == '0.':
109
+ return rpr[1:]
110
+ elif rpr[:3] == '-0.':
111
+ return '-' + rpr[2:]
112
+ elif rpr[-2:] == '.0':
113
+ return rpr[:-2]
114
+ else:
115
+ return rpr
116
+
117
+
118
+ def MFSimple_vrmlstr(value, lineariser=None):
119
+ """Convert value to a VRML97 representation"""
120
+ linvalues = _linvalues(lineariser)
121
+ stringreps = [str(obj) for obj in value]
122
+ stringsets = []
123
+ setLength = 100 # 100 is arbitrary
124
+ while stringreps:
125
+ stringsets.append(linvalues['numsep'].join(stringreps[:setLength]))
126
+ del stringreps[:setLength]
127
+ return '[ %s ]' % ('\n'.join(stringsets))
128
+
129
+
130
+ if str is bytes:
131
+
132
+ class _SFString(object):
133
+ """SFString field/event type base-class"""
134
+
135
+ defaultDefault = ""
136
+
137
+ def coerce(self, value):
138
+ """Coerce the given value to our type
139
+ Allowable types:
140
+ simple string -> unchanged
141
+ unicode string -> utf-8 encoded
142
+
143
+ sequence of length == 1 where first element is a string -> returns first element
144
+ sequence of length > 1 where all elements are strings -> returns string.join( value, '')
145
+ """
146
+ if isinstance(value, unicode):
147
+ return value.encode('utf-8')
148
+ elif isinstance(value, field.SEQUENCE_TYPES):
149
+ if value and len(value) == 1:
150
+ value = value[0]
151
+ elif not value:
152
+ value = ""
153
+ else:
154
+ value = "".join(value)
155
+ if not isinstance(value, bytes):
156
+ value = bytes(value)
157
+ return value
158
+
159
+ def check(self, value):
160
+ "Raise ValueError if isn't correct type"
161
+ if not isinstance(value, (bytes, unicode)):
162
+ return 0
163
+ return 1
164
+
165
+ coerce = classmethod(coerce)
166
+ check = classmethod(check)
167
+ vrmlstr = staticmethod(SFString_vrmlstr)
168
+
169
+ else:
170
+
171
+ class _SFString(object):
172
+ """SFString field/event type base-class"""
173
+
174
+ defaultDefault = ""
175
+
176
+ def coerce(self, value):
177
+ """Coerce the given value to our type
178
+ Allowable types:
179
+ simple string -> unchanged
180
+ unicode string -> utf-8 encoded
181
+
182
+ sequence of length == 1 where first element is a string -> returns first element
183
+ sequence of length > 1 where all elements are strings -> returns string.join( value, '')
184
+ """
185
+ if isinstance(value, bytes):
186
+ return value.decode('utf-8')
187
+ elif isinstance(value, field.SEQUENCE_TYPES):
188
+ if value and len(value) == 1:
189
+ value = value[0]
190
+ elif not value:
191
+ value = u""
192
+ else:
193
+ value = u"".join(value)
194
+ if not isinstance(value, unicode):
195
+ value = unicode(value)
196
+ return value
197
+
198
+ def check(self, value):
199
+ "Raise ValueError if isn't correct type"
200
+ if not isinstance(value, unicode):
201
+ return 0
202
+ return 1
203
+
204
+ coerce = classmethod(coerce)
205
+ check = classmethod(check)
206
+ vrmlstr = staticmethod(SFString_vrmlstr)
207
+
208
+
209
+ class _MFString(object):
210
+ """MFString field/event type base-class"""
211
+
212
+ defaultDefault = list
213
+
214
+ def coerce(self, value):
215
+ """Coerce the given value to our type
216
+ Allowable types:
217
+ simple string -> wrapped in a list
218
+ sequence of strings (of any length) -> equivalent list returned
219
+ """
220
+ if isinstance(value, (str, unicode)):
221
+ value = [value]
222
+ try:
223
+ return [SFString.coerce(item) for item in value]
224
+ except ValueError as error:
225
+ raise ValueError(
226
+ """Attempted to set value %r for an %s field which is not compatible: %s"""
227
+ % (value, self.typeName(), error)
228
+ )
229
+
230
+ def check(self, value):
231
+ "Raise ValueError if isn't correct type"
232
+ if isinstance(value, list):
233
+ if not filter(None, [isinstance(item, (str, unicode)) for item in value]):
234
+ return 1
235
+ return 0
236
+
237
+ def copyValue(self, value, copier=None):
238
+ """Copy a value for copier"""
239
+ return value[:]
240
+
241
+ vrmlstr = staticmethod(MFString_vrmlstr)
242
+
243
+
244
+ class _SFBool(object):
245
+ """SFBool field/event type base-class"""
246
+
247
+ defaultDefault = 0
248
+
249
+ def coerce(self, value):
250
+ """Coerce the given value to our type
251
+ Allowable types:
252
+ any object with true/false protocol
253
+ """
254
+ if isinstance(value, (str, unicode)):
255
+ try:
256
+ value = int(value)
257
+ except (ValueError, TypeError):
258
+ if value.lower() == 'true':
259
+ value = True
260
+ elif value.lower() == 'false':
261
+ value = False
262
+ if value:
263
+ return 1
264
+ else:
265
+ return 0
266
+
267
+ def check(self, value):
268
+ """Check that the given value is of exactly expected type"""
269
+ if value in (0, 1):
270
+ return 1
271
+ return 0
272
+
273
+ def vrmlstr(self, value, lineariser=None):
274
+ """Convert the given value to a VRML97 representation"""
275
+ if value:
276
+ return 'TRUE'
277
+ else:
278
+ return 'FALSE'
279
+
280
+
281
+ class _SFInt32(object):
282
+ """SFInt32 field/event type base-class"""
283
+
284
+ defaultDefault = 0
285
+
286
+ def coerce(self, value):
287
+ """Coerce the given value to our type
288
+ Allowable types:
289
+ any object with true/false protocol
290
+ """
291
+ try:
292
+ return int(value)
293
+ except ValueError:
294
+ raise ValueError(
295
+ """Attempted to set value for an %s field which is not compatible: %s"""
296
+ % (self.typeName(), repr(value))
297
+ )
298
+
299
+ def check(self, value):
300
+ """Check that the given value is of exactly expected type"""
301
+ if isinstance(value, int):
302
+ return 1
303
+ return 0
304
+
305
+ def vrmlstr(self, value, lineariser=None):
306
+ """Convert the given value to a VRML97 representation"""
307
+ try:
308
+ return str(int(value))
309
+ except OverflowError:
310
+ base = str(value)
311
+ if base and base[-1] in ('l', 'L'):
312
+ base = base[:-1]
313
+ return base + ' # Overly long number\n'
314
+
315
+
316
+ class _SFUInt32(_SFInt32):
317
+ """SFUInt32 base-class"""
318
+
319
+ def coerce(self, value):
320
+ """Coerce the given value to our type
321
+ Allowable types:
322
+ any object with true/false protocol
323
+ """
324
+ try:
325
+ return long(value)
326
+ except ValueError:
327
+ raise ValueError(
328
+ """Attempted to set value for an %s field which is not compatible: %s"""
329
+ % (self.typeName(), repr(value))
330
+ )
331
+
332
+ def check(self, value):
333
+ """Check that the given value is of exactly expected type"""
334
+ if isinstance(value, long):
335
+ return 1
336
+ return 0
337
+
338
+ def vrmlstr(self, value, lineariser=None):
339
+ """Convert the given value to a VRML97 representation"""
340
+ base = str(long(value))
341
+ if base[-1] in ('l', 'L'):
342
+ base = base[:-1]
343
+ return base
344
+
345
+
346
+ class _SFFloat(object):
347
+ """SFFloat field/event type base-class"""
348
+
349
+ defaultDefault = 0.0
350
+
351
+ def coerce(self, value):
352
+ """Coerce the given value to our type
353
+ Allowable types:
354
+ any object with true/false protocol
355
+ """
356
+ try:
357
+ return float(value)
358
+ except ValueError:
359
+ raise ValueError(
360
+ """Attempted to set value for an %s field which is not compatible: %s"""
361
+ % (self.typeName(), repr(value))
362
+ )
363
+
364
+ def check(self, value):
365
+ """Check that value is of precisely the expected data type"""
366
+ if isinstance(value, float):
367
+ return 1
368
+ return 0
369
+
370
+ vrmlstr = staticmethod(SFFloat_vrmlstr)
371
+
372
+
373
+ class _SFTime(_SFFloat):
374
+ """SFTime field/event type base-class"""
375
+
376
+ defaultDefault = 0.0
377
+
378
+
379
+ class _MFInt32(object):
380
+ """MFInt32 field/event type base-class
381
+
382
+ Stored as a flat Numeric-python array
383
+ """
384
+
385
+ defaultDefault = list
386
+ arrayDataType = 'i'
387
+ acceptedTypes = ('i', INT_TYPE)
388
+ base_converter = int
389
+
390
+ def coerce(self, value):
391
+ """Base coercion mechanism for multiple-value integer fields"""
392
+ if isinstance(value, (str, unicode)):
393
+ value = [self.base_converter(x) for x in value.replace(',', ' ').split()]
394
+ if isinstance(value, field.NUMERIC_TYPES):
395
+ return arrays.array([int(value)], self.arrayDataType)
396
+ elif isinstance(value, arrays.ArrayType):
397
+ if arrays.typeCode(value) not in self.acceptedTypes:
398
+ value = value.astype(self.arrayDataType)
399
+ return arrays.contiguous(arrays.ravel(value))
400
+ elif isinstance(value, field.SEQUENCE_TYPES):
401
+ return arrays.array(
402
+ [int(obj) for obj in value],
403
+ self.arrayDataType,
404
+ )
405
+ elif not value:
406
+ return arrays.array([], self.arrayDataType)
407
+ raise ValueError(
408
+ """Attempted to set value for an %s field which is not compatible: %s"""
409
+ % (self.typeName(), repr(value))
410
+ )
411
+
412
+ vrmlstr = staticmethod(MFSimple_vrmlstr)
413
+
414
+ def copyValue(self, value, copier=None):
415
+ """Copy a value for copier"""
416
+ return arrays.array(value, arrays.typeCode(value))
417
+
418
+
419
+ class _MFUInt32(_MFInt32):
420
+ """Unsigned integer version of MFInt32 (mostly for indices)"""
421
+
422
+ defaultDefault = list
423
+ arrayDataType = 'I'
424
+ base_converter = long
425
+ acceptedTypes = ('I', UINT_TYPE)
426
+
427
+
428
+ class _SFImage(_MFInt32):
429
+ """SFImage field/event type base-class
430
+
431
+ SFImage = MFInt32, should do something more
432
+ intelligent, such as auto-compiling those to
433
+ mip-mapped images, or at least storing them
434
+ efficiently.
435
+ """
436
+
437
+ defaultDefault = list
438
+ arrayDataType = 'I'
439
+ acceptedTypes = ('I', UINT_TYPE)
440
+
441
+
442
+ class _MFFloat(object):
443
+ """MFFloat field/event type base-class
444
+
445
+ Stored as a flat Numeric-python array
446
+ """
447
+
448
+ defaultDefault = list
449
+ acceptedTypes = ('d', DOUBLE_TYPE)
450
+ targetType = DOUBLE_TYPE
451
+
452
+ def coerce(self, value):
453
+ """Base coercion mechanism for floating point field types"""
454
+ if isinstance(value, (str, unicode)):
455
+ value = [float(x) for x in value.replace(',', ' ').split()]
456
+ if isinstance(value, field.NUMERIC_TYPES):
457
+ return arrays.array([float(value)], self.targetType)
458
+ elif isinstance(value, arrays.ArrayType):
459
+ if arrays.typeCode(value) not in self.acceptedTypes:
460
+ value = value.astype(self.targetType)
461
+ return arrays.contiguous(arrays.ravel(value))
462
+ elif isinstance(value, field.SEQUENCE_TYPES):
463
+ return arrays.array(
464
+ [float(x) for x in collapse(value)],
465
+ self.targetType,
466
+ )
467
+ elif not value:
468
+ return arrays.array([], self.targetType)
469
+ raise ValueError(
470
+ """Attempted to set value for an %s field which is not compatible: %s"""
471
+ % (self.typeName(), repr(value))
472
+ )
473
+
474
+ vrmlstr = staticmethod(MFSimple_vrmlstr)
475
+
476
+ def copyValue(self, value, copier=None):
477
+ """Copy a value for copier"""
478
+ return arrays.array(value, arrays.typeCode(value))
479
+
480
+
481
+ class _MFFloat32(_MFFloat):
482
+ """32-BIT floating-point type"""
483
+
484
+ acceptedTypes = ('f', FLOAT_TYPE)
485
+ targetType = FLOAT_TYPE
486
+ fieldType = 'MFFloat32'
487
+
488
+
489
+ class _MFTime(_MFFloat):
490
+ """MFTime field/event type base-class
491
+
492
+ Stored as a flat Numeric-python array
493
+ """
494
+
495
+
496
+ class _SFVec(object):
497
+ """SFVecXX field/event type base-class
498
+
499
+ Stored as a Numeric-python double array of self.length
500
+ """
501
+
502
+ acceptedTypes = ('d', DOUBLE_TYPE)
503
+ targetType = DOUBLE_TYPE
504
+ dimension = (3,) # our dimension...
505
+
506
+ @property
507
+ def length(self):
508
+ import operator
509
+
510
+ self.length = reduce(operator.mul, self.dimension)
511
+ return self.length
512
+
513
+ def defaultDefault(self):
514
+ """Default default value for vectors/colours"""
515
+ return arrays.zeros(self.dimension, self.targetType)
516
+
517
+ def coerce(self, value):
518
+ """Base coercion mechanism for vector-like field types"""
519
+ if isinstance(value, (str, unicode)):
520
+ value = [float(x) for x in value.replace(',', ' ').split()]
521
+ if isinstance(value, (int, long, float)):
522
+ value = arrays.zeros(self.dimension, self.targetType)
523
+ value[:] = float(value)
524
+ elif isinstance(value, arrays.ArrayType):
525
+ if arrays.typeCode(value) not in self.acceptedTypes:
526
+ value = value.astype(self.targetType)
527
+ value = value.reshape(self.dimension)
528
+ elif isinstance(value, field.SEQUENCE_TYPES):
529
+ value = arrays.asarray([float(x) for x in collapse(value)], self.targetType)
530
+ value.reshape(self.dimension)
531
+ else:
532
+ try:
533
+ value = arrays.asarray(value, self.targetType)
534
+ except Exception:
535
+ raise ValueError(
536
+ """Attempted to set value for an %s field which is not compatible: %s"""
537
+ % (self.typeName(), repr(value))
538
+ )
539
+ else:
540
+ value.reshape(self.dimension)
541
+ if value.shape != self.dimension:
542
+ raise ValueError(
543
+ """%s value of incorrect shape (is %s, should be %s)"""
544
+ % (
545
+ self.__class__.__name__,
546
+ value.shape,
547
+ self.dimension,
548
+ )
549
+ )
550
+ value = arrays.contiguous(value)
551
+ return value
552
+
553
+ def vrmlstr(self, value, lineariser=None):
554
+ """Convert the given value to a VRML97 representation"""
555
+ return _linvalues(lineariser)['numsep'].join(
556
+ [SFFloat_vrmlstr(obj, lineariser) for obj in value]
557
+ )
558
+
559
+ def copyValue(self, value, copier=None):
560
+ """Copy a value for copier"""
561
+ return arrays.array(value, arrays.typeCode(value))
562
+
563
+
564
+ class _Color(object):
565
+ """Mix-in for colour-value clamping and string coercion"""
566
+
567
+ def coerce(self, value):
568
+ """Adds clipping of values to 0.0 through 1.0 range"""
569
+ value = super(_Color, self).coerce(value)
570
+ value = arrays.clip(value, 0.0, 1.0)
571
+ return value
572
+
573
+ def copyValue(self, value, copier=None):
574
+ """Copy a value for copier"""
575
+ return arrays.array(value, arrays.typeCode(value))
576
+
577
+
578
+ class _SFArray(object):
579
+ """Base class which holds a single array-type value (can be arbitrarily spec'd numpy array)"""
580
+
581
+ defaultDefault = list
582
+ acceptedTypes = ('d', DOUBLE_TYPE, 'V')
583
+ targetType = DOUBLE_TYPE
584
+
585
+ def reshape(self, value):
586
+ """Do reshape of value to our target dimensions"""
587
+ return value
588
+
589
+ def coerce(self, value):
590
+ if isinstance(value, (str, unicode)):
591
+ value = [
592
+ float(x)
593
+ for x in value.replace(',', ' ').replace('[', ' ').replace(']').split()
594
+ ]
595
+ if field.UNPACK_TYPES and isinstance(value, field.UNPACK_TYPES):
596
+ value = list(value)
597
+ if isinstance(value, arrays.ArrayType):
598
+ if arrays.typeCode(value) not in self.acceptedTypes:
599
+ value = value.astype(self.targetType)
600
+ elif isinstance(value, field.SEQUENCE_TYPES):
601
+ try:
602
+ value = arrays.array(value, self.targetType)
603
+ except ValueError:
604
+ value = arrays.array(
605
+ [float(obj) for obj in value],
606
+ self.targetType,
607
+ )
608
+ elif isinstance(value, (int, long, float)):
609
+ value = arrays.array([value], self.targetType)
610
+ else:
611
+ try:
612
+ value = arrays.asarray(value, self.targetType)
613
+ except Exception:
614
+ raise ValueError(
615
+ """Attempted to set value for an %s field which is not compatible: %s"""
616
+ % (self.typeName(), repr(value))
617
+ )
618
+ # special casing, again, for explicitly structured arrays
619
+ if not arrays.typeCode(value) == 'V':
620
+ value = arrays.contiguous(self.reshape(value))
621
+ return value
622
+
623
+ def check(self, value):
624
+ """Check that the given value is of exactly the expected type"""
625
+ if isinstance(value, arrays.ArrayType):
626
+ typeCode = arrays.typeCode(value)
627
+ if typeCode in self.acceptedTypes:
628
+ if typeCode == 'V':
629
+ # special vector type
630
+ return 1
631
+ else:
632
+ s = arrays.shape(value)
633
+ if len(s) == len(self.dimension) + 1 and s[1:] == self.dimension:
634
+ return 1
635
+ return 0
636
+
637
+ def vrmlstr(self, value, lineariser=None):
638
+ """Convert the given value to a VRML97 representation"""
639
+ return str(value)
640
+
641
+ def copyValue(self, value, copier=None):
642
+ """Copy a value for copier"""
643
+ return arrays.array(value, arrays.typeCode(value))
644
+
645
+
646
+ class _SFArray32(_SFArray):
647
+ """32-bit version of SFArrays"""
648
+
649
+ acceptedTypes = ('f', FLOAT_TYPE, 'V')
650
+ targetType = FLOAT_TYPE
651
+
652
+
653
+ class _MFVec(_SFArray):
654
+ """MFVecXX field/event type base-class
655
+
656
+ Stored as x * self.length Numeric Python double array
657
+ """
658
+
659
+ defaultDefault = list
660
+ acceptedTypes = ('d', DOUBLE_TYPE)
661
+ targetType = DOUBLE_TYPE
662
+ dimension = (3,) # our dimension...
663
+
664
+ @property
665
+ def length(self):
666
+ import operator
667
+
668
+ self._length = reduce(operator.mul, self.dimension)
669
+ return self._length
670
+
671
+ def reshape(self, value):
672
+ return arrays.reshape(value, (-1,) + self.dimension)
673
+
674
+ def check(self, value):
675
+ """Check that the given value is of exactly the expected type"""
676
+ if isinstance(value, arrays.ArrayType):
677
+ if arrays.typeCode(value) in self.acceptedTypes:
678
+ s = arrays.shape(value)
679
+ if len(s) == len(self.dimension) + 1 and s[1:] == self.dimension:
680
+ return 1
681
+ return 0
682
+
683
+ def vrmlstr(self, value, lineariser=None):
684
+ """Convert the given value to a VRML97 representation"""
685
+ try:
686
+ if not len(value):
687
+ return '[ ]'
688
+ except ValueError:
689
+ # numpy arrays can't be tested for null-ity, should be a typeerror, but whatever
690
+ pass
691
+ linvalues = _linvalues(lineariser)
692
+ sets = [
693
+ linvalues['numsep'].join([str(obj) for obj in vector.ravel()])
694
+ for vector in value
695
+ ]
696
+ setLength = int(100 / self.length) # 100 is arbitrary
697
+
698
+ # now process the string chunk representations
699
+ if len(sets) < setLength: # again, arbitrary
700
+ return '[%s]' % (linvalues['subelspacer'].join(sets))
701
+ else: # greater than setLength elements...
702
+ stringsets2 = []
703
+ while sets:
704
+ stringsets2.append(linvalues['subelspacer'].join(sets[:setLength]))
705
+ del sets[:setLength]
706
+ return '[%s]' % ('\n'.join(stringsets2))
707
+
708
+ def copyValue(self, value, copier=None):
709
+ """Copy a value for copier"""
710
+ return arrays.array(value, arrays.typeCode(value))
711
+
712
+
713
+ class _SFVec32(_SFVec):
714
+ acceptedTypes = ('f', FLOAT_TYPE)
715
+ targetType = FLOAT_TYPE
716
+
717
+
718
+ class _MFVec32(_MFVec):
719
+ acceptedTypes = ('f', FLOAT_TYPE)
720
+ targetType = FLOAT_TYPE
721
+
722
+
723
+ class _SFVec2f(_SFVec32):
724
+ """SFVec2f field/event type base-class"""
725
+
726
+ dimension = (2,)
727
+
728
+
729
+ class _SFVec3f(_SFVec32):
730
+ """SFVec3f field/event type base-class"""
731
+
732
+ dimension = (3,)
733
+
734
+
735
+ class _SFVec4f(_SFVec32):
736
+ """SFVec4f field/event type base-class"""
737
+
738
+ dimension = (4,)
739
+
740
+
741
+ class _SFRotation(_SFVec32):
742
+ """SFRotation field/event type base-class"""
743
+
744
+ dimension = (4,)
745
+
746
+
747
+ class _SFMatrix3f(_SFVec32):
748
+ """3x3 matrix field/event type base-class"""
749
+
750
+ dimension = (3, 3)
751
+
752
+ def defaultDefault(self):
753
+ """Default default value for vectors/colours"""
754
+ return arrays.identity(self.dimension[0], self.targetType)
755
+
756
+
757
+ class _SFMatrix4f(_SFVec32):
758
+ """4x4 matrix field/event type base-class"""
759
+
760
+ dimension = (4, 4)
761
+
762
+ def defaultDefault(self):
763
+ """Default default value for vectors/colours"""
764
+ return arrays.identity(self.dimension[0], self.targetType)
765
+
766
+
767
+ class _SFVec2d(_SFVec):
768
+ """SFVec2f field/event type base-class"""
769
+
770
+ dimension = (2,)
771
+
772
+
773
+ class _SFVec3d(_SFVec):
774
+ """SFVec3f field/event type base-class"""
775
+
776
+ dimension = (3,)
777
+
778
+
779
+ class _SFVec4d(_SFVec):
780
+ """SFVec4f field/event type base-class"""
781
+
782
+ dimension = (4,)
783
+
784
+
785
+ class _SFMatrix3d(_SFVec):
786
+ """3x3 matrix field/event type base-class"""
787
+
788
+ dimension = (3, 3)
789
+
790
+
791
+ class _SFMatrix4d(_SFVec):
792
+ """4x4 matrix field/event type base-class"""
793
+
794
+ dimension = (4, 4)
795
+
796
+
797
+ class _SFColor(_Color, _SFVec3f):
798
+ """SFColor field/event type base-class"""
799
+
800
+ def coerce(self, value):
801
+ """Adds string-coercion for color data types"""
802
+ if isinstance(value, (str, unicode)):
803
+ value = csscolors.stringToColor(value)
804
+ return super(_SFColor, self).coerce(value)
805
+
806
+ # can't use classmethod because then super's get class instead of instance
807
+ # coerce = classmethod( coerce )
808
+
809
+
810
+ _SFCOLOR_TOOL = _SFColor()
811
+
812
+
813
+ class _MFVec2f(_MFVec32):
814
+ """MFVec2f field/event type base-class"""
815
+
816
+ dimension = (2,)
817
+
818
+
819
+ class _MFVec3f(_MFVec32):
820
+ """MFVec3f field/event type base-class"""
821
+
822
+ dimension = (3,)
823
+
824
+
825
+ class _MFVec4f(_MFVec32):
826
+ """MFVec4f field/event type base-class"""
827
+
828
+ dimension = (4,)
829
+
830
+
831
+ class _MFVec2d(_MFVec):
832
+ """MFVec2d field/event type base-class"""
833
+
834
+ dimension = (2,)
835
+
836
+
837
+ class _MFVec3d(_MFVec):
838
+ """MFVec3d field/event type base-class"""
839
+
840
+ dimension = (3,)
841
+
842
+
843
+ class _MFVec4d(_MFVec):
844
+ """MFVec4d field/event type base-class"""
845
+
846
+ dimension = (4,)
847
+
848
+
849
+ class _MFColor(_Color, _MFVec3f):
850
+ """MFColor field/event type base-class"""
851
+
852
+ def coerce(self, value):
853
+ """Adds string coercion for color data types"""
854
+ try:
855
+ return super(_MFColor, self).coerce(value)
856
+ except (ValueError, TypeError) as err:
857
+ # allow for string-based specifications...
858
+ result = []
859
+ current = []
860
+ for item in value:
861
+ if isinstance(item, (str, unicode)):
862
+ if current:
863
+ raise ValueError(
864
+ """Incorrect number of float values %r before string value %r for color number %s"""
865
+ % (
866
+ current,
867
+ item,
868
+ len(result),
869
+ )
870
+ )
871
+ result.append(_SFCOLOR_TOOL.coerce(item))
872
+ else:
873
+ current.append(item)
874
+ if len(current) == 3:
875
+ result.append(_SFCOLOR_TOOL.coerce(current))
876
+ current = []
877
+ if current:
878
+ raise ValueError(
879
+ """Incorrect number of float values at end of MFColor: %(current)r"""
880
+ % locals()
881
+ )
882
+ return super(_MFColor, self).coerce(result)
883
+
884
+
885
+ class _MFRotation(_MFVec):
886
+ """MFRotation field/event type base-class"""
887
+
888
+ dimension = (4,)
889
+
890
+
891
+ class _MFMatrix3f(_MFVec32):
892
+ """3x3 matrix-set field/event type base-class"""
893
+
894
+ dimension = (3, 3)
895
+
896
+
897
+ class _MFMatrix4f(_MFVec32):
898
+ """4x4 matrix-set field/event type base-class"""
899
+
900
+ dimension = (4, 4)
901
+
902
+
903
+ class _MFMatrix3d(_MFVec):
904
+ """3x3 matrix-set field/event type base-class"""
905
+
906
+ dimension = (3, 3)
907
+
908
+
909
+ class _MFMatrix4d(_MFVec):
910
+ """4x4 matrix-set field/event type base-class"""
911
+
912
+ dimension = (4, 4)
913
+
914
+
915
+ ### The concrete field and event classes (auto-generated).
916
+ class MFColor(_MFColor, field.Field):
917
+ """MFColor Field class"""
918
+
919
+
920
+ class MFColorEvt(
921
+ _MFColor,
922
+ field.Event,
923
+ ):
924
+ """MFColor Event class"""
925
+
926
+ fieldType = 'MFColor'
927
+
928
+
929
+ class SFArray(_SFArray, field.Field):
930
+ """SFArray Field class"""
931
+
932
+
933
+ class SFArrayEvt(_SFArray, field.Event):
934
+ """SFArray Event class"""
935
+
936
+ fieldType = 'SFArray'
937
+
938
+
939
+ class SFArray32(_SFArray32, field.Field):
940
+ """SFArray32 Field class"""
941
+
942
+
943
+ class SFArray32Evt(_SFArray32, field.Event):
944
+ """SFArray32 Event class"""
945
+
946
+ fieldType = 'SFArray32'
947
+
948
+
949
+ class MFFloat(_MFFloat, field.Field):
950
+ """MFFloat Field class"""
951
+
952
+
953
+ class MFFloatEvt(
954
+ _MFFloat,
955
+ field.Event,
956
+ ):
957
+ """MFFloat Event class"""
958
+
959
+ fieldType = 'MFFloat'
960
+
961
+
962
+ class MFFloat32(_MFFloat32, field.Field):
963
+ """MFFloat32 Field class"""
964
+
965
+
966
+ class MFFloat32Evt(
967
+ _MFFloat32,
968
+ field.Event,
969
+ ):
970
+ """MFFloat32 Event class"""
971
+
972
+ fieldType = 'MFFloat32'
973
+
974
+
975
+ class MFInt32(_MFInt32, field.Field):
976
+ """MFInt32 Field class"""
977
+
978
+
979
+ class MFInt32Evt(
980
+ _MFInt32,
981
+ field.Event,
982
+ ):
983
+ """MFInt32 Event class"""
984
+
985
+ fieldType = 'MFInt32'
986
+
987
+
988
+ class MFUInt32(_MFUInt32, field.Field):
989
+ """MFUInt32 Field class"""
990
+
991
+
992
+ class MFUInt32Evt(
993
+ _MFUInt32,
994
+ field.Event,
995
+ ):
996
+ """MFUInt32 Event class"""
997
+
998
+ fieldType = 'MFUInt32'
999
+
1000
+
1001
+ class MFRotation(_MFRotation, field.Field):
1002
+ """MFRotation Field class"""
1003
+
1004
+
1005
+ class MFRotationEvt(
1006
+ _MFRotation,
1007
+ field.Event,
1008
+ ):
1009
+ """MFRotation Event class"""
1010
+
1011
+ fieldType = 'MFRotation'
1012
+
1013
+
1014
+ class MFString(_MFString, field.Field):
1015
+ """MFString Field class"""
1016
+
1017
+
1018
+ class MFStringEvt(
1019
+ _MFString,
1020
+ field.Event,
1021
+ ):
1022
+ """MFString Event class"""
1023
+
1024
+ fieldType = 'MFString'
1025
+
1026
+
1027
+ class MFTime(_MFTime, field.Field):
1028
+ """MFTime Field class"""
1029
+
1030
+
1031
+ class MFTimeEvt(
1032
+ _MFTime,
1033
+ field.Event,
1034
+ ):
1035
+ """MFTime Event class"""
1036
+
1037
+ fieldType = 'MFTime'
1038
+
1039
+
1040
+ class MFVec2f(_MFVec2f, field.Field):
1041
+ """MFVec2f Field class"""
1042
+
1043
+
1044
+ class MFVec2fEvt(
1045
+ _MFVec2f,
1046
+ field.Event,
1047
+ ):
1048
+ """MFVec2f Event class"""
1049
+
1050
+ fieldType = 'MFVec2f'
1051
+
1052
+
1053
+ class MFVec2d(_MFVec2f, field.Field):
1054
+ """MFVec2d Field class"""
1055
+
1056
+
1057
+ class MFVec2dEvt(
1058
+ _MFVec2d,
1059
+ field.Event,
1060
+ ):
1061
+ """MFVec2d Event class"""
1062
+
1063
+ fieldType = 'MFVec2d'
1064
+
1065
+
1066
+ class MFVec3f(_MFVec3f, field.Field):
1067
+ """MFVec3f Field class"""
1068
+
1069
+
1070
+ class MFVec3fEvt(
1071
+ _MFVec3f,
1072
+ field.Event,
1073
+ ):
1074
+ """MFVec3f Event class"""
1075
+
1076
+ fieldType = 'MFVec3f'
1077
+
1078
+
1079
+ class MFVec3d(_MFVec3d, field.Field):
1080
+ """MFVec3d Field class"""
1081
+
1082
+
1083
+ class MFVec3dEvt(
1084
+ _MFVec3d,
1085
+ field.Event,
1086
+ ):
1087
+ """MFVec3d Event class"""
1088
+
1089
+ fieldType = 'MFVec3d'
1090
+
1091
+
1092
+ class MFVec4f(_MFVec4f, field.Field):
1093
+ """MFVec4f Field class"""
1094
+
1095
+
1096
+ class MFVec4fEvt(
1097
+ _MFVec4f,
1098
+ field.Event,
1099
+ ):
1100
+ """MFVec4f Event class"""
1101
+
1102
+ fieldType = 'MFVec4f'
1103
+
1104
+
1105
+ class MFVec4d(_MFVec4d, field.Field):
1106
+ """MFVec4d Field class"""
1107
+
1108
+
1109
+ class MFVec4dEvt(
1110
+ _MFVec4d,
1111
+ field.Event,
1112
+ ):
1113
+ """MFVec4d Event class"""
1114
+
1115
+ fieldType = 'MFVec4d'
1116
+
1117
+
1118
+ class MFMatrix3f(_MFMatrix3f, field.Field):
1119
+ """MFMatrix3f Field class"""
1120
+
1121
+
1122
+ class MFMatrix3fEvt(_MFMatrix3f, field.Event):
1123
+ """MFMatrix3f Field class"""
1124
+
1125
+ fieldType = 'MFMatrix3f'
1126
+
1127
+
1128
+ class MFMatrix3d(_MFMatrix3d, field.Field):
1129
+ """MFMatrix3d Field class"""
1130
+
1131
+
1132
+ class MFMatrix3dEvt(_MFMatrix3d, field.Event):
1133
+ """MFMatrix3d Field class"""
1134
+
1135
+ fieldType = 'MFMatrix3d'
1136
+
1137
+
1138
+ class MFMatrix4f(_MFMatrix4f, field.Field):
1139
+ """MFMatrix4f Field class"""
1140
+
1141
+
1142
+ class MFMatrix4fEvt(_MFMatrix4f, field.Event):
1143
+ """MFMatrix4f Field class"""
1144
+
1145
+ fieldType = 'MFMatrix4f'
1146
+
1147
+
1148
+ class MFMatrix4d(_MFMatrix4d, field.Field):
1149
+ """MFMatrix4d Field class"""
1150
+
1151
+
1152
+ class MFMatrix4dEvt(_MFMatrix4d, field.Event):
1153
+ """MFMatrix3d Field class"""
1154
+
1155
+ fieldType = 'MFMatrix4d'
1156
+
1157
+
1158
+ class SFBool(_SFBool, field.Field):
1159
+ """SFBool Field class"""
1160
+
1161
+
1162
+ class SFBoolEvt(
1163
+ _SFBool,
1164
+ field.Event,
1165
+ ):
1166
+ """SFBool Event class"""
1167
+
1168
+ fieldType = 'SFBool'
1169
+
1170
+
1171
+ class SFColor(_SFColor, field.Field):
1172
+ """SFColor Field class"""
1173
+
1174
+
1175
+ class SFColorEvt(
1176
+ _SFColor,
1177
+ field.Event,
1178
+ ):
1179
+ """SFColor Event class"""
1180
+
1181
+ fieldType = 'SFColor'
1182
+
1183
+
1184
+ class SFFloat(_SFFloat, field.Field):
1185
+ """SFFloat Field class"""
1186
+
1187
+
1188
+ class SFFloatEvt(
1189
+ _SFFloat,
1190
+ field.Event,
1191
+ ):
1192
+ """SFFloat Event class"""
1193
+
1194
+ fieldType = 'SFFloat'
1195
+
1196
+
1197
+ class SFImage(_SFImage, field.Field):
1198
+ """SFImage Field class"""
1199
+
1200
+
1201
+ class SFImageEvt(
1202
+ _SFImage,
1203
+ field.Event,
1204
+ ):
1205
+ """SFImage Event class"""
1206
+
1207
+ fieldType = 'SFImage'
1208
+
1209
+
1210
+ class SFInt32(_SFInt32, field.Field):
1211
+ """SFInt32 Field class"""
1212
+
1213
+
1214
+ class SFInt32Evt(
1215
+ _SFInt32,
1216
+ field.Event,
1217
+ ):
1218
+ """SFInt32 Event class"""
1219
+
1220
+ fieldType = 'SFInt32'
1221
+
1222
+
1223
+ class SFUInt32(_SFUInt32, field.Field):
1224
+ """SFInt32 Field class"""
1225
+
1226
+
1227
+ class SFUInt32Evt(
1228
+ _SFUInt32,
1229
+ field.Event,
1230
+ ):
1231
+ """SFInt32 Event class"""
1232
+
1233
+ fieldType = 'SFUInt32'
1234
+
1235
+
1236
+ class SFRotation(_SFRotation, field.Field):
1237
+ """SFRotation Field class"""
1238
+
1239
+
1240
+ class SFRotationEvt(
1241
+ _SFRotation,
1242
+ field.Event,
1243
+ ):
1244
+ """SFRotation Event class"""
1245
+
1246
+ fieldType = 'SFRotation'
1247
+
1248
+
1249
+ class SFString(_SFString, field.Field):
1250
+ """SFString Field class"""
1251
+
1252
+
1253
+ class SFStringEvt(
1254
+ _SFString,
1255
+ field.Event,
1256
+ ):
1257
+ """SFString Event class"""
1258
+
1259
+ fieldType = 'SFString'
1260
+
1261
+
1262
+ class SFTime(_SFTime, field.Field):
1263
+ """SFTime Field class"""
1264
+
1265
+
1266
+ class SFTimeEvt(
1267
+ _SFTime,
1268
+ field.Event,
1269
+ ):
1270
+ """SFTime Event class"""
1271
+
1272
+ fieldType = 'SFTime'
1273
+
1274
+
1275
+ class SFVec2f(_SFVec2f, field.Field):
1276
+ """SFVec2f Field class"""
1277
+
1278
+
1279
+ class SFVec2fEvt(
1280
+ _SFVec2f,
1281
+ field.Event,
1282
+ ):
1283
+ """SFVec2f Event class"""
1284
+
1285
+ fieldType = 'SFVec2f'
1286
+
1287
+
1288
+ class SFVec2d(_SFVec2d, field.Field):
1289
+ """SFVec2d Field class"""
1290
+
1291
+
1292
+ class SFVec2dEvt(
1293
+ _SFVec2d,
1294
+ field.Event,
1295
+ ):
1296
+ """SFVec2d Event class"""
1297
+
1298
+ fieldType = 'SFVec2d'
1299
+
1300
+
1301
+ class SFVec3f(_SFVec3f, field.Field):
1302
+ """SFVec3f Field class"""
1303
+
1304
+
1305
+ class SFVec3fEvt(
1306
+ _SFVec3f,
1307
+ field.Event,
1308
+ ):
1309
+ """SFVec3f Event class"""
1310
+
1311
+ fieldType = 'SFVec3f'
1312
+
1313
+
1314
+ class SFVec3d(_SFVec3d, field.Field):
1315
+ """SFVec3f Field class"""
1316
+
1317
+
1318
+ class SFVec3dEvt(
1319
+ _SFVec3d,
1320
+ field.Event,
1321
+ ):
1322
+ """SFVec3d Event class"""
1323
+
1324
+ fieldType = 'SFVec3d'
1325
+
1326
+
1327
+ class SFVec4f(_SFVec4f, field.Field):
1328
+ """SFVec4f Field class"""
1329
+
1330
+
1331
+ class SFVec4fEvt(
1332
+ _SFVec4f,
1333
+ field.Event,
1334
+ ):
1335
+ """SFVec4f Event class"""
1336
+
1337
+ fieldType = 'SFVec4f'
1338
+
1339
+
1340
+ class SFVec4d(_SFVec4d, field.Field):
1341
+ """SFVec4d Field class"""
1342
+
1343
+
1344
+ class SFVec4dEvt(
1345
+ _SFVec4d,
1346
+ field.Event,
1347
+ ):
1348
+ """SFVec4f Event class"""
1349
+
1350
+ fieldType = 'SFVec4d'
1351
+
1352
+
1353
+ class SFMatrix3f(_SFMatrix3f, field.Field):
1354
+ """SFMatrix3f Field class"""
1355
+
1356
+
1357
+ class SFMatrix3fEvt(_SFMatrix3f, field.Event):
1358
+ """SFMatrix3f Field class"""
1359
+
1360
+ fieldType = 'SFMatrix3f'
1361
+
1362
+
1363
+ class SFMatrix3d(_SFMatrix3d, field.Field):
1364
+ """SFMatrix3d Field class"""
1365
+
1366
+
1367
+ class SFMatrix3dEvt(_SFMatrix3d, field.Event):
1368
+ """SFMatrix3d Field class"""
1369
+
1370
+ fieldType = 'SFMatrix3d'
1371
+
1372
+
1373
+ class SFMatrix4f(_SFMatrix4f, field.Field):
1374
+ """SFMatrix4f Field class"""
1375
+
1376
+
1377
+ class SFMatrix4fEvt(_SFMatrix4f, field.Event):
1378
+ """SFMatrix4f Field class"""
1379
+
1380
+ fieldType = 'SFMatrix4f'
1381
+
1382
+
1383
+ class SFMatrix4d(_SFMatrix4d, field.Field):
1384
+ """SFMatrix4d Field class"""
1385
+
1386
+
1387
+ class SFMatrix4dEvt(_SFMatrix4d, field.Event):
1388
+ """SFMatrix3d Field class"""
1389
+
1390
+ fieldType = 'SFMatrix4d'
1391
+
1392
+
1393
+ ### Now register everything
1394
+ field.register(MFFloat)
1395
+ field.register(MFFloat32)
1396
+ field.register(SFBool)
1397
+ field.register(MFColor)
1398
+ field.register(MFRotation)
1399
+ field.register(SFRotation)
1400
+ field.register(MFInt32)
1401
+ field.register(MFUInt32)
1402
+ field.register(MFString)
1403
+ field.register(SFImage)
1404
+ field.register(SFFloat)
1405
+ field.register(SFTime)
1406
+ field.register(MFTime)
1407
+ field.register(SFColor)
1408
+ field.register(SFString)
1409
+ field.register(SFInt32)
1410
+ field.register(SFUInt32)
1411
+ field.register(SFVec2f)
1412
+ field.register(SFVec3f)
1413
+ field.register(SFVec4f)
1414
+ field.register(SFArray)
1415
+ field.register(SFArray32)
1416
+ field.register(MFVec2f)
1417
+ field.register(MFVec3f)
1418
+ field.register(MFVec4f)
1419
+ field.register(MFMatrix3f)
1420
+ field.register(MFMatrix4f)
1421
+ field.register(SFVec2d)
1422
+ field.register(SFVec3d)
1423
+ field.register(SFVec4d)
1424
+ field.register(MFVec2d)
1425
+ field.register(MFVec3d)
1426
+ field.register(MFVec4d)
1427
+ field.register(MFMatrix3d)
1428
+ field.register(MFMatrix4d)
1429
+
1430
+ ## event classes...
1431
+ field.register(MFFloatEvt)
1432
+ field.register(MFFloat32Evt)
1433
+ field.register(SFBoolEvt)
1434
+ field.register(MFColorEvt)
1435
+ field.register(MFRotationEvt)
1436
+ field.register(SFRotationEvt)
1437
+ field.register(MFInt32Evt)
1438
+ field.register(MFUInt32Evt)
1439
+ field.register(MFStringEvt)
1440
+ field.register(SFImageEvt)
1441
+ field.register(SFFloatEvt)
1442
+ field.register(SFTimeEvt)
1443
+ field.register(MFTimeEvt)
1444
+ field.register(SFColorEvt)
1445
+ field.register(SFStringEvt)
1446
+ field.register(SFInt32Evt)
1447
+ field.register(SFUInt32Evt)
1448
+ field.register(SFVec2fEvt)
1449
+ field.register(SFVec3fEvt)
1450
+ field.register(SFVec4fEvt)
1451
+ field.register(SFArrayEvt)
1452
+ field.register(SFArray32Evt)
1453
+ field.register(MFVec2fEvt)
1454
+ field.register(MFVec3fEvt)
1455
+ field.register(MFVec4fEvt)
1456
+ field.register(MFMatrix3fEvt)
1457
+ field.register(MFMatrix4fEvt)
1458
+ field.register(SFVec2dEvt)
1459
+ field.register(SFVec3dEvt)
1460
+ field.register(SFVec4dEvt)
1461
+ field.register(MFVec2dEvt)
1462
+ field.register(MFVec3dEvt)
1463
+ field.register(MFVec4dEvt)
1464
+ field.register(MFMatrix3dEvt)
1465
+ field.register(MFMatrix4dEvt)
1466
+
1467
+ if __name__ == "__main__":
1468
+ import unittest
1469
+
1470
+ class ColorTest(unittest.TestCase):
1471
+ """Test simple color coercion"""
1472
+
1473
+ def testMFColorString(self):
1474
+ color = MFColor("test", 1, list)
1475
+ result = color.coerce([0.2, 0.3, 0.4, 'red'])
1476
+ assert arrays.allclose(result, ((0.2, 0.3, 0.4), (1, 0, 0)))
1477
+
1478
+ def testSFColorString(self):
1479
+ color = SFColor("test", 1, list)
1480
+ for value, expected in [
1481
+ ((0.2, 0.3, 0.4), (0.2, 0.3, 0.4)),
1482
+ ('red', (1, 0, 0)),
1483
+ ('#ff0000', (1, 0, 0)),
1484
+ ]:
1485
+ result = color.coerce(value)
1486
+ assert arrays.allclose(result, expected), (
1487
+ """FAIL: color conversion for %(value)r\nExpected: %(expected)s\nGot:%(result)s"""
1488
+ % (locals())
1489
+ )
1490
+
1491
+ unittest.main()