summaryrefslogtreecommitdiff
path: root/cad/src/scratch/TransformState.py
blob: 7eb337496d6aa553e5cc86307993f2ea7d21dc7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# Copyright 2009 Nanorex, Inc.  See LICENSE file for details.
"""
TransformState.py -- mutable transform classes, shared by TransformNodes

NOT YET USED as of 090204... declared a scratch file, bruce 090225;
see comments in TransformNode docstring for why.

@author: Bruce
@version: $Id$
@copyright: 2009 Nanorex, Inc.  See LICENSE file for details.
"""


from foundation.state_utils import StateMixin
from foundation.state_utils import copy_val

from utilities.Comparison import same_vals

# ==

ORIGIN = V(0, 0, 0)
_IDENTITY_TRANSLATION = V(0, 0, 0)
_IDENTITY_ROTATION = Q(1, 0, 0, 0)
    # review: let these be class attrs of the types, or of the classes V and Q?
    # REVIEW: does same_vals think 0 and 0.0 are the same? if not, should we use 0.0 here?
    # TEST whether anything looks like identity... note that if initial values use 0 this might be ok

# ==

class TransformState(StateMixin, object):
    """
    A mutable orthonormal 3d transform, represented as a rotation followed by
    a translation (with the rotation and translation being undoable state).

    Changes to the transform value can be subscribed to. ###doc how
    """
    # REVIEW:
    # - can you subscribe separately to changes to its translation and rotation?
    #   - if so, are they public usagetracked attrs?
    # - make value accessible as data object? (if so, let it be an attr self.value)
    #   (note: a ref to this would autosubscribe to the two components; or we could cache constructed object)
    # - permit comparison as if we were data? (no, buggy if someone uses '==' when they mean 'is')

    # todo:
    # - undoable state for the data
    # - finish nim ops to change the data (translate, rotate, etc),
    # - make sure the data (or overall value only?) can be subscribed to

    # TODO: make the State version work... initially we just use hand-invals, below.
    # but we'll need to make State work here as soon as we need to usage-track these
    # for purposes of properly implementing the DrawingSet graphics API.
    #
    ##rotation = State(Quaternion)
    ##
    ##translation = State(Vector3D)
    ##
    ##    ### IMPLEM:
    ##    # - how do we make State work here? ###REVIEW how we do it in Test_ConnectWithState, funmode
    ##    #   - and how do we let it be undoable state? compatible with _s_attr decls?? ###
    ##    # - get default value from type;
    ##    # - define these type names
    ##    ### REVIEW:
    ##    # - review type name: Vector vs Vector3 vs Vector3D ? Or just use V and Q?

    rotation = _IDENTITY_ROTATION
    translation = _IDENTITY_TRANSLATION

    # not yet needed:
    ## value = TransformData( rotation, translation ) # REVIEW expr name, whether this formula def is appropriate


    # TODO: add formula for matrix? (to help merge with TransformControl?)


    def _kluge_manual_inval(self):
        """
        Kluge: manually invalidate our overall transform value.

        @note: calls only needed until we support State decls for
               self.rotation and self.translation;
               implem only needed in some subclasses
        """
        return

    def applyDataFrom(self, other):
        """
        """
        self.rotate( other.rotation, center = ORIGIN)
        self.translate( other.translation)

    def translate(self, vector): # see also 'def move' in other classes
        self.translation = self.translation + vector
        self._kluge_manual_inval()

    def rotate(self, quat, center = None):
        """
        Rotate self around self.translation, or around the specified center.
        """
        self.rotation = self.rotation + quat # review operation order, compare with chunk.py --
            ### also compare default choice of center, THIS MIGHT BE WRONG
        if center is not None:
            offset = self.translation - center
            self.translate( quat.rot(offset) - offset ) # REVIEW - compare with chunk.py
        self._kluge_manual_inval()
        return

    def is_identity(self): # review: optimize? special fast case if data never modified?
        return same_vals( (self.translation, self.rotation),
                          (_IDENTITY_TRANSLATION, _IDENTITY_ROTATION) )
    pass

# ==

class StaticTransform( TransformState):
    """
    A subclass of TransformState that keeps track of which nodes it belongs to,
    and owns a TransformControl, and maintains it to always hold the same transform
    value as self.
    """
    # REVIEW: merge this class with TransformControl?
    ### IMPLEM: intercept changes and inval or update our TC... and its own subscribers in the graphics code...

    transformControl = None

    def __init__(self, value = None, tc = None):
        self._nodes = {}
        if value is not None:
            # copy our value from the given value or TransformState
            if isinstance(value, TransformState):
                self.rotation = copy_val(value.rotation)
                self.translation = copy_val(value.translation)
            else:
                assert 0 # nim
        assert tc ### probably required by other code... REVIEW
        if tc is not None:
            self.transformControl = tc ### IMPLEM proper fields/effects and maintenance of the relationship
        return

    def add_node(self, node):
        self._nodes[node] = node
        return

    def del_node(self, node):
        del self._nodes[node]
        return

    def nodecount(self):
        """
        Return the number of nodes we belong to.
        """
        return len(self._nodes)

    def iternodes(self):
        """
        yield the nodes we belong to
        """
        return self._nodes.itervalues()

    pass

# ==

class DynamicTransform( TransformState):
    """
    A subclass of TransformState that keeps track of which objects bridge
    between nodes which have it and nodes which don't have it, and passes
    its value invalidations on to those objects.
    """
    def __init__(self, nodes):
        """
        Initialize self, and add self to the given nodes.
        """
        TransformState.__init__(self)
        self._nodes = nodes # list, not dict -- no need to modify or index
            # only needed for self.destroy();
            # not needed for determining whether node-bridging objects added
            # later are self-bridging, since our nodes point to us too
            # (and anyway, AFAIK we never add bridging objects later)
        for node in nodes:
            node.dynamic_transform = self ### REVIEW; needs to inval subscribers
        self._bridging_objects = self._get_bridging_objects()
        # note: if our initial value wasn't identity, we'd also need to call
        # self._invalidate_transform_value() here.
        return

    def _get_bridging_objects(self):
        # Anything on one of our nodes which bridges *anything*,
        # is also known to be on self, therefore one of *our* bridging objects.
        # More precisely: it's on self; it's not only on self (or not bridging);
        # thus it's a self-bridging object (a node on self, a node not on self).
        res = []
        for node in self.nodes:
            res.extend( node.bridging_objects() )
            ### REVIEW: can one be included more than once? if so, use a dict...
            # guess: yes, once we have multi-node ones. ###
        return res

    def _kluge_manual_inval(self):
        TransformState._kluge_manual_inval(self)
        self._invalidate_transform_value()

    def _invalidate_transform_value(self):
        for bo in self._bridging_objects:
            bo.invalidate_distortion()
        return

    pass

# end