summaryrefslogtreecommitdiff
path: root/cad/src/graphics/widgets/GLPane_lighting_methods.py
blob: de9ccad819164782519e5ed7111d62ab9b9ba044 (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
263
264
265
266
267
# Copyright 2004-2009 Nanorex, Inc.  See LICENSE file for details.
"""
GLPane_lighting_methods.py

@version: $Id$
@copyright: 2004-2009 Nanorex, Inc.  See LICENSE file for details.

bruce 080910 split this out of class GLPane
"""

from graphics.drawing.gl_lighting import glprefs_data_used_by_setup_standard_lights

from graphics.drawing.gl_lighting import _default_lights # REVIEW: rename as a constant?
    # note: pylint claims incorrectly that this import of _default_lights is unused.
    # In fact, it's used, then locally overridden.
    # (So the code is confusing but does use it. TODO: clean it up. [bruce 080918 comment])
from graphics.drawing.gl_lighting import setup_standard_lights

from utilities.prefs_constants import glpane_lights_prefs_key
from utilities.prefs_constants import light1Color_prefs_key
from utilities.prefs_constants import light2Color_prefs_key
from utilities.prefs_constants import light3Color_prefs_key

import foundation.preferences as preferences
import foundation.env as env

from OpenGL.GL import glMatrixMode
from OpenGL.GL import GL_MODELVIEW
from OpenGL.GL import GL_PROJECTION
from OpenGL.GL import glLoadIdentity
from OpenGL.GL import glEnable
from OpenGL.GL import GL_NORMALIZE

from utilities.debug import print_compact_traceback

from utilities.debug_prefs import debug_pref
from utilities.debug_prefs import Choice_boolean_False

_DEBUG_LIGHTING = False #bruce 050418


class GLPane_lighting_methods(object):
    """
    private mixin for providing lighting methods to class GLPane
    """
    #bruce 050311 wrote these in class GLPane (rush order for Alpha4)
    #bruce 080910 split them into a separate file

    # default values of instance variables
    
    # [bruce 051212 comment: not sure if this needs to be in sync with any other values;
    #  also not sure if this is used anymore, since __init__ sets _lights from prefs db via loadLighting.]
    _lights = _default_lights

    _default_lights = _lights # this copy will never be changed

    need_setup_lighting = True # whether the next paintGL needs to call _setup_lighting (in our method setup_lighting_if_needed)

    _last_glprefs_data_used_by_lights = None #bruce 051212, replaces/generalizes _last_override_light_specular

    def setup_lighting_if_needed(self): #bruce 080910 un-inlined this
        if self.need_setup_lighting \
           or (self._last_glprefs_data_used_by_lights !=
               glprefs_data_used_by_setup_standard_lights( self.glprefs)) \
           or debug_pref("always setup_lighting?", Choice_boolean_False):
            #bruce 060415 added debug_pref("always setup_lighting?"),
            # in GLPane and ThumbView [KEEP DFLTS THE SAME!!];
            # using it makes specularity work on my iMac G4,
            # except for brief periods as you move mouse around to change selobj
            # (also observed on G5, but less frequently I think).
            # BTW using this (on G4) has no effect on whether "new wirespheres"
            # is needed to make wirespheres visible.
            #
            # (bruce 051126 added override_light_specular part of condition)
            # I don't know if it matters to avoid calling this every time...
            # in case it's slow, we'll only do it when it might have changed.
            self.need_setup_lighting = False # set to true again if setLighting is called
            self._setup_lighting()
        return

    def setLighting(self, lights, _guard_ = 6574833, gl_update = True): 
        """
        Set current lighting parameters as specified
        (using the format as described in the getLighting method docstring).
        This does not save them in the preferences file; for that see the saveLighting method.
        If option gl_update is False, then don't do a gl_update, let caller do that if they want to.
        """
        assert _guard_ == 6574833 # don't permit optional args to be specified positionally!!
        try:
            # check, standardize, and copy what the caller gave us for lights
            res = []
            lights = list(lights)
            assert len(lights) == 3
            for c,a,d,s,x,y,z,e in lights:
                # check values, give them standard types
                r = float(c[0])
                g = float(c[1])
                b = float(c[2])
                a = float(a)
                d = float(d)
                s = float(s)
                x = float(x)
                y = float(y)
                z = float(z)
                assert 0.0 <= r <= 1.0
                assert 0.0 <= g <= 1.0
                assert 0.0 <= b <= 1.0
                assert 0.0 <= a <= 1.0
                assert 0.0 <= d <= 1.0
                assert 0.0 <= s <= 1.0
                assert e in [0,1,True,False]
                e = not not e
                res.append( ((r,g,b),a,d,s,x,y,z,e) )
            lights = res
        except:
            print_compact_traceback("erroneous lights %r (ignored): " % lights)
            return
        self._lights = lights
        # set a flag so we'll set up the new lighting in the next paintGL call
        self.need_setup_lighting = True
        #e maybe arrange to later save the lighting in prefs... don't know if this belongs here
        # update GLPane unless caller wanted to do that itself
        if gl_update:
            self.gl_update()
        return

    def getLighting(self):
        """
        Return the current lighting parameters.
        [For now, these are a list of 3 tuples, one per light,
        each giving several floats and booleans
        (specific format is only documented in other methods or in their code).]
        """
        return list(self._lights)

    def _setup_lighting(self):
        """
        [private method]
        Set up lighting in the model (according to self._lights).
        [Called from both initializeGL and paintGL.]
        """
        # note: there is some duplicated code in this method
        # in GLPane_lighting_methods (has more comments) and ThumbView,
        # but also significant differences. Should refactor sometime.
        # [bruce 060415/080912 comment]
        
        glEnable(GL_NORMALIZE)
            # bruce comment 050311: I don't know if this relates to lighting or not
            # grantham 20051121: Yes, if NORMALIZE is not enabled (and normals
            # aren't unit length or the modelview matrix isn't just rotation)
            # then the lighting equation can produce unexpected results.  

        #bruce 050413 try to fix bug 507 in direction of lighting:
        ##k might be partly redundant now; not sure whether projection matrix needs to be modified here [bruce 051212]
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

        #bruce 051212 moved most code from this method into new function, setup_standard_lights
        setup_standard_lights( self._lights, self.glprefs)

        # record what glprefs data was used by that, for comparison to see when we need to call it again
        # (not needed for _lights since another system tells us when that changes)
        self._last_glprefs_data_used_by_lights = glprefs_data_used_by_setup_standard_lights( self.glprefs)
        return

    def saveLighting(self):
        """
        save the current lighting values in the standard preferences database
        """
        try:
            prefs = preferences.prefs_context()
            key = glpane_lights_prefs_key
            # we'll store everything in a single value at this key,
            # making it a dict of dicts so it's easy to add more lighting attrs (or lights) later
            # in an upward-compatible way.
            # [update, bruce 051206: it turned out that when we added lots of info it became
            #  not upward compatible, causing bug 1181 and making the only practical fix of that bug
            #  a change in this prefs key. In successive commits I moved this key to prefs_constants,
            #  then renamed it (variable and key string) to try to fix bug 1181. I would also like to find out
            #  what's up with our two redundant storings of light color in prefs db, ###@@@
            #  but I think bug 1181 can be fixed safely this way without my understanding that.]

            (((r0,g0,b0),a0,d0,s0,x0,y0,z0,e0), \
             ( (r1,g1,b1),a1,d1,s1,x1,y1,z1,e1), \
             ( (r2,g2,b2),a2,d2,s2,x2,y2,z2,e2)) = self._lights

            # now process it in a cleaner way
            val = {}
            for (i, (c,a,d,s,x,y,z,e)) in zip(range(3), self._lights):
                name = "light%d" % i
                params = dict( color = c, \
                               ambient_intensity = a, \
                               diffuse_intensity = d, \
                               specular_intensity = s, \
                               xpos = x, ypos = y, zpos = z, \
                               enabled = e )
                val[name] = params
            # save the prefs to the database file
            prefs[key] = val
            # This was printing many redundant messages since this method is called 
            # many times while changing lighting parameters in the Preferences | Lighting dialog.
            # Mark 051125.
            #env.history.message( greenmsg( "Lighting preferences saved" ))
        except:
            print_compact_traceback("bug: exception in saveLighting (pref changes not saved): ")
            #e redmsg?
        return

    def loadLighting(self, gl_update = True):
        """
        load new lighting values from the standard preferences database, if possible;
        if correct values were loaded, start using them, and do gl_update unless option for that is False;
        return True if you loaded new values, False if that failed
        """
        try:
            prefs = preferences.prefs_context()
            key = glpane_lights_prefs_key
            try:
                val = prefs[key]
            except KeyError:
                # none were saved; not an error and not even worthy of a message
                # since this is called on startup and it's common for nothing to be saved.
                # Return with no changes.
                return False
            # At this point, you have a saved prefs val, and if this is wrong it's an error.        
            # val format is described (partly implicitly) in saveLighting method.
            res = [] # will become new argument to pass to self.setLighting method, if we succeed
            for name in ['light0','light1','light2']:
                params = val[name] # a dict of ambient, diffuse, specular, x, y, z, enabled
                color = params['color'] # light color (r,g,b)
                a = params['ambient_intensity'] # ambient intensity
                d = params['diffuse_intensity'] # diffuse intensity
                s = params['specular_intensity'] # specular intensity
                x = params['xpos'] # X position
                y = params['ypos'] # Y position
                z = params['zpos'] # Z position
                e = params['enabled'] # boolean

                res.append( (color,a,d,s,x,y,z,e) )
            self.setLighting( res, gl_update = gl_update)
            if _DEBUG_LIGHTING:
                print "_DEBUG_LIGHTING: fyi: Lighting preferences loaded"
            return True
        except:
            print_compact_traceback("bug: exception in loadLighting (current prefs not altered): ")
            #e redmsg?
            return False
        pass

    def restoreDefaultLighting(self, gl_update = True):
        """
        restore the default (built-in) lighting preferences (but don't save them).
        """
        # Restore light color prefs keys.
        env.prefs.restore_defaults([
            light1Color_prefs_key, 
            light2Color_prefs_key, 
            light3Color_prefs_key,
        ])
        self.setLighting( self._default_lights,  gl_update = gl_update )
        return True

    pass

# end