summaryrefslogtreecommitdiff
path: root/cad/src/temporary_commands/ZoomToAreaMode.py
blob: 8156cf2473c49dbbcdd42922af4d37c52b188d9b (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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# Copyright 2007-2008 Nanorex, Inc.  See LICENSE file for details.
"""
Zoom to Area functionality.

@author:    Mark Sims
@version:   $Id$
@copyright: 2007-2008 Nanorex, Inc.  See LICENSE file for details.
@license:   GPL

History:
Mark 2008-01-31: Renamed from ZoomMode to ZoomToAreaMode.py
"""

from Numeric import dot

from OpenGL.GL import GL_DEPTH_TEST
from OpenGL.GL import glDisable
from OpenGL.GL import GL_LIGHTING
from OpenGL.GL import glColor3d
from OpenGL.GL import GL_COLOR_LOGIC_OP
from OpenGL.GL import glEnable
from OpenGL.GL import GL_XOR
from OpenGL.GL import glLogicOp
from OpenGL.GL import glFlush
from OpenGL.GL import GL_DEPTH_COMPONENT
from OpenGL.GL import glReadPixelsf
from OpenGL.GLU import gluUnProject

from geometry.VQT import V, A
from graphics.drawing.drawers import drawrectangle
from utilities.constants import GL_FAR_Z
from temporary_commands.TemporaryCommand import TemporaryCommand_Overdrawing


# == the GraphicsMode part

class ZoomToAreaMode_GM( TemporaryCommand_Overdrawing.GraphicsMode_class ):
    """
    Custom GraphicsMode for use as a component of ZoomToAreaMode.
    """
        
    def leftDown(self, event):
        """
        Compute the rubber band window starting point, which
        lies on the near clipping plane, projecting into the same 
        point that current cursor points at on the screen plane.
        """
        self.pWxy = (event.pos().x(), self.glpane.height - event.pos().y())
        p1 = A(gluUnProject(self.pWxy[0], self.pWxy[1], 0.005)) 
        
        self.pStart = p1
        self.pPrev = p1
        self.firstDraw = True

        self.command.glStatesChanged = True
            # this warns our exit code to undo the following OpenGL state changes:
        
        self.glpane.redrawGL = False
        glDisable(GL_DEPTH_TEST)
        glDisable(GL_LIGHTING)
        rbwcolor = self.command.rbwcolor
        glColor3d(rbwcolor[0], rbwcolor[1], rbwcolor[2])
        
        glEnable(GL_COLOR_LOGIC_OP)
        glLogicOp(GL_XOR)
        
        return
        
    def leftDrag(self, event):
        """
        Compute the changing rubber band window ending point. Erase the
        previous window, draw the new window.
        """
        # bugs 1190, 1818 wware 4/05/2006 - sometimes Qt neglects to call leftDown
        # before this
        if not hasattr(self, "pWxy") or not hasattr(self, "firstDraw"):
            return
        cWxy = (event.pos().x(), self.glpane.height - event.pos().y())

        rbwcolor = self.command.rbwcolor

        if not self.firstDraw: #Erase the previous rubberband window
            drawrectangle(self.pStart, self.pPrev, self.glpane.up,
                          self.glpane.right, rbwcolor)
        self.firstDraw = False

        self.pPrev = A(gluUnProject(cWxy[0], cWxy[1], 0.005))
        # draw the new rubberband window
        drawrectangle(self.pStart, self.pPrev, self.glpane.up,
                      self.glpane.right, rbwcolor)
        
        glFlush()
        self.glpane.swapBuffers() # Update display
        
        # Based on a suggestion in bug 2961, I added this second call to 
        # swapBuffers(). It definitely helps, but the rectangle disappears 
        # once the zoom cursor stops moving. I suspect this is due to 
        # a gl_update() elsewhere.  I'll ask Bruce about his thoughts on 
        # this. --Mark 2008-12-22.
        self.glpane.swapBuffers() 
        return
        
    def leftUp(self, event):
        """
        Erase the final rubber band window and do zoom if user indeed draws a
        rubber band window.
        """
        # bugs 1190, 1818 wware 4/05/2006 - sometimes Qt neglects to call
        # leftDown before this
        if not hasattr(self, "pWxy") or not hasattr(self, "firstDraw"):
            return
        cWxy = (event.pos().x(), self.glpane.height - event.pos().y())
        zoomX = (abs(cWxy[0] - self.pWxy[0]) + 0.0) / (self.glpane.width + 0.0)
        zoomY = (abs(cWxy[1] - self.pWxy[1]) + 0.0) / (self.glpane.height + 0.0)

        # The rubber band window size can be larger than that of glpane.
        # Limit the zoomFactor to 1.0
        zoomFactor = min(max(zoomX, zoomY), 1.0)
        
        # Huaicai: when rubber band window is too small,
        # like a double click, a single line rubber band, skip zoom
        DELTA = 1.0E-5
        if self.pWxy[0] == cWxy[0] or self.pWxy[1] == cWxy[1] \
                or zoomFactor < DELTA:
            
            self.command.command_Done()
            return
        
        # Erase the last rubber-band window
        rbwcolor = self.command.rbwcolor
        drawrectangle(self.pStart, self.pPrev, self.glpane.up,
                      self.glpane.right, rbwcolor)
        glFlush()
        self.glpane.swapBuffers()
        
        winCenterX = (cWxy[0] + self.pWxy[0]) / 2.0
        winCenterY = (cWxy[1] + self.pWxy[1]) / 2.0
        winCenterZ = \
            glReadPixelsf(int(winCenterX), int(winCenterY), 1, 1,
                          GL_DEPTH_COMPONENT)
        
        assert winCenterZ[0][0] >= 0.0 and winCenterZ[0][0] <= 1.0
        if winCenterZ[0][0] >= GL_FAR_Z:  # window center touches nothing
            p1 = A(gluUnProject(winCenterX, winCenterY, 0.005))
            p2 = A(gluUnProject(winCenterX, winCenterY, 1.0))

            los = self.glpane.lineOfSight
            k = dot(los, -self.glpane.pov - p1) / dot(los, p2 - p1)
            
            zoomCenter = p1 + k*(p2-p1)
            
        else:
            zoomCenter = \
                A(gluUnProject(winCenterX, winCenterY, winCenterZ[0][0]))
        self.glpane.pov = V(-zoomCenter[0], -zoomCenter[1], -zoomCenter[2]) 
        
        # The following are 2 ways to do the zoom, the first one 
        # changes view angles, the 2nd one change viewing distance
        # The advantage for the 1st one is model will not be clipped by 
        #  near or back clipping planes, and the rubber band can be 
        # always shown. The disadvantage: when the view field is too 
        # small, a selection window may be actually act as a single pick.
        # rubber band window will not look as rectangular any more.
        #zf = self.glpane.getZoomFactor() # [note: method does not exist]
        #zoomFactor = pow(zoomFactor, 0.25)
        #zoomFactor *= zf
        #self.glpane.setZoomFactor(zoomFactor) # [note: method does not exist]
        
        # Change viewing distance to do zoom. This works better with
        # mouse wheel, since both are changing viewing distance, and
        # it's not too bad of model being clipped, since the near/far clip
        # plane change as scale too.
        self.glpane.scale *= zoomFactor
       
        self.command.command_Done()
        return
        
    def update_cursor_for_no_MB(self): # Fixes bug 1638. Mark 3/12/2006.
        """
        Update the cursor for 'Zoom' mode.
        """
        self.glpane.setCursor(self.win.ZoomCursor)

    def _restore_patches_by_GraphicsMode(self):
        """
        This is run when we exit this command for any reason.
        """
        # Note: this is no longer part of the GraphicsMode API 
        # but we retain it as an essentially private method and call it from
        # self.command.command_will_exit in that case. [bruce 080829 comment]
        #(comment slightly updated on 2008-09-26 : removed reference to 
        #the 'new command api' because it is now the default API we use 
        # -- Ninad)
        # [bruce 080929 made this private]
        
        # If OpenGL states changed during this mode, we need to restore
        # them before exit. Currently, only leftDown() will change that.
        # [bruce 071011/071012 change: do this in
        #  _restore_patches_by_GraphicsMode, not in Done]
        if self.command.glStatesChanged:
            self.glpane.redrawGL = True
            glDisable(GL_COLOR_LOGIC_OP)
            glEnable(GL_LIGHTING)
            glEnable(GL_DEPTH_TEST)
        return
    
    pass

# == the Command part

class ZoomToAreaMode(TemporaryCommand_Overdrawing):
    """
    Encapsulates the Zoom Tool functionality.
    """
    
    # TODO: rename to ZoomTool or ZoomCommand or TemporaryCommand_Zoom or ...
    
    # class constants
    commandName = 'ZOOMTOAREA'
    featurename = "Zoom to Area Tool"
    from utilities.constants import CL_VIEW_CHANGE
    command_level = CL_VIEW_CHANGE

    GraphicsMode_class = ZoomToAreaMode_GM
        

    def command_entered(self):
        super(ZoomToAreaMode, self).command_entered()
        bg = self.glpane.backgroundColor
        
        # rubber window shows as white color normally, but when the
        # background becomes bright, we'll set it as black.
        brightness = bg[0] + bg[1] + bg[2]
        if brightness > 1.5:
            self.rbwcolor = bg
                # note: accessed as self.command.rbwcolor in our GraphicsMode part
        else:
            # REVIEW: should the following color be converted to tuple(),
            # in case it's black and the Numeric.array version
            # would fool some code due to being boolean false?
            # [bruce 080829 question]
            self.rbwcolor = A((1.0, 1.0, 1.0)) - A(bg)
        
        self.glStatesChanged = False
            # note: accessed as self.command.glStatesChanged in our GraphicsMode part
        return


    def command_enter_misc_actions(self):
        super(ZoomToAreaMode, self).command_enter_misc_actions()
        
        self.win.zoomToAreaAction.setChecked(1) # toggle on the Zoom Tool icon

    def command_exit_misc_actions(self):       
        self.win.zoomToAreaAction.setChecked(0) # toggle off the Zoom Tool icon
        super(ZoomToAreaMode, self).command_exit_misc_actions()

    def command_will_exit(self):        
        self.graphicsMode._restore_patches_by_GraphicsMode()
        super(ZoomToAreaMode, self).command_will_exit()
        return