summaryrefslogtreecommitdiff
path: root/cad/src/exprs/DragBehavior_AlongLine.py
blob: 1c7113303160280e3509a6c75b7b4991ea394eb9 (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
# Copyright 2007-2008 Nanorex, Inc.  See LICENSE file for details.
"""
DragBehavior_AlongLine.py

@author: Bruce
@version: $Id$
@copyright: 2007-2008 Nanorex, Inc.  See LICENSE file for details.


the DragBehavior in this file improves on the one in test_statearray_2.py:
- uses its own coordsys, not the one in the Highlightable
- computes the translation from the height (for internal and external use)
- has a range limit

TODO:

needs a refactoring; see comments herein about "refactoring"

[following comment came from DragBehavior.py, guessing it was about this class:]
maybe revise how it works for off-line points -- not closest in space
but closest within a plane perp to screen and -- what? ###
"""
from exprs.Highlightable import SavedCoordsys
from exprs.geometry_exprs import Ray
from exprs.Exprs import tuple_Expr
from exprs.attr_decl_macros import Arg, Option, Instance
from exprs.ExprsConstants import StateRef, ORIGIN
from exprs.__Symbols__ import Anything
from exprs.DragBehavior import DragBehavior


class DragBehavior_AlongLine(DragBehavior): #070318 (compare to SimpleDragBehavior)
    """
    A drag behavior which moves the original hitpoint along a line,
    storing only its 1d-position-offset along the line's direction
    [noting that the hitpoint is not necessarily equal to the moved object's origin]
    [#doc better]
    """
    # args [#e replace 'height' with 'posn_parameter' or 'posn_param' in these comments & docstrings]
    highlightable = Arg(Anything) ###e is there a way we can require that we're passed an Instance rather than making it ourselves?
        # I suspect that violating that caused the bug in example 2.
        # (A way that would work: a specialcase check in _init_instance.
        #  But I'd rather have an option on Arg to do that. require_already_instance?? Or ArgInstance? that latter invites confusion
        #  since it's not analogous to ArgExpr.)
    posn_parameter_ref = Arg(StateRef, doc = "where the variable height is stored")
    constrain_to_line = Arg(Ray, doc = "the line/ray on which the height is interpreted as a position")
        ###e rename: constraint_line? line_to_constrain_to? constrain_to_this_line?
        # note: the position of the height on this line is typically used as the position of the drawn movable object's origin;
        # we shouldn't assume the drag startpoint is on the line, since the drawn movable object might be touched at any of its points.
        ##e rename Ray -> MarkedLine? (a line, with real number markings on it) ParametricLine? (no, suggests they can be distorted/curved)
    range = Option(tuple_Expr, None, doc = "range limit of height")#nim
    ##e drag event object can be passed to us... as a delegate! [In recent code, it seems like the Highlightable is taking this role.]

    # (if this delegates or supers to something that knows all about the drag in a lower level way,
    #  then maybe it's not so bad to be getting dragevent info from self rather than a dragevent arg... hmm.
    #  An argument in favor of that: self is storing state related to one drag anyway, so nothing is lost
    #  by assuming some other level of self stores some other level of that state. So maybe we create a standard
    #  DragHandling object, then create a behavior-specific delegate to it, to which *it* delegates on_press etc;
    #  and we also get it to delegate state-modifying calls to some external object -- or maybe that's not needed
    #  if things like this one's stateref are enough. ##k)
    #
    # related: Maybe DraggableObject gets split into the MovableObject and the DragBehavior...

    # same as in SimpleDragBehavior: saved_coordsys & current_event_mousepoint

    # state:
    saved_coordsys = Instance( SavedCoordsys() ) # provides transient state for saving a fixed coordsys to use throughout a drag

    # helper methods (these probably belong in a superclass):
    def current_event_mousepoint(self, *args, **kws): #e zap this and inline it, for clarity? or move it into DragBehavior superclass??
        return self.saved_coordsys.current_event_mousepoint(*args, **kws)

    def current_event_mouseray(self):
        p0 = self.current_event_mousepoint(depth = 0.0) # nearest depth ###k
        p1 = self.current_event_mousepoint(depth = 1.0) # farthest depth ###k
        return Ray(p0, p1 - p0) #e passing just p1 should be ok too, but both forms can't work unless p0,p1 are typed objects...

    # specific methods
    def _C__translation(self): ### WARNING: used externally too -- rename to be not private if we decide that's ok ###e
        """
        compute self._translation from the externally stored height
        """
        k = self.posn_parameter_ref.value
        return self.constrain_to_line.posn_from_params(k) #e review renaming, since we are asking it for a 3-tuple

    def on_press(self):
        self.saved_coordsys.copy_from( self.highlightable) # needed before using current_event_mousepoint or current_event_mouseray
            # (since self.highlightable's coordsys changes during the drag)
        self.startpoint = self.current_event_mousepoint() # the touched point on the visible object (hitpoint)
        self.offset = self.startpoint - (ORIGIN + self._translation) #k maybe ok for now, but check whether sensible in long run
        self.line = self.constrain_to_line + self.offset # the line the hitpoint is on (and constrained to, if handle is rigid)
            # (this is parallel to self.constrain_to_line and intersects the hitpoint)
    def on_drag(self):
        # Note: we can assume this is a "real drag" (not one which is too short to count), due to how we are called.
        mouseray = self.current_event_mouseray()
        k = self.line.closest_pt_params_to_ray(mouseray)
            #
            # BUG: for lines with a lot of Z direction, in perspective view,
            # this is wrong -- we want the closest point on the screen,
            # not in space. The current code (closest point in space)
            # would tend to come up with a point too near the screen,
            # if the constraint line and the mouseray are diverging away
            # from each other (in model space) with depth.
            #
            # TODO: fix this. Possible fixes include:
            # - common code, using a projection matrix & its inverse (seems hard)
            # - separate code for Ortho vs Perspective case (needs eyepoint)
            #   (might be clearest & simplest; it might turn out we'd want other
            #    behavior tweaks which differed in these cases -- for example,
            #    stop dragging and turn the handle red when it gets so far away
            #    in model space (near the "point at infinity" visible on the screen
            #    for an infinite line -- aka the "point of convergence" for a family
            #    of parallel lines) that its motion would be too sensitive, since
            #    the constraint line is too close to perpendicular to the screen)
            # - save the initial mouseray too, since it gives us enough info
            #   (along with current one) to know the entire projection
            #   (except when they coincide, in which case no motion is needed).
            #   Alg for this is not yet worked out. Q: is an ortho projection
            #   along initial mouseray a good approximation? Probably not,
            #   if we approach the "point at infinity".
            #
            # Also we need a refactoring, so that one external object can store
            # both the geometric info about the constraint, and the state of the
            # dragpoint along it, accessible as parameter-along-line or point or both,
            # with either being the underlying state. (Unless nothing less than a
            # DragBehavior can actually do all those things, in which case,
            # we need variants depending on whether the point or the parameter
            # is the underlying state. But more likely, we want a DragState which
            # knows how to make the underlying state convenient, and a DragBehavior
            # which knows how to connect that to mouse gestures, so these can vary
            # separately.)
            #
            # [bruce 070913 comment]
            #
        if k is not None:
            # store k, after range-limiting
            range = self.range # don't use the python builtin of the same name,
                               #in this method! (#e or rename the option?)
            if range is not None:
                low, high = range
                if low is not None and k < low:
                    k = low
                if high is not None and k > high:
                    k = high
            self.posn_parameter_ref.value = k
            ##e by analogy with DraggableObject, should we perhaps save this
            ##side effect until the end?
        return
    def on_release(self):
        pass
    pass # end of class DragBehavior_AlongLine

# end