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.
- pyvrml97-2.3.4b3.dist-info/METADATA +65 -0
- pyvrml97-2.3.4b3.dist-info/RECORD +39 -0
- pyvrml97-2.3.4b3.dist-info/WHEEL +5 -0
- pyvrml97-2.3.4b3.dist-info/top_level.txt +1 -0
- vrml/__init__.py +22 -0
- vrml/_bytes.py +85 -0
- vrml/arrays.py +86 -0
- vrml/cache.py +250 -0
- vrml/copier.py +27 -0
- vrml/csscolors.py +177 -0
- vrml/event.py +24 -0
- vrml/field.py +476 -0
- vrml/fieldtypes.py +1491 -0
- vrml/node.py +569 -0
- vrml/nodepath.py +70 -0
- vrml/olist.py +126 -0
- vrml/protofunctions.py +233 -0
- vrml/protonamespace.py +13 -0
- vrml/route.py +167 -0
- vrml/vrml200x/__init__.py +0 -0
- vrml/vrml200x/parser.py +87 -0
- vrml/vrml97/__init__.py +6 -0
- vrml/vrml97/_transformmatrix.py +133 -0
- vrml/vrml97/_transformmatrix_accel.py +63 -0
- vrml/vrml97/basenamespaces.py +28 -0
- vrml/vrml97/basenodes.py +648 -0
- vrml/vrml97/linearise.py +579 -0
- vrml/vrml97/nodepath.py +135 -0
- vrml/vrml97/nodetypes.py +95 -0
- vrml/vrml97/nurbs.py +79 -0
- vrml/vrml97/parseprocessor.py +452 -0
- vrml/vrml97/parser.py +72 -0
- vrml/vrml97/scenegraph.py +181 -0
- vrml/vrml97/script.py +25 -0
- vrml/vrml97/shaders.py +123 -0
- vrml/vrml97/transformmatrix.py +193 -0
- vrml/weakkeydictfix.py +36 -0
- vrml/weaklist.py +163 -0
- vrml/weaktuple.py +138 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyVRML97
|
|
3
|
+
Version: 2.3.4b3
|
|
4
|
+
Summary: VRML97 Scenegraph model for Python
|
|
5
|
+
Author-email: "Mike C. Fletcher" <mcfletch@vrplumber.com>
|
|
6
|
+
License: BSD-style, see license.txt for details
|
|
7
|
+
Project-URL: Homepage, http://pyopengl.sourceforge.net/context/
|
|
8
|
+
Keywords: VRML,VRML97,scenegraph
|
|
9
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: C
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: PyDispatcher
|
|
19
|
+
Requires-Dist: simpleparse
|
|
20
|
+
|
|
21
|
+
PyVRML97 Scenegraph Model
|
|
22
|
+
==========================
|
|
23
|
+
|
|
24
|
+
PyVRML97 is the scenegraph model from `OpenGLContext`_ but can be used separately
|
|
25
|
+
in VRML97 processing tools. It relies on `SimpleParse`_, `PyDispatcher`_ and `Numpy`_
|
|
26
|
+
to provide the basic modelling functionality.
|
|
27
|
+
|
|
28
|
+
Properties (fields) in PyVRML97 follow the "Observer" pattern, and PyVRML97
|
|
29
|
+
provides a cache mechanism that allows you to do cache invalidation when
|
|
30
|
+
setting properties (fields). OpenGLContext uses that extensively to
|
|
31
|
+
optimize the rendering process.
|
|
32
|
+
|
|
33
|
+
.. _`OpenGLContext`: https://pypi.python.org/pypi/OpenGLContext
|
|
34
|
+
.. _`SimpleParse`: https://pypi.python.org/pypi/SimpleParse
|
|
35
|
+
.. _`PyDispatcher`: https://pypi.python.org/pypi/PyDispatcher
|
|
36
|
+
.. _`Numpy`: https://pypi.python.org/pypi/numpy
|
|
37
|
+
|
|
38
|
+
Installation
|
|
39
|
+
-------------
|
|
40
|
+
|
|
41
|
+
It isn't common to directly install PyVRML97 itself (normally you want to
|
|
42
|
+
install it as part of installing OpenGLContext), but should you wish to, you
|
|
43
|
+
can install via::
|
|
44
|
+
|
|
45
|
+
$ pip install PyVRML97 PyVRML97_accelerate
|
|
46
|
+
|
|
47
|
+
The PyVRML97_accelerate module requires that you have a working compiler
|
|
48
|
+
(or, if you are on Windows, prebuilt wheels likely are available).
|
|
49
|
+
|
|
50
|
+
.. image:: https://ci.appveyor.com/api/projects/status/MikeCFletcher/pyvrml97/branch/master
|
|
51
|
+
:target: https://ci.appveyor.com/project/MikeCFletcher/pyvrml97
|
|
52
|
+
:alt: Appveyor Build
|
|
53
|
+
|
|
54
|
+
.. image:: https://img.shields.io/pypi/v/pyvrml97.svg
|
|
55
|
+
:target: https://pypi.python.org/pypi/pyvrml97
|
|
56
|
+
:alt: Latest PyPI Version
|
|
57
|
+
|
|
58
|
+
.. image:: https://img.shields.io/pypi/dm/pyvrml97.svg
|
|
59
|
+
:target: https://pypi.python.org/pypi/pyvrml97
|
|
60
|
+
:alt: Monthly download counter
|
|
61
|
+
|
|
62
|
+
Build/Release Process
|
|
63
|
+
----------------------
|
|
64
|
+
|
|
65
|
+
* Push an annotated tag with `version` prefix (github workflow `build_and_publish.yml`)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
vrml/__init__.py,sha256=MaK51RM3d-jCwGlHh5Emvb_QVwejMvHdhViz5DgWGRc,827
|
|
2
|
+
vrml/_bytes.py,sha256=Xug9wGWIn91Y9HeTDuZwxhsSxgUYAmNFH9U409JvShY,2404
|
|
3
|
+
vrml/arrays.py,sha256=b8MvRaTJ8dJRTk3oQ_WhHlEMU7urbO4D0vqftb8lEZo,2511
|
|
4
|
+
vrml/cache.py,sha256=netNmLNUVSutC2beD0Z89I_wOv7stIUGbzSS0PUyzVU,8062
|
|
5
|
+
vrml/copier.py,sha256=1tP1xbHe5UOulpnzO_ccywHfm0HoQTSb-l-o17FgPqE,1077
|
|
6
|
+
vrml/csscolors.py,sha256=d76-sTGbLcMZs9Wh65cmLrdf2VcKsQrwHYOiqat2ztA,6978
|
|
7
|
+
vrml/event.py,sha256=6u_Je6Qad8nauBc2vCyOvEBJzv-4ZseuXn1yOse5Dtg,845
|
|
8
|
+
vrml/field.py,sha256=9c5aC_ixOTSkkaowpHPocf93ukZ60maZXtXg_LZQDT4,14380
|
|
9
|
+
vrml/fieldtypes.py,sha256=sTchoCoi8elYOXPNlka9i1oEfQnjqjUfBPgYfz3ZkYo,37090
|
|
10
|
+
vrml/node.py,sha256=JfE8xg14ptkFBgq3WkuEf6IRpEsDbxTuxLiTNh4IxRM,18364
|
|
11
|
+
vrml/nodepath.py,sha256=ojvV5Wg3ktZ0s82HGwJatuvhFOWDPmlZfpk36qEXxOQ,2388
|
|
12
|
+
vrml/olist.py,sha256=asZ3Z_AE_5wiAi0yQJX-RCaentaqffUp1IcuqR_hMIs,4774
|
|
13
|
+
vrml/protofunctions.py,sha256=Any0tervT96fWFPt9q9fELbus5yoqX1LW2Iq95QIznM,6685
|
|
14
|
+
vrml/protonamespace.py,sha256=WkWoZykw09OIxi7DjGiwXdSYFExDBNLLAeyk_lX1ocY,567
|
|
15
|
+
vrml/route.py,sha256=KSx9vYtYRW63_hjS6wrxus4e5BZf6CDM5PDGFpbk5lM,6139
|
|
16
|
+
vrml/weakkeydictfix.py,sha256=O-svMXnT2I7333EKtJOp59Kxj_QLaoAHzJGwfh2APmQ,1278
|
|
17
|
+
vrml/weaklist.py,sha256=36WRCeWWyPsMbfoSjEBUxlQIQxSNjP2EqVbMMXVuQTs,5759
|
|
18
|
+
vrml/weaktuple.py,sha256=bxIA6QZxIiWa1PEy1TNEDlRsGbSwL9gp7UJcgOKZ2BE,4733
|
|
19
|
+
vrml/vrml200x/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
vrml/vrml200x/parser.py,sha256=deDLgwLdXCBWudmhuFxU2eY_yyYhEOv_gg4ECwIis_k,3842
|
|
21
|
+
vrml/vrml97/__init__.py,sha256=QZ1php9-9TKWFblZ8L6LctbKTFJSKOVQtqM1JQHScqs,119
|
|
22
|
+
vrml/vrml97/_transformmatrix.py,sha256=rBG6B7mi32cGA3BzilzFfUoJklmUVU4-IXucOrPE2tg,3986
|
|
23
|
+
vrml/vrml97/_transformmatrix_accel.py,sha256=AMh9R0lMrXdEtsa3r9iaHDgtcyP0n4mq7mRAUtgmYlM,1922
|
|
24
|
+
vrml/vrml97/basenamespaces.py,sha256=BRk0qAG7kAWBvXLEVOEz0eSgWoq50JMYNLOb9AvM1-8,740
|
|
25
|
+
vrml/vrml97/basenodes.py,sha256=IXNxZq7EuoXRLA_DhZ9XLRt2e_0gAgmQtQabdydsZzY,28273
|
|
26
|
+
vrml/vrml97/linearise.py,sha256=p9a9vo05RmTgZnJSQKml42tnPxPHRrfLca5Olngy7vs,21946
|
|
27
|
+
vrml/vrml97/nodepath.py,sha256=dPtrTpYGrRKdjc0rQy1CIRW1rKYYs6-8LP78ne5OtcI,4948
|
|
28
|
+
vrml/vrml97/nodetypes.py,sha256=V00a3zA5ucNGn-csVNQL6f-hfSjUG6DVbREaiQzYYRQ,2275
|
|
29
|
+
vrml/vrml97/nurbs.py,sha256=JTRZBSEWOwvfL34tmgbk2THLErVpKlLZuH4-Su6dqUQ,3500
|
|
30
|
+
vrml/vrml97/parseprocessor.py,sha256=ZeTIhPqbxmpNy_mWfEIHud3cGom1blNoIPH2yITVHyU,16070
|
|
31
|
+
vrml/vrml97/parser.py,sha256=3jsCmpiYpO846QuQJJikUlGJeKAo-OxBxCmqjVYh5AA,3350
|
|
32
|
+
vrml/vrml97/scenegraph.py,sha256=8ebIqPWsiwVBAQrncuOfh1VbLGbIvWVAsnoCiBeVmLc,6955
|
|
33
|
+
vrml/vrml97/script.py,sha256=cxXRJp9xbYJTlO-hLqXSR_-UBmc8BWHSJE0_4Hor3_U,809
|
|
34
|
+
vrml/vrml97/shaders.py,sha256=Igc8A1td7n9pNl5-QINA1X-ZEKPYVcec-djleJhDYg4,5421
|
|
35
|
+
vrml/vrml97/transformmatrix.py,sha256=-7Em6LRpNxErUKqGOFoTPLma04oCrbdel2eJdDYeGjo,5673
|
|
36
|
+
pyvrml97-2.3.4b3.dist-info/METADATA,sha256=Ue-K5TWkQR5onszMzFXx8_Ezclo7-GsYlqeThvKLfIs,2491
|
|
37
|
+
pyvrml97-2.3.4b3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
38
|
+
pyvrml97-2.3.4b3.dist-info/top_level.txt,sha256=HNkQ-TkhfmnJgOd_zwLjqNi-T86T_CSpBac8zWIFGBc,5
|
|
39
|
+
pyvrml97-2.3.4b3.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vrml
|
vrml/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""VRML Directed Acyclic Graph library and VRML97 tools
|
|
2
|
+
|
|
3
|
+
This package is a partial representation of VRML97
|
|
4
|
+
node and prototype semantics based loosely on the
|
|
5
|
+
original mcf.vrml processing engine by the author.
|
|
6
|
+
It is strongly dependent on the Python 2.2.2 property
|
|
7
|
+
class, and as such will not likely be portable to
|
|
8
|
+
earlier python versions.
|
|
9
|
+
|
|
10
|
+
The package includes a full VRML97 parser and
|
|
11
|
+
lineariser based on the SimpleParse 2.0 parsing engine
|
|
12
|
+
(also by the author).
|
|
13
|
+
|
|
14
|
+
The vrml.vrml97 sub-package contains classes and
|
|
15
|
+
functions specifically relating to the VRML97 ISO
|
|
16
|
+
standard. For instance, the vrml.vrml97.basenodes
|
|
17
|
+
file contains the "prototypes" for most of the
|
|
18
|
+
built-in nodes in the official specification.
|
|
19
|
+
"""
|
|
20
|
+
__version__ = "2.3.4b3"
|
|
21
|
+
__author__ = "Michael Colin Fletcher"
|
|
22
|
+
__license__ = "BSD-style, see license.txt for details"
|
vrml/_bytes.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""8-bit string definitions for Python 2/3 compatibility
|
|
2
|
+
|
|
3
|
+
Defines the following which allow for dealing with Python 3 breakages:
|
|
4
|
+
|
|
5
|
+
STR_IS_BYTES
|
|
6
|
+
STR_IS_UNICODE
|
|
7
|
+
|
|
8
|
+
Easily checked booleans for type identities
|
|
9
|
+
|
|
10
|
+
_NULL_8_BYTE
|
|
11
|
+
|
|
12
|
+
An 8-bit byte with NULL (0) value
|
|
13
|
+
|
|
14
|
+
as_8_bit( x, encoding='utf-8')
|
|
15
|
+
|
|
16
|
+
Returns the value as the 8-bit version
|
|
17
|
+
|
|
18
|
+
unicode -- always pointing to the unicode type
|
|
19
|
+
bytes -- always pointing to the 8-bit bytes type
|
|
20
|
+
"""
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
_NULL_8_BYTE = b'\000'
|
|
24
|
+
try:
|
|
25
|
+
STR_IS_BYTES = True
|
|
26
|
+
STR_IS_UNICODE = False
|
|
27
|
+
unicode
|
|
28
|
+
bytes = str
|
|
29
|
+
unicode = unicode
|
|
30
|
+
long = long
|
|
31
|
+
integer_types = int,long
|
|
32
|
+
def as_8_bit( x, encoding='utf-8' ):
|
|
33
|
+
if isinstance( x, unicode ):
|
|
34
|
+
return x.encode( encoding )
|
|
35
|
+
return bytes( x )
|
|
36
|
+
def as_str( x, encoding='utf-8'):
|
|
37
|
+
"""Produce a native string (i.e. different on python 2 and 3)"""
|
|
38
|
+
if isinstance(x,bytes):
|
|
39
|
+
return x
|
|
40
|
+
elif isinstance(x,unicode):
|
|
41
|
+
return x.encode(encoding)
|
|
42
|
+
else:
|
|
43
|
+
return str(x)
|
|
44
|
+
except NameError:
|
|
45
|
+
STR_IS_BYTES = False
|
|
46
|
+
STR_IS_UNICODE = True
|
|
47
|
+
unicode = str
|
|
48
|
+
bytes = bytes
|
|
49
|
+
long = int
|
|
50
|
+
integer_types = int,
|
|
51
|
+
def as_8_bit( x, encoding='utf-8' ):
|
|
52
|
+
if isinstance( x,unicode ):
|
|
53
|
+
return x.encode(encoding)
|
|
54
|
+
elif isinstance( x, bytes ):
|
|
55
|
+
# Note: this can create an 8-bit string that is *not* in encoding,
|
|
56
|
+
# but that is potentially exactly what we wanted, as these can
|
|
57
|
+
# be arbitrary byte-streams being passed to C functions
|
|
58
|
+
return x
|
|
59
|
+
return str(x).encode( encoding )
|
|
60
|
+
def as_str( x, encoding='utf-8'):
|
|
61
|
+
"""Produce a native string (i.e. different on python 2 and 3)"""
|
|
62
|
+
if isinstance(x,unicode):
|
|
63
|
+
return x
|
|
64
|
+
elif isinstance(x,bytes):
|
|
65
|
+
return x.decode(encoding)
|
|
66
|
+
else:
|
|
67
|
+
return str(x)
|
|
68
|
+
|
|
69
|
+
if hasattr( sys, 'maxsize' ):
|
|
70
|
+
maxsize = sys.maxsize
|
|
71
|
+
else:
|
|
72
|
+
maxsize = sys.maxint
|
|
73
|
+
|
|
74
|
+
def as_unicode(x,encoding='utf-8'):
|
|
75
|
+
"""Ensure is a unicode object given default encoding"""
|
|
76
|
+
if isinstance(x,unicode):
|
|
77
|
+
return x
|
|
78
|
+
elif isinstance(x,bytes):
|
|
79
|
+
try:
|
|
80
|
+
return x.decode(encoding)
|
|
81
|
+
except UnicodeDecodeError as err:
|
|
82
|
+
return x.decode('latin-1')
|
|
83
|
+
else:
|
|
84
|
+
return unicode(x)
|
|
85
|
+
|
vrml/arrays.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Abstraction point allowing use with numpy or Numeric
|
|
2
|
+
|
|
3
|
+
Chooses numpy if available because when it's installed
|
|
4
|
+
Numeric tends to be a bit flaky...
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from numpy import *
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
# TODO: don't import this here, as it's not
|
|
11
|
+
# actually *used* in pyvrml97, it's just used
|
|
12
|
+
# in OpenGLContext...
|
|
13
|
+
from vrml_accelerate import frustcullaccel
|
|
14
|
+
except ImportError as err:
|
|
15
|
+
frustcullaccel = None
|
|
16
|
+
# why did this get taken out? Is divide now safe?
|
|
17
|
+
amin = amin
|
|
18
|
+
amax = amax
|
|
19
|
+
divide_safe = divide
|
|
20
|
+
# Now deal with differing numpy APIs...
|
|
21
|
+
a = array([1, 2, 3], 'i')
|
|
22
|
+
ArrayType = ndarray # alias removed in later versions
|
|
23
|
+
# Take's API changed from Numeric, we've updated to
|
|
24
|
+
# always provide axis now...
|
|
25
|
+
if hasattr(a, '__array_typestr__'):
|
|
26
|
+
|
|
27
|
+
def typeCode(a):
|
|
28
|
+
"""Retrieve the typecode for the given array
|
|
29
|
+
|
|
30
|
+
Depending on whether you access the classic or new API
|
|
31
|
+
you have different access methods, so we have to use
|
|
32
|
+
the typecode() method if __array_typestr__ isn't there.
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
return a.__array_typestr__
|
|
36
|
+
except AttributeError:
|
|
37
|
+
return a.typecode()
|
|
38
|
+
|
|
39
|
+
else:
|
|
40
|
+
|
|
41
|
+
def typeCode(a):
|
|
42
|
+
"""Retrieve the typecode for the given array
|
|
43
|
+
|
|
44
|
+
Depending on whether you access the classic or new API
|
|
45
|
+
you have different access methods, so we have to use
|
|
46
|
+
the typecode() method if .dtype.char isn't there.
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
return a.dtype.char
|
|
50
|
+
except AttributeError:
|
|
51
|
+
return a.typecode()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
del a
|
|
55
|
+
implementation_name = 'numpy'
|
|
56
|
+
try:
|
|
57
|
+
# PyVRML97 is from before numpy printed errors, we explicitly do not care
|
|
58
|
+
# about the divide-by-zero, which commonly happens in mesh data processing
|
|
59
|
+
# TODO: likely should rework the mesh processing to check manually and remove
|
|
60
|
+
# this sledge-hammer approach
|
|
61
|
+
seterr(all='ignore')
|
|
62
|
+
except Exception as err:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def safeCompare(first, second):
|
|
67
|
+
"""Watch out for pointless numpy truth-value checks"""
|
|
68
|
+
if first is None:
|
|
69
|
+
if second is None:
|
|
70
|
+
return True
|
|
71
|
+
else:
|
|
72
|
+
return False
|
|
73
|
+
elif second is None:
|
|
74
|
+
return False
|
|
75
|
+
if isinstance(first, (int, float, str)):
|
|
76
|
+
return first == second
|
|
77
|
+
if isinstance(first, ArrayType) and isinstance(second, ArrayType):
|
|
78
|
+
return bool(any(first == second))
|
|
79
|
+
elif type(first) != type(second):
|
|
80
|
+
return False
|
|
81
|
+
return bool(first == second)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def contiguous(a):
|
|
85
|
+
"""Force to a contiguous array"""
|
|
86
|
+
return array(a, typeCode(a))
|
vrml/cache.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""Caching mechanism for scene graph
|
|
2
|
+
|
|
3
|
+
Basically, the scenegraph Cache allows you to associate
|
|
4
|
+
data with particular nodes in the scene graph without
|
|
5
|
+
actually attaching the information to the particular
|
|
6
|
+
node being annotated.
|
|
7
|
+
|
|
8
|
+
This allows us to store cached information such as:
|
|
9
|
+
display list objects
|
|
10
|
+
texture objects
|
|
11
|
+
compiled array geometry
|
|
12
|
+
|
|
13
|
+
The cache uses weak reference key dictionaries to clear
|
|
14
|
+
the cache of data associated with deleted nodes within
|
|
15
|
+
the scene graph. similarly, cache is cleared when node
|
|
16
|
+
fields which are dependencies of the cache are changed.
|
|
17
|
+
|
|
18
|
+
The cache is based on the vrml.field and dispatch.dispatcher
|
|
19
|
+
modules. All field objects by default provide dispatcher
|
|
20
|
+
notifications on set and delete, and can provide
|
|
21
|
+
notification on get as well. The cache watches for these
|
|
22
|
+
notifications for dependent nodes where the dispatcher
|
|
23
|
+
messages are (type, fieldObject) for type in ("set","del").
|
|
24
|
+
|
|
25
|
+
There is a per-context cache, and a global cache, display-
|
|
26
|
+
lists, textures, etceteras should be stored in the per-
|
|
27
|
+
context cache, while data with dependencies only on the
|
|
28
|
+
node can be stored in the global cache.
|
|
29
|
+
|
|
30
|
+
Note:
|
|
31
|
+
The cache makes extensive use of weak references, and
|
|
32
|
+
was the failure case which exposed a number of problems
|
|
33
|
+
with the Python 2.2.2 weakref and weakkeydictionary
|
|
34
|
+
mechanisms.
|
|
35
|
+
"""
|
|
36
|
+
from vrml import protofunctions
|
|
37
|
+
import weakref
|
|
38
|
+
#from vrml.weakkeydictfix import WeakKeyDictionary
|
|
39
|
+
from pydispatch import dispatcher
|
|
40
|
+
import traceback
|
|
41
|
+
|
|
42
|
+
import sys
|
|
43
|
+
if sys.version_info[0] == 2:
|
|
44
|
+
bytes = str
|
|
45
|
+
else:
|
|
46
|
+
unicode = str
|
|
47
|
+
|
|
48
|
+
class Cache (dict):
|
|
49
|
+
"""Trivial sub-class of a dict which has some convenience methods
|
|
50
|
+
|
|
51
|
+
Maps id(client): {
|
|
52
|
+
key="": CacheHolder()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
That is, for each client node, a dictionary
|
|
56
|
+
holds opaque key(normally strings) to CacheHolder
|
|
57
|
+
instances. The CacheHolder is responsible for
|
|
58
|
+
most of the implementation of the cache.
|
|
59
|
+
"""
|
|
60
|
+
def getHolder( self, client, key = ""):
|
|
61
|
+
"""Return the cache holder for the given client and key"""
|
|
62
|
+
current = self.get(id(client))
|
|
63
|
+
if current is not None:
|
|
64
|
+
return current.get( key)
|
|
65
|
+
return None
|
|
66
|
+
def getData( self, client, key="", default=None):
|
|
67
|
+
"""Return the data for given client and key, default otherwise"""
|
|
68
|
+
current = self.get( id(client) )
|
|
69
|
+
if current is not None:
|
|
70
|
+
current = current.get( key )
|
|
71
|
+
if current is not None:
|
|
72
|
+
return current.data
|
|
73
|
+
return default
|
|
74
|
+
def holder(
|
|
75
|
+
self,
|
|
76
|
+
client,
|
|
77
|
+
data,
|
|
78
|
+
key="",
|
|
79
|
+
):
|
|
80
|
+
"""Create a new CacheHolder in this cache"""
|
|
81
|
+
return CacheHolder(
|
|
82
|
+
client,
|
|
83
|
+
data,
|
|
84
|
+
key,
|
|
85
|
+
self,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
CACHE = Cache()
|
|
89
|
+
getData = CACHE.getData
|
|
90
|
+
|
|
91
|
+
def cleaner( cache, id ):
|
|
92
|
+
"""Return callback function that cleans the given id from cache"""
|
|
93
|
+
def clean_id( weak ):
|
|
94
|
+
try:
|
|
95
|
+
del cache[id]
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
return clean_id
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class CacheHolder( object ):
|
|
102
|
+
"""Holder for data values within a cache
|
|
103
|
+
|
|
104
|
+
The CacheHolder provides the bulk of the cache
|
|
105
|
+
implementation. It associates an opaque data value
|
|
106
|
+
with a client node and an opaque key. The key
|
|
107
|
+
value allows multiple dimensions of storage, to
|
|
108
|
+
allow, for instance storing compiled shadow
|
|
109
|
+
information separate from compiled geometry
|
|
110
|
+
information.
|
|
111
|
+
|
|
112
|
+
The depend method uses the dispatcher module
|
|
113
|
+
to invalidate this CacheHolder when the given
|
|
114
|
+
fields for the given nodes are changed.
|
|
115
|
+
|
|
116
|
+
Attributes:
|
|
117
|
+
client -- weak reference to the client node
|
|
118
|
+
key -- strong reference to the opaque key value
|
|
119
|
+
data -- strong reference to the opaque data value
|
|
120
|
+
cache -- weak reference to the cache in which
|
|
121
|
+
we are storing ourselves
|
|
122
|
+
"""
|
|
123
|
+
#__slots__ = ('client','data','key','cache','nodeDependencies','__weakref__','notifier')
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
client, data,
|
|
127
|
+
key="",
|
|
128
|
+
cache = CACHE,
|
|
129
|
+
):
|
|
130
|
+
"""Initialise the cache-deletion callable
|
|
131
|
+
|
|
132
|
+
client -- the node doing the caching, if gc'd,
|
|
133
|
+
then the entire cache for the node is deleted
|
|
134
|
+
key -- opaque key into the cache's per-node storage
|
|
135
|
+
cache -- the particular cache in which to store
|
|
136
|
+
ourselves.
|
|
137
|
+
"""
|
|
138
|
+
client_id = id(client)
|
|
139
|
+
self.client = weakref.ref(client, cleaner( cache,client_id) )
|
|
140
|
+
self.key = key
|
|
141
|
+
self.data = data
|
|
142
|
+
self.cache = weakref.ref( cache )
|
|
143
|
+
self.nodeDependencies = []
|
|
144
|
+
|
|
145
|
+
# get the cached values for this client node
|
|
146
|
+
set = cache.get(client_id, None)
|
|
147
|
+
if set is None:
|
|
148
|
+
cache[ client_id ] = set = {}
|
|
149
|
+
set[key] = self
|
|
150
|
+
def set( self, data ):
|
|
151
|
+
"""Set data after instantiation"""
|
|
152
|
+
self.data = data
|
|
153
|
+
|
|
154
|
+
def depend( self, node, field=None ):
|
|
155
|
+
"""Add a dependency on given node's field value
|
|
156
|
+
|
|
157
|
+
source -- the node being watched
|
|
158
|
+
field -- the field on the node being watched
|
|
159
|
+
|
|
160
|
+
Dependency on the node means that this cache
|
|
161
|
+
holder will be invalidated if the field value
|
|
162
|
+
changes. This does not create a dependency
|
|
163
|
+
on the existence of node, so you should set
|
|
164
|
+
the dependency for the field holding any
|
|
165
|
+
nodes which should invalidate this CacheHolder
|
|
166
|
+
|
|
167
|
+
Note:
|
|
168
|
+
This does not affect any other CacheHolder
|
|
169
|
+
for our client node.
|
|
170
|
+
"""
|
|
171
|
+
if field is not None:
|
|
172
|
+
if isinstance( field, (bytes,unicode)):
|
|
173
|
+
field = protofunctions.getField(node, field)
|
|
174
|
+
self.depend_signal(
|
|
175
|
+
('set', field),#signal
|
|
176
|
+
node, # sender
|
|
177
|
+
)
|
|
178
|
+
self.depend_signal(
|
|
179
|
+
('del', field),#signal
|
|
180
|
+
node, # sender
|
|
181
|
+
)
|
|
182
|
+
self.depend_signal(
|
|
183
|
+
('route', field),#signal
|
|
184
|
+
node, # sender
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
# dependency on the mere existence of the node
|
|
188
|
+
self.depend_object( node )
|
|
189
|
+
def depend_signal( self, signal, sender=dispatcher.Any ):
|
|
190
|
+
"""Depend on signal from sender"""
|
|
191
|
+
dispatcher.connect(
|
|
192
|
+
self.clear,#receiver
|
|
193
|
+
signal,
|
|
194
|
+
sender,
|
|
195
|
+
)
|
|
196
|
+
def depend_object( self, node ):
|
|
197
|
+
"""Depend on node's existence"""
|
|
198
|
+
self.nodeDependencies.append(
|
|
199
|
+
weakref.ref(
|
|
200
|
+
node,
|
|
201
|
+
self,
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
def clear( self, signal=None, sender=None ):
|
|
205
|
+
"""Clear this object's held value (only)"""
|
|
206
|
+
if not self.client():
|
|
207
|
+
self( signal=signal, sender=sender )
|
|
208
|
+
else:
|
|
209
|
+
self.data = None
|
|
210
|
+
def __call__( self, signal=None, sender=None ):
|
|
211
|
+
"""Delete the cached value (this object)
|
|
212
|
+
|
|
213
|
+
This de-registers ourselves from our cache object,
|
|
214
|
+
with suitable checks for whether our cache is still
|
|
215
|
+
alive itself, and whether it still has an entry
|
|
216
|
+
for our client and our key.
|
|
217
|
+
|
|
218
|
+
If this is the last registered cache object
|
|
219
|
+
for our client, deletes the overall cache dictionary
|
|
220
|
+
for the client.
|
|
221
|
+
"""
|
|
222
|
+
try:
|
|
223
|
+
cache = self.cache()
|
|
224
|
+
if cache is None:
|
|
225
|
+
return 0
|
|
226
|
+
client = self.client()
|
|
227
|
+
if client is None:
|
|
228
|
+
return 0
|
|
229
|
+
client_id = id(client)
|
|
230
|
+
current = cache.get( client_id )
|
|
231
|
+
self.data = None
|
|
232
|
+
if current is None:
|
|
233
|
+
return 0
|
|
234
|
+
try:
|
|
235
|
+
del current[ self.key ]
|
|
236
|
+
if not current:
|
|
237
|
+
try:
|
|
238
|
+
del cache[ client_id ]
|
|
239
|
+
except KeyError:
|
|
240
|
+
pass
|
|
241
|
+
try:
|
|
242
|
+
del self.nodeDependencies[:]
|
|
243
|
+
except:
|
|
244
|
+
pass
|
|
245
|
+
return 1
|
|
246
|
+
except KeyError:
|
|
247
|
+
return 0
|
|
248
|
+
except RuntimeError:
|
|
249
|
+
traceback.print_exc()
|
|
250
|
+
|
vrml/copier.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Object representing node-copying pass"""
|
|
2
|
+
|
|
3
|
+
class Copier( object ):
|
|
4
|
+
"""An object representing a node-copying pass"""
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
shareProtos = 1,
|
|
8
|
+
instantiation=0,
|
|
9
|
+
):
|
|
10
|
+
"""Instantiate the copier object
|
|
11
|
+
|
|
12
|
+
# share prototypes, we don't currently support not doing this!
|
|
13
|
+
shareProtos -- whether to try to share prototypes between source
|
|
14
|
+
and copied nodes
|
|
15
|
+
instantiation -- whether this is a prototype-instantiation copy
|
|
16
|
+
baseNode -- baseNode for prototype instantiations, this is the
|
|
17
|
+
node whose sub-scenegraph is being built by the copier
|
|
18
|
+
"""
|
|
19
|
+
self.shareProtos, self.instantiation = shareProtos, instantiation
|
|
20
|
+
def use( self, clientNode, newNode=None ):
|
|
21
|
+
"""See whether (or register that) we have a USE for this (source) clientNode"""
|
|
22
|
+
if not hasattr( self, 'useNodes'):
|
|
23
|
+
self.useNodes = {}
|
|
24
|
+
if newNode is not None:
|
|
25
|
+
self.useNodes[ clientNode ] = newNode
|
|
26
|
+
else:
|
|
27
|
+
return self.useNodes.get( clientNode )
|