summaryrefslogtreecommitdiff
path: root/cad/src/graphics/widgets/GLPane_rendering_methods.py
blob: 9cf2926d6c1a59745bc18cd77540fc69b77e5a81 (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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
# Copyright 2004-2009 Nanorex, Inc.  See LICENSE file for details.
"""
GLPane_rendering_methods.py

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

bruce 080913 split this out of class GLPane
"""

TEST_DRAWING = False # True  ## Debug/test switch.  Never check in a True value.
    # Also can be changed at runtime by external code.

if TEST_DRAWING:
    from prototype.test_drawing import test_drawing
        # review: is this needed here for its side effects?
        # guess yes, should document if so.
    pass

from OpenGL.GL import GL_DEPTH_BUFFER_BIT
from OpenGL.GL import GL_LEQUAL
from OpenGL.GL import GL_MODELVIEW
from OpenGL.GL import GL_MODELVIEW_STACK_DEPTH
from OpenGL.GL import GL_STENCIL_BUFFER_BIT
from OpenGL.GL import glDepthFunc
from OpenGL.GL import glFlush
from OpenGL.GL import glGetInteger
from OpenGL.GL import glMatrixMode
from OpenGL.GL import glPopMatrix

# kluge for debug bruce 081218 (temporary)
##from OpenGL.GL import glEnable
##from OpenGL.GL import glLogicOp
##from OpenGL.GL import glDisable
##from OpenGL.GL import GL_COLOR_LOGIC_OP
##from OpenGL.GL import GL_XOR

from PyQt4.QtOpenGL import QGLWidget


from utilities import debug_flags

from utilities.debug import print_compact_traceback, print_compact_stack

from utilities.Comparison import same_vals

from utilities.prefs_constants import displayCompass_prefs_key
from utilities.prefs_constants import displayOriginAxis_prefs_key
from utilities.prefs_constants import displayOriginAsSmallAxis_prefs_key
from utilities.prefs_constants import displayCompassLabels_prefs_key
from utilities.prefs_constants import displayRulers_prefs_key
from utilities.prefs_constants import showRulersInPerspectiveView_prefs_key
from utilities.prefs_constants import fogEnabled_prefs_key

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

from utilities.GlobalPreferences import use_frustum_culling
from utilities.GlobalPreferences import pref_skip_redraws_requested_only_by_Qt


import foundation.env as env


import graphics.drawing.drawing_globals as drawing_globals

from graphics.drawing.drawers import drawOriginAsSmallAxis
from graphics.drawing.drawers import drawaxes

from graphics.drawing.gl_lighting import disable_fog
from graphics.drawing.gl_lighting import enable_fog
from graphics.drawing.gl_lighting import setup_fog

from graphics.drawing.drawcompass import Compass
from graphics.drawing.Guides import Guides


from graphics.widgets.GLPane_image_methods import GLPane_image_methods


# ==

class GLPane_rendering_methods(GLPane_image_methods):
    """
    private mixin for providing rendering methods to class GLPane
    (including calls to highlighting/hit-test methods
     defined in mixin class GLPane_highlighting_methods,
     which must be a mixin of class GLPane along with this class)
    """
    def _init_GLPane_rendering_methods(self):
        """
        """
        # clipping planes, as percentage of distance from the eye
        self.near = 0.25 # After testing, this is much, much better.  Mark 060116. [Prior value was 0.66 -- bruce 060124 comment]
        self.far = 12.0  ##2.0, Huaicai: make this bigger, so models will be
                    ## more likely sitting within the view volume

        ##Huaicai 2/8/05: If this is true, redraw everything. It's better to split
        ##the paintGL() to several functions, so we may choose to draw 
        ##every thing, or only some thing that has been changed.
        self.redrawGL = True  

        # [bruce 050608]
        self.glselect_dict = {} # only used within individual runs [of what? paintGL I guess?]
            # see also self.object_for_glselect_name(), defined in GLPane_highlighting_methods

        self.makeCurrent() # REVIEW: safe now? needed for loadLighting? [bruce 080913 questions]

        # don't call GLPane_minimal._setup_display_lists yet -- this will be
        # called later by GLPane_minimal.initializeGL. [bruce 080913 change]

        # NOTE: before bruce 080913 split out this file, setAssy was done
        # at this point. Hopefully it's ok to do it just after we return,
        # instead. (Seems to work.)
        
        self.loadLighting() #bruce 050311 [defined in GLPane_lighting_methods]
            #bruce question 051212: why doesn't this prevent bug 1204
            # in use of lighting directions on startup?

        self.guides = Guides(self) # rulers, and soon, grid lines. Mark 2008-02-24.

        self.compass = Compass(self) #bruce 081015 refactored this
        
        return
    
    def model_is_valid(self): #bruce 080117
        """
        whether our model is currently valid for drawing
        [overrides GLPane_minimal method]
        """
        return self.assy.assy_valid

    def should_draw_valence_errors(self):
        """
        [overrides GLPane_minimal method]
        """
        return True

    def _paintGL(self):
        """
        [private; the body of paintGL in GLPane.py]

        Decide whether we need to call _paintGL_drawing,
        and if so, prepare for that (this might modify the model)
        and then call it.
        
        Also (first) call self._call_whatever_waits_for_gl_context_current()
        if that would be safe.

        @return: whether we modified GL buffer state (by clear or draw).
        @rtype: boolean
        """
        #bruce 081222 added return flag
        if TEST_DRAWING:  # See prototype/test_drawing.py
            from prototype.test_drawing import test_drawing, USE_GRAPHICSMODE_DRAW
                # intentionally redundant with toplevel import [bruce 080930]
            if USE_GRAPHICSMODE_DRAW:
                # Init and continue on, assuming that test_Draw_model will be called
                # separately (in an override of graphicsMode.Draw_model).
                
                # LIKELY BUG: USE_GRAPHICSMODE_DRAW is now a constant
                # but needs to depend on the testCase or be a separately
                # settable variable. This might be fixable inside test_drawing.py
                # since we reimport it each time here. [bruce 081211 comment]
                test_drawing(self, True)
            else:
                self.graphicsMode.gm_start_of_paintGL(self)
                test_drawing(self)
                self.graphicsMode.gm_end_of_paintGL(self)
                return True
        
        self._frustum_planes_available = False

        if not self.initialised:
            return False
        
        if not self.model_is_valid():
            #bruce 080117 bugfix in GLPane and potential bugfix in ThumbView;
            # for explanation see my same-dated comment in files_mmp
            # near another check of assy_valid.
            return False
        
        env.after_op() #bruce 050908; moved a bit lower, 080117
            # [disabled in changes.py, sometime before 060323;
            #  probably obs as of 060323; see this date below]

        # SOMEDAY: it might be good to set standard GL state, e.g. matrixmode,
        # before checking self.redrawGL here, in order to mitigate bugs in other
        # code (re bug 727), but only if the current mode gets to redefine what
        # "standard GL state" means, since some modes which use this flag to
        # avoid standard repaints also maintain some GL state in nonstandard
        # forms (e.g. for XOR-mode drawing). [bruce 050707 comment]

        if not self.redrawGL:
            return False
        
        self._call_whatever_waits_for_gl_context_current() #bruce 071103

        if not self._needs_repaint and \
           pref_skip_redraws_requested_only_by_Qt():
            # if we don't think this redraw is needed,
            # skip it (but print '#' if atom_debug is set -- disabled as of 080512).

            #bruce 070109 restored/empowered the following code, but
            # only within this new debug pref [persistent as of 070110].
            #
            # ITS USE IS PREDICTED TO CAUSE SOME BUGS: one in changed bond
            # redrawing [described below, "bruce 050717 bugfix"]
            # (though the fact that _needs_repaint is not reset until below
            #  makes me think it either won't happen now,
            #  or is explained incorrectly in that comment),
            # and maybe some in look of glpane after resizing, toolbar changes,
            # or popups/dialogs going up or down, any of which might be
            # platform-dependent. The debug_pref's purpose is experimentation --
            # if we could figure out which repaints are really needed, we could
            # probably optimize away quite a few unneeded ones.
            #
            # Update, bruce 070414: so far I only found one bug this debug_pref
            # causes: MT clicks which change chunk selection don't cause redraws,
            # but need to (to show their selection wireframes). That could be
            # easily fixed. [Bug no longer exists as of 080512; I don't recall
            # why. But I have had this always on for a long time and don't
            # recall noticing any bugs. So I'm turning it on by default, and
            # disabling the printing of '#'; if we need it back for debugging
            # we can add a debug_pref for it and/or for drawing redraw_counter
            # as text in the frame. bruce 080512]
            #
            # older comments:
            #
            #bruce 050516 experiment
            #
            # This probably happens fairly often when Qt calls paintGL but
            # our own code didn't change anything and therefore didn't call
            # gl_update.
            #
            # This is known to happen when a context menu is put up,
            # the main app window goes into bg or fg, etc.
            #
            # SOMEDAY:
            # An alternative to skipping the redraw would be to optimize it
            # by redrawing a saved image. We're likely to do that for other
            # reasons as well (e.g. to optimize redraws in which only the
            # selection or highlighting changes).
            # [doing this experimentally, 080919; see class GLPane_image_methods]

            # disabling this debug print (see long comment above), bruce 080512
            ## if debug_flags.atom_debug:
            ##     sys.stdout.write("#") # indicate a repaint is being skipped
            ##     sys.stdout.flush()

            return False # skip the following repaint

        # at this point, we've decided to call _paintGL_drawing.
        
        env.redraw_counter += 1 #bruce 050825

        #bruce 050707 (for bond inference -- easiest place we can be sure to update bonds whenever needed)
        #bruce 050717 bugfix: always do this, not only when "self._needs_repaint"; otherwise,
        # after an atomtype change using Build's cmenu, the first redraw (caused by the cmenu going away, I guess)
        # doesn't do this, and then the bad bond (which this routine should have corrected, seeing the atomtype change)
        # gets into the display list, and then even though the bondtype change (set_v6) does invalidate the display list,
        # nothing triggers another gl_update, so the fixed bond is not drawn right away. I suppose set_v6 ought to do its own
        # gl_update, but for some reason I'm uncomfortable with that for now (and even if it did, this bugfix here is
        # probably also needed). And many analogous LL changers don't do that.

        env.do_post_event_updates( warn_if_needed = False)
            # WARNING: this calls command-specific ui updating methods
            # like model_changed, even when it doesn't need to (still true
            # 080804). They all need to be revised to be fast when no changes
            # are needed, or this will make redraw needlessly slow.
            # [bruce 071115/080804 comment]
            # TODO: doc what else it does - break interpart bonds? dna updater? undo checkpoint?

        # Note: at one point we surrounded this repaint with begin/end undo
        # checkpoints, to fix bugs from missing mouseReleases (like bug 1411)
        # (provided they do a gl_update like that one does), from model changes
        # during env.do_post_event_updates(), or from unexpected model changes
        # during the following repaint. But this was slow, and caused bug 1759,
        # and a better fix for 1411 was added (in the menu_spec processor in
        # widgets.py). So the checkpoints were zapped [by bruce 060326].
        # There might be reasons to revive that someday, and ways to avoid
        # its slowness and bugs, but it's not needed for now.
        
        try:
            self._paintGL_drawing()
        except:
            print_compact_traceback("exception in _paintGL_drawing ignored: ")

        return True # from paintGL

    def _paintGL_drawing(self):
        """
        [private submethod of _paintGL]
        
        Do whatever OpenGL drawing paintGL should do (then glFlush).

        @note: caller must handle TEST_DRAWING, redrawGL, _needs_repaint.
        """
        #bruce 080919 renamed this from most_of_paintGL to _paintGL_drawing

##        if 'kluge for debug bruce 081218':
##            glEnable(GL_COLOR_LOGIC_OP)
##            glLogicOp(GL_XOR)
##            glDisable(GL_COLOR_LOGIC_OP)

        self._needs_repaint = False
            # do this now, even if we have an exception during the repaint

        self.graphicsMode.gm_start_of_paintGL(self)

        #k not sure whether _restore_modelview_stack_depth is also needed
        # in the split-out standard_repaint [bruce 050617]
        self._restore_modelview_stack_depth()

        self._use_frustum_culling = use_frustum_culling()
            # there is some overhead calling the debug_pref,
            # and we want the same answer used throughout
            # one call of paintGL. Note that this is checked both
            # in this file and in GLPane_highlighting_methods.py.
        assert not self._frustum_planes_available

        glDepthFunc( GL_LEQUAL) #bruce 070921; GL_LESS causes bugs
            # (e.g. in exprs/Overlay.py)
            # TODO: put this into some sort of init function in GLPane_minimal;
            # not urgent, since all instances of GLPane_minimal share one GL
            # context for now, and also they all contain this in paintGL.

        self.setDepthRange_setup_from_debug_pref()
        self.setDepthRange_Normal()

        method = self.graphicsMode.render_scene # revised, bruce 070406/071011
        if method is None:
            self.render_scene() # usual case
                # [TODO: move that code into basicGraphicsMode and let it get
                #  called in the same way as the following]
        else:
            method( self) # let the graphicsMode override it

        glFlush()

        self.graphicsMode.gm_end_of_paintGL(self)

        ##self.swapBuffers()  ##This is a redundant call, Huaicai 2/8/05

        return # from _paintGL_drawing

    def _restore_modelview_stack_depth(self):
        """
        restore GL_MODELVIEW_STACK_DEPTH to 1, if necessary
        """
        #bruce 040923 [updated 080910]:
        # I'd like to reset the OpenGL state
        # completely, here, including the stack depths, to mitigate some
        # bugs. How??  Note that there might be some OpenGL init code
        # earlier which I'll have to not mess up, including _setup_display_lists.
        #   What I ended up doing is just to measure the
        # stack depth and pop it 0 or more times to make the depth 1.
        #   BTW I don't know for sure whether this causes a significant speed
        # hit for some OpenGL implementations (esp. X windows)...
        # TODO: test the performance effect sometime.
        glMatrixMode(GL_MODELVIEW)

        depth = glGetInteger(GL_MODELVIEW_STACK_DEPTH)
        # this is normally 1
        # (by experiment, qt-mac-free-3.3.3, Mac OS X 10.2.8...)
        if depth > 1:
            print "apparent bug: glGetInteger(GL_MODELVIEW_STACK_DEPTH) = %r in GLPane.paintGL" % depth
            print "workaround: pop it back to depth 1"
            while depth > 1:
                depth -= 1
                glPopMatrix()
            newdepth = glGetInteger(GL_MODELVIEW_STACK_DEPTH)
            if newdepth != 1:
                print "hmm, after depth-1 pops we should have reached depth 1, but instead reached depth %r" % newdepth
            pass
        return

    def render_scene(self):#bruce 061208 split this out so some modes can override it (also removed obsolete trans_feature experiment)

        #k not sure whether next things are also needed in the split-out standard_repaint [bruce 050617]

        self.glprefs.update()
            #bruce 051126; kluge: have to do this before lighting *and* inside standard_repaint_0
            # [addendum, bruce 090304: I assume that's because we need it for lighting, but also
            #  need its prefs accesses to be usage-tracked, so changes in them do gl_update;
            #  maybe a better solution would be to start usage-tracking sooner?]

        self.setup_lighting_if_needed() # defined in GLPane_lighting_methods

        self.standard_repaint()

        return # from render_scene

    __subusage = None #bruce 070110

    def standard_repaint(self):
        """
        call standard_repaint_0 inside "usage tracking". This is so subsequent
        changes to tracked variables (such as env.prefs values) automatically
        cause self.gl_update to be called.
        
        @warning: this trashes both gl matrices! caller must push them both
                  if it needs the current ones. this routine sets its own
                  matrixmode, but depends on other gl state being standard
                  when entered.
        """
        # zap any leftover usage tracking from last time
        #
        # [bruce 070110 new feature, for debugging and possibly as a bugfix;
        #  #e it might become an option of begin_tracking_usage, but an "aspect" would need to be passed
        #  to permit more than one tracked aspect to be used -- it would determine the attr
        #  corresponding to __subusage in this code. Maybe the same aspect could be passed to
        #  methods of SelfUsageTrackingMixin, but I'm not sure whether the two tracking mixins
        #  would or should interact -- maybe one would define an invalidator for the other to use?]
        #
        if self.__subusage is None: 
            # usual the first time
            pass
        elif self.__subusage == 0:
            # should never happen
            print_compact_stack( "bug: apparent recursive usage tracking in GLPane: ")
            pass
                # it'd be better if we'd make invals illegal in this case, but in current code
                # we don't know the obj to tell to do that (easy to fix if needed)
        elif self.__subusage == -1:
            print "(possible bug: looks like the last begin_tracking_usage raised an exception)"
            pass
        else:
            # usual case except for the first time
            self.__subusage.make_invals_illegal(self)
        self.__subusage = -1

        match_checking_code = self.begin_tracking_usage() #bruce 050806
        self.__subusage = 0

        debug_prints_prefs_key = "A9 devel/debug prints for my bug?" # also defined in exprs/test.py
        if env.prefs.get(debug_prints_prefs_key, False):
            print "glpane begin_tracking_usage" #bruce 070110
        try:
            try:
                self.standard_repaint_0()
            except:
                print "exception in standard_repaint_0 (being reraised)"
                    # we're not restoring stack depths here, so this will mess up callers, so we'll reraise;
                    # so the caller will print a traceback, thus we don't need to print one here. [bruce 050806]
                raise
        finally:
            self.wants_gl_update = True #bruce 050804
            self.__subusage = self.end_tracking_usage( match_checking_code, self.wants_gl_update_was_True )
                # same invalidator even if exception
            if env.prefs.get(debug_prints_prefs_key, False):
                print "glpane end_tracking_usage" #bruce 070110
        return
    
    drawing_phase = '?' # set to different fixed strings for different drawing phases
        # [new feature, bruce 070124] [also defined in GLPane_minimal]
        #
        # WARNING: some of the strings are hardcoded in condition checks in
        # various places in the code. If spelling is changed, or new strings are
        # added, or the precise drawing covered by each string is modified,
        # all existing uses need to be examined for possibly needing changes.
        # (Ideally, this would be cleaned up by defining in one place the
        #  possible values, as symbolic constants, and the functions for testing
        #  aspect of interest from them, e.g. whether to draw shader primitives,
        #  whether to draw OpenGL display lists, which DrawingSet cache to use.)
        # [bruce 090227 comment]
    
    drawing_globals.drawing_phase = drawing_phase # KLUGE (as is everything about drawing_globals)
    
    def set_drawing_phase(self, drawing_phase): 
        """
        Set self.drawing_phase to the specified value, and do any updates
        this requires (presently, copy it into drawing_globals.drawing_phase).

        Various internal code looks at self.drawing_phase (and/or
        drawing_globals.drawing_phase) so it can behave differently during
        different drawing phases. See code and code comments for details, and
        a list of permitted values of drawing_phase and what they mean.

        @note: setting self.drawing_phase directly (not using this method)
            would cause bugs.
        """
        # Russ 081208: Encapsulate setting, to tell shaders as well.
        self.drawing_phase = drawing_phase
        drawing_globals.drawing_phase = drawing_phase
        # Note, there are direct calls of GL_SELECT drawing not from class
        # GLPane, which ought to set this but don't. (They have a lot of other
        # things wrong with them too, especially duplicated code). The biggest
        # example is for picking jigs. During those calls, this attr will
        # probably equal '?' -- all the draw calls here reset it to that right
        # after they're done. (## todo: We ought to set it to that at the end of
        # paintGL as well, for safety.)
        #
        # Explanation of possible values: [### means explan needs to be filled in]
        # - '?' -- drawing at an unexpected time, or outside of paintGL
        # - 'glselect' -- only used if mode requested object picking
        #              -- Only draws Display Lists since the glSelect stack is not available to shaders.
        #              -- glRenderMode(GL_SELECT) in effect; reduced projection matrix
        # - 'glselect_glname_color' -- only used if mode requested object picking
        #              -- Only draws shader primitives, and reads the glnames back as pixel colors.
        #              -- glRenderMode(GL_RENDER) in effect; reduced projection matrix and viewport (one pixel.)
        # - 'main' -- normal drawing, main coordinate system for model (includes trackball/zoom effect)
        # - 'main/Draw_after_highlighting' -- normal drawing, but after selobj is drawn ### which coord system?
        # - 'main/draw_glpane_label' -- ###
        # - 'overlay' -- ###
        # - 'selobj' -- we're calling selobj.draw_in_abs_coords (not drawing the entire model), within same coordsys as 'main'
        # - 'selobj/preDraw_glselect_dict' -- like selobj, but color buffer drawing is off ### which coord system, incl projection??
        # [end]
        return

    _drawingset_cachename_from_drawing_phase = {
        '?': 'main', # theory: used by event methods that draw entire model for
            # selection, e.g. jigGLselect (sp?); should clean them up #### review
        'glselect': 'main',
        'glselect_glname_color': 'main',
        'main': 'main',
        'main/Draw_after_highlighting': 'main/Draw_after_highlighting',
        'main/draw_glpane_label': 'temp',
        'overlay': 'temp',
        'selobj': 'selobj',
        'selobj/preDraw_glselect_dict': 'temp',
     }

    _drawingset_temporary_cachenames = {
        'temp': True
     }
    def _choose_drawingset_cache_policy(self): #bruce 090227
        """
        Based on self.drawing_phase, decide which cache to keep DrawingSets in
        and whether it should be temporary.

        @return: (temporary, cachename)
        @rtype: (bool, string)

        [overrides a method defined in a mixin of a superclass
         of the class we mix into]
        """
        # Note: mistakes in what we return will reduce graphics performance,
        # and could increase VRAM usage, but have no effect on what is drawn.

        # About the issue of using too much ram: all whole-model caches should
        # be the same; small ones don't matter too much, but might be best
        # temporary in case they are not always small;
        # at least use the same cache in all temp cases.

        # todo: Further optimization is possible but not yet done:
        # for drawing main model for selobj tests, just reuse
        # the cached drawingsets without even scanning the model
        # to see if they need to be modified. This is worth
        # doing if we have time, but less urgent than several
        # other things we need to do.
        
        drawing_phase = self.drawing_phase
        if drawing_phase == '?':
            print_compact_stack("warning: _choose_drawingset_cache_policy during '?' -- should clean up: ") ######
        cachename = self._drawingset_cachename_from_drawing_phase.get(drawing_phase)
        if cachename is None:
            cachename = 'temp'
            print "bug: unknown drawing_phase %r, using cachename %r" % \
                  (drawing_phase, cachename)
            assert self._drawingset_temporary_cachenames.get(cachename, False) == True
            pass
        temporary = self._drawingset_temporary_cachenames.get(cachename, False)
        return (temporary, cachename)

    # ==

    def setup_shaders_each_frame(self): #bruce 090304
        """
        This must be called once near the start of each call of paintGL,
        sometime after self.glprefs.update has been first called during that
        call of paintGL. It makes sure shaders exist and are loaded, but does
        not configure them (since that requires some GL state which is
        not yet set when this is called, and may need to be done more
        than once per frame).
        """
        if self.permit_shaders:
            drawing_globals.setup_desired_shaders( self.glprefs)
            # review: is it good that we condition this on self.permit_shaders?
            # it doesn't matter for now, since ThumbView never calls it.
        return
    
    def enabled_shaders(self): #bruce 090303
        return drawing_globals.enabled_shaders(self)
            #### todo: refactor: self.drawing_globals (but renamed)

    def configure_enabled_shaders(self): #bruce 090303
        """
        This must be called before any drawing that might use shaders,
        but after all preferences are cached (by glprefs.update or any other
        means), and after certain other GL drawing state is set up (in case
        configShader reads that state). The state it reads from glpane
        (including from glpane.glprefs) are related to debug_code, material,
        perspective, clip/DEPTH_TWEAK, and lights (see configShader
        for details).

        It is ok to call it multiple times per paintGL call. If in doubt
        about where to correctly call it, just call it in both places.
        However, it is believed that in current code, it only uses state
        set by user pref changes, which means a single call per paintGL
        call should be sufficient if it's in the right place. (It never
        uses the GL matrices or glpane.drawing_phase. But see setPicking.)

        @note: setup_shaders_each_frame must have been called
            before this, during the same paintGL call.
        """
        for shader in self.enabled_shaders():
            shader.configShader(self)
        return

    # ==

    _cached_bg_image_comparison_data = None
        # note: for the image itself, see attrs of class GLPane_image_methods

    _last_general_appearance_prefs_summary = None #bruce 090306
    _general_appearance_change_indicator = 0 # also defined in GLPane_minimal
    
    def standard_repaint_0(self):
        """
        [private indirect submethod of paintGL]
        
        This is the main rendering routine -- it clears the OpenGL window,
        does all drawing done during paintGL, and does hit-testing if
        requested by event handlers before this call of paintGL.

        @note: this is called inside a begin_tracking_usage/end_tracking_usage
            pair, invalidation of which results (indirectly) in self.gl_update().

        @note: self.graphicsMode can control whether this gets called;
               for details see the call of self.render_scene in this class.
        """
        if self.width != QGLWidget.width(self) or \
           self.height != QGLWidget.height(self): #bruce 080922; never yet seen
            print "\n*** debug fyi: inconsistent: self width/height %r, %r vs QGLWidget %r, %r" % \
                  (self.width, self.height, QGLWidget.width, QGLWidget.height)
            pass
        
        self.glprefs.update()
            # (kluge: have to do this before lighting *and* inside standard_repaint_0)
            # (this is also required to come BEFORE setup_shaders_each_frame)

        # optimization: compute a change indicator for Chunk & ExternalBondSet
        # display lists here, not every time we draw one of them!
        # (kluge: assume no need for same_vals, i.e. no Numeric arrays
        #  in this value)
        # [bruce 090306]
        current_summary = self.part.general_appearance_prefs_summary(self)
        if self._last_general_appearance_prefs_summary != current_summary:
            self._general_appearance_change_indicator += 1 # invalidates display lists
            self._last_general_appearance_prefs_summary = current_summary
            pass

        self.clear_and_draw_background( GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
            # also sets self.fogColor

        # fog added by bradg 20060224
        # piotr 080605 1.1.0 rc1 - replaced fog debug pref with user pref
        self._fog_test_enable = env.prefs[fogEnabled_prefs_key]

        if self._fog_test_enable:
            # piotr 080515 fixed fog
            # I think that the bbox_for_viewing_model call can be expensive.
            # I have to preserve this value or find another way of computing it.
            bbox = self.assy.bbox_for_viewing_model()
            scale = bbox.scale()
            enable_fog() #k needed?? [bruce 080915 question]
            setup_fog(self.vdist - scale, self.vdist + scale, self.fogColor)
            # [I suspect the following comment is about a line that has since
            #  been moved elsewhere -- bruce 080911]
            # this next line really should be just before rendering
            # the atomic model itself.  I dunno where that is. [bradg I think]

        # ask mode to validate self.selobj (might change it to None)
        # (note: self.selobj is used in do_glselect_if_wanted)
        self._selobj_and_hicolor = self.validate_selobj_and_hicolor()

        # do modelview setup (needed for GL_SELECT or regular drawing)
        self._setup_modelview()
            #bruce 050608 moved modelview setup here, from just before the mode.Draw call

        # set self.stereo_* attributes based on current user prefs values
        # (just once per draw event, before anything might use these attributes)
        self._update_stereo_settings()

        # do hit-testing, if requested by some event handler before this
        # call of paintGL (mostly done in do_glselect_if_wanted)

        ###e note: if any objects moved since they were last rendered,
        # this hit-test will still work (using their new posns),
        # but the later depth comparison (below, inside preDraw_glselect_dict)
        # might not work right. See comments there for details.

        self.glselect_dict.clear()
            # this will be filled iff we do a gl_select draw,
            # then used only in the same paintGL call to alert some objects
            # they might be the one under the mouse

        self.setup_shaders_each_frame()
        
        self.configure_enabled_shaders()
            # I don't know if this is needed this early (i.e. before
            # do_glselect_if_wanted), but it shouldn't hurt (though it
            # can't come much earlier than this, and must come after
            # setup_shaders_each_frame which must come after glprefs.update).
            # Supposedly we only need one call of this per paintGL call
            # (see its docstring for details), so we'll see if only this one
            # is sufficient. [bruce 090304]

        try:
            self.graphicsMode.Draw_preparation()
        except:
            msg = "bug: exception in %r.Draw_preparation ignored" % self.graphicsMode
            print_compact_traceback(msg + ": ")
            # Q: then skip the rest of this frame?
            # A: No, typically that worsens bugs -- it's better to see
            #    something than nothing.
            pass
        
        self.do_glselect_if_wanted() # review: rename: _do_mouseover_picking?
            # note: this might call _do_graphicsMode_Draw up to 2 times
            # (as of before 090311).
            # note: if self.glselect_wanted, this sets up a special projection
            # matrix, and leaves it in place (effectively trashing the
            # projection matrix of the caller)

        self._setup_projection() # setup the usual projection matrix

        # Compute frustum planes required for frustum culling - piotr 080331
        # Moved it right after _setup_projection is called (piotr 080331)
        # Note that this method is also called by "do_glselect_if_wanted",
        # but computes different planes. The second call (here) will 
        # re-compute the frustum planes according to the current projection 
        # matrix.
        if self._use_frustum_culling:
            self._compute_frustum_planes()

        # In the glselect_wanted case, we now know (in glselect_dict)
        # which objects draw any pixels at the mouse position,
        # but not which one is in front. (The near/far info from
        # GL_SELECT has too wide a range to tell us that.)
        # (In the shader primitive case we might know a specific object,
        #  but not always, as long as some objects are not drawn
        #  using shaders.)
        # So we have to get them to tell us their depth at that point
        # (as it was last actually drawn)
            ###@@@ should do that for bugfix; also selobj first
        # (and how it compares to the prior measured depth-buffer value there,
        #  as passed in glselect_wanted, if we want to avoid selecting
        #  something when it's obscured by non-selectable drawing in
        #  front of it).
        if self.glselect_dict:
            # kluge: this is always the case if self.glselect_wanted was set
            # and self.selobj was set, since selobj is always stored in
            # glselect_dict then; if not for that, we might need to reset
            # selobj to None here for empty glselect_dict -- not sure, not
            # fully analyzed. [bruce 050612]

##            self.configure_enabled_shaders() #### new call 090304, maybe not needed
            
            newpicked = self.preDraw_glselect_dict() # retval is new mouseover object, or None
            # now record which object is hit by the mouse in self.selobj
            # (or None if none is hit); and (later) overdraw it for highlighting.
            if newpicked is not self.selobj:
                self.set_selobj( newpicked, "newpicked")
                self._selobj_and_hicolor = self.validate_selobj_and_hicolor()
                    # REVIEW: should set_selobj also call that, and save hicolor
                    # in an attr of self, so self._selobj_and_hicolor is not needed?
                # future: we'll probably need to notify some observers that
                # selobj changed (if in fact it did).
                # REVIEW: we used to print this in the statusbar:
                ## env.history.statusbar_msg("%s" % newpicked)
                # but it was messed up by Build Atoms "click to do x" msg.
                # that message is nim now, so we could restore this if desired.
                # should we? [bruce 080915 comment]

        # otherwise don't change prior selobj -- we have a separate system
        # to set it back to None when needed (which has to be implemented
        # in the bareMotion methods of instances stored in self.graphicsMode --
        # would self.bareMotion (which doesn't exist now) be better? (REVIEW)
        # [later: see also UNKNOWN_SELOBJ, I think]
        
        ### REVIEW: I suspect the above comment, and not resetting selobj here,
        # is wrong, at least when selobj has no depth at all at the current 
        # location; and that this error causes a "selobj stickiness" bug
        # when moving the mouse directly from selobj onto non-glname objects
        # (or buggy-glname objects, presently perhaps including shader spheres 
        # in Build Atoms when the debug_pref 'Use batched primitive shaders?' 
        # is set). [bruce 090105 comment]

        # draw according to self.graphicsMode

        glMatrixMode(GL_MODELVIEW) # this is assumed within Draw methods

        # these are modified below as needed:
        draw_saved_bg_image = False # whether to draw previously cached image, this frame
        capture_saved_bg_image = False # whether to capture a new image from what we draw this frame
        bg_image_comparison_data = None # if this changes, discard any previously cached image
        
        if debug_pref("GLPane: use cached bg image? (experimental)",
                      Choice_boolean_False,
                      ## non_debug = True, [bruce 090311 removed non_debug]
                      prefs_key = True):
            # experimental implementation, has bugs (listed here or in
            # submethods when known, mostly in GLPane_image_methods)
            
            ### REVIEW: this doesn't yet work, and it's never been reviewed
            # for compatibility with cached DrawingSet optimizations
            # (which may mostly supercede the need for it).
            # [bruce 090311 comment]
            if self._resize_just_occurred:
                self._cached_bg_image_comparison_data = None
                # discard cached image, and do *neither* capture nor draw of
                # cached image on this frame (the first one drawn after resize).
                # This seems to prevent crash due to resize (in GEForceFX OpenGL
                # driver, in a "processing colors" routine),
                # at least when we meet all of these conditions: [bruce 080922]
                # - test on iMac G5, Mac OS 10.3.9
                # - do the print below
                # - comment out self.do_glselect_if_wanted() above (highlighting)
                # - comment out drawing the depth part of the cached image
                ## print "debug fyi: skipping bg image ops due to resize"
                # ... Actually, crash can still happen if we slightly expand width
                # and then trigger redraw by mouseover compass.
            else:
                bg_image_comparison_data = self._get_bg_image_comparison_data()
                cached_image_is_valid = same_vals( bg_image_comparison_data,
                                                   self._cached_bg_image_comparison_data)
                if cached_image_is_valid:
                    draw_saved_bg_image = True
                else:
                    capture_saved_bg_image = True
                    if bg_image_comparison_data == self._cached_bg_image_comparison_data: 
                        print "DEBUG FYI: equal values not same_vals:\n%r, \n%r" % \
                          ( bg_image_comparison_data, self._cached_bg_image_comparison_data ) ###
                pass
            pass
        else:
            self._cached_bg_image_comparison_data = None
            
        if draw_saved_bg_image:
            self._draw_saved_bg_image() # in GLPane_image_methods
                # saved and drawn outside of stereo loop (intentional)
                # (instead of ordinary drawing inside it, separate code below)
        else:
            # capture it below, and only after that, do this assignment:
            # self._cached_bg_image_comparison_data = bg_image_comparison_data
            pass

        for stereo_image in self.stereo_images_to_draw:
            self._enable_stereo(stereo_image)
                # note: this relies on modelview matrix already being correctly
                # set up for non-stereo drawing

            if not draw_saved_bg_image:
                self._do_drawing_for_bg_image_inside_stereo()
                # note: this calls _do_graphicsMode_Draw once.
            # otherwise, no need, we called _draw_saved_bg_image above
                
            if not capture_saved_bg_image:
                self._do_other_drawing_inside_stereo()
                # otherwise, do this later (don't mess up captured image)
            
            self._disable_stereo()
            continue # to next stereo_image

        if capture_saved_bg_image:
            self._capture_saved_bg_image() # in GLPane_image_methods
            self._cached_bg_image_comparison_data = bg_image_comparison_data
            for stereo_image in self.stereo_images_to_draw:
                self._enable_stereo(stereo_image)
                self._do_other_drawing_inside_stereo()
                self._disable_stereo()
                continue # to next stereo_image
            pass

        # let parts (other than the main part) draw a text label, to warn
        # the user that the main part is not being shown [bruce 050408]
        # [but let the GM control this: moved and renamed
        #  part.draw_text_label -> GM.draw_glpane_label; bruce 090219]
        try:
            self.set_drawing_phase('main/draw_glpane_label') #bruce 070124, renamed 090219
            self.graphicsMode.draw_glpane_label(self)
        except:
            # if this happens at all, it'll happen too often to bother non-debug
            # users with a traceback (but always print an error message)
            if debug_flags.atom_debug:
                print_compact_traceback( "atom_debug: exception in self.graphicsMode.draw_glpane_label(self): " )
            else:
                print "bug: exception in self.graphicsMode.draw_glpane_label; use ATOM_DEBUG to see details"
        self.set_drawing_phase('?')

        # draw the compass (coordinate-orientation arrows) in chosen corner
        if env.prefs[displayCompass_prefs_key]:
            self.drawcompass()
            # review: needs drawing_phase? [bruce 070124 q]

        # draw the "origin axes"
        ### TODO: put this, and the GM part of it (now in basicGraphicsMode.Draw_axes),
        # into one of the methods
        # _do_other_drawing_inside_stereo or _do_drawing_for_bg_image_inside_stereo
        if env.prefs[displayOriginAxis_prefs_key]:
            for stereo_image in self.stereo_images_to_draw:
                self._enable_stereo(stereo_image, preserve_colors = True)

                # REVIEW: can we simplify and/or optim by moving this into
                # the same stereo_image loop used earlier for _do_graphicsMode_Draw?
                # [bruce 080911 question]
                
                # WARNING: this code is duplicated, or almost duplicated,
                # in GraphicsMode.py and GLPane.py.
                # It should be moved into a common method in drawers.py.
                # [bruce 080710 comment]

                #ninad060921 The following draws a dotted origin axis
                # if the correct preference is checked. The GL_DEPTH_TEST is
                # disabled while drawing this, so that if axis is behind a
                # model object, it will just draw it as a dotted line (since
                # this drawing will occur, but the solid origin axes drawn
                # in other code, overlapping these, will be obscured).
                #bruce 080915 REVIEW: can we clarify that by doing the solid
                # axis drawing here as well?
                if env.prefs[displayOriginAsSmallAxis_prefs_key]:
                    drawOriginAsSmallAxis(self.scale, (0.0, 0.0, 0.0), dashEnabled = True)
                else:
                    drawaxes(self.scale, (0.0, 0.0, 0.0), coloraxes = True, dashEnabled = True)

                self._disable_stereo()

        self._draw_cc_test_images()
            # draw some test images related to the confirmation corner
            # (needs to be done before draw_overlay)
        
        # draw various overlays

        self.set_drawing_phase('overlay')

        # Draw ruler(s) if "View > Rulers" is checked
        # (presently in main menus, not in prefs dialog)
        if env.prefs[displayRulers_prefs_key]:
            if (self.ortho or env.prefs[showRulersInPerspectiveView_prefs_key]):
                self.guides.draw()

        # draw the confirmation corner
        try:
            glMatrixMode(GL_MODELVIEW) #k needed?
            self.graphicsMode.draw_overlay() #bruce 070405 (misnamed)
        except:
            print_compact_traceback( "exception in self.graphicsMode.draw_overlay(): " )
        
        self.set_drawing_phase('?')

        # restore standard glMatrixMode, in case drawing code outside of paintGL
        # forgets to do this [precaution]
        glMatrixMode(GL_MODELVIEW)
            # (see discussion in bug 727, which was caused by that)
            # (todo: it might also be good to set mode-specific
            #  standard GL state before checking self.redrawGL in paintGL)

        return # from standard_repaint_0 (the main rendering submethod of paintGL)

    def _do_drawing_for_bg_image_inside_stereo(self):
        """
        """
        #bruce 080919 split this out
        if self._fog_test_enable:
            enable_fog()

        self.set_drawing_phase('main')
        
        try:
            self._do_graphicsMode_Draw()
        finally:
            self.set_drawing_phase('?')

            if self._fog_test_enable: #bruce 090219 moved inside 'finally'
                disable_fog()

        return

    def _do_graphicsMode_Draw(self, for_mouseover_highlighting = False):
        """
        Private helper for various places in which we need to draw the model
        (in this GLPane mixin and others).

        This draws self.part (the model)
        (or some portion of it or none of it, depending on self.graphicsMode),
        with chunk & atom selection indicators, and graphicsMode-specific extras
        such as handles/rubberbands/labels (Draw_other).

        It draws (some of) the axes only when for_mouseover_highlighting is false.

        @note: this is used for both normal visible rendering, and finding
            candidate objects to be "selobj" for mouseover-highlighting.
            In the latter case, for_mouseover_highlighting = True tells us
            to skip some drawing.
        """
        #bruce 090219, revised 090311
        def prefunc():
            if not for_mouseover_highlighting:
                self.graphicsMode.Draw_axes() # review: best place to do this?
            self.graphicsMode.Draw_other_before_model()
        
        def func():
            # not always called (see _call_func_that_draws_model for details)
            self.graphicsMode.Draw_model()
        
        def postfunc():
            self.graphicsMode.Draw_other()
        
        self._call_func_that_draws_model( func,
                                          prefunc = prefunc,
                                          postfunc = postfunc )
        return

    def _whole_model_drawingset_change_indicator(self):
        """
        #doc
        """
        ## BUGS:
        #
        # The following implementation is not correct:
        #
        # - view changes are not taken into account, but need to affect
        #   drawingsets due to frustum culling
        #
        # - doesn't include effect of visual preferences
        #   (or PM settings implemented via env.prefs)
        #
        # (workaround for both: select something to update the display)
        #
        # Also, current code that uses this has bugs:
        #
        # - ignores necessary drawing not inside DrawingSets
        #   - jigs, dna/protein styles, overlap indicators, atom sel wireframes, bond vanes...
        #   - extra drawing by graphicsMode, e.g. expr handles, dna ribbons
        #   - probably more
        #
        # So it is only used when a debug_pref is set.
        # Another comment or docstring has more info on purpose and status.
        #
        # [bruce 090309]

        res = self.part and \
               (self.part,
                self.assy.model_change_indicator(),
                self.assy.selection_change_indicator(),
                self.displayMode, #bruce 090312 bugfix
               )
        return res

    def _do_other_drawing_inside_stereo(self): #bruce 080919 split this out
        """
        [might be misnamed -- does not (yet) do *all* other drawing
         currently done inside stereo]
        """
        # highlight selobj if necessary, by drawing it again in highlighted
        # form (never inside fog).
        # It was already drawn normally, but we redraw it now for three reasons:
        # - it might be in a display list in non-highlighted form (and if so,
        #   the above draw used that form);
        # - if fog is enabled, the above draw was inside fog; this one won't be;
        # - we need to draw it into the stencil buffer too, so subsequent calls
        #   of self.graphicsMode.bareMotion event handlers can find out whether
        #   the mouse is still over it, and avoid asking for hit-test again
        #   if it was (probably an important optimization).
        selobj, hicolor = self._selobj_and_hicolor
        if selobj is not None:
            self._draw_highlighted_selobj(selobj, hicolor)
                # REVIEW: is it ok that the mode had to tell us selobj and hicolor
                # (and validate selobj) before drawing the model?

        # draw transparent things (e.g. Build Atoms water surface,
        # parts of Plane or ESPImage nodes)
        # [bruce 080919 bugfix: do this inside the stereo loop]

        self.call_Draw_after_highlighting(self.graphicsMode)
        return

    def call_Draw_after_highlighting(self, graphicsMode, pickCheckOnly = False):
        """
        Call graphicsMode.Draw_after_highlighting() in the correct way
        (with appropriate drawing_phase), with exception protection,
        and return whatever it returns. Pass pickCheckOnly.

        @note: calls of this should be inside a stereo loop, to be correct.

        @note: event processing or drawing code should use this method to call
            graphicsMode.Draw_after_highlighting(), rather than
            calling it directly. But implementations of Draw_after_highlighting
            itself should call their superclass versions directly.
        """
        # note: some existing calls of this are buggy since not in a stereo
        # loop. They are bad in other ways too. See comment there for more info.
        
        ### REVIEW: any need for _call_func_that_draws_model? I guess not now,
        # but revise if we ever want to use csdls with objects drawn by this,
        # in any GraphicsMode. [bruce 090219 comment]
        self.set_drawing_phase('main/Draw_after_highlighting')
        try:
            res = self.graphicsMode.Draw_after_highlighting( pickCheckOnly)
                # e.g. draws water surface in Build mode [###REVIEW: ok inside stereo loop?],
                # or transparent parts of ESPImage or Plane (must be inside stereo loop).
                # Note: this is called in the main model coordinate system
                # (perhaps modified for current stereo image),
                # just like self.graphicsMode.Draw_model, etc
                # [bruce 061208/080919 comment]
        except:
            res = None
            msg = "bug in %r.Draw_after_highlighting ignored" % (graphicsMode,)
            print_compact_traceback(msg + ": ")
            pass
        self.set_drawing_phase('?')
        return res

    def validate_selobj_and_hicolor(self): #bruce 070919 split this out, slightly revised behavior, and simplified code
        """
        Return the selobj to use, and its highlight color (according to self.graphicsMode),
        after validating the graphicsmode says it's still ok and has a non-None hicolor.
        Return a tuple (selobj, hicolor) (with selobj and hicolor not None) or (None, None).
        """
        selobj = self.selobj # we'll use this, or set it to None and use None
        if selobj is None:
            return None, None
        if not self.graphicsMode.selobj_still_ok(selobj):
            #bruce 070919 removed local exception-protection from this method call
            self.set_selobj(None)
            return None, None
        hicolor = self.selobj_hicolor(selobj) # ask the mode; protected from exceptions
        if hicolor is None:
            # the mode wants us to not use it.
            # REVIEW: is anything suboptimal about set_selobj(None) here,
            # like disabling the stencil buffer optim?
            # It might be better to retain self.selobj but not draw it in this case.
            # [bruce 070919 comment]
            self.set_selobj(None)
            return None, None
        # both selobj and hicolor are ok and not None
        return selobj, hicolor

    def selobj_hicolor(self, obj):
        """
        If obj was to be highlighted as selobj
        (whether or not it's presently self.selobj),
        what would its highlight color be?
        Or return None if obj should not be allowed as selobj.
        """
        try:
            hicolor = self.graphicsMode.selobj_highlight_color( obj)
                #e should implem noop version in basicMode [or maybe i did]
            # mode can decide whether selobj should be highlighted
            # (return None if not), and if so, in what color
        except:
            if debug_flags.atom_debug:
                msg = "atom_debug: selobj_highlight_color exception for %r" % (obj,)
                print_compact_traceback(msg + ": ")
            else:
                print "bug: selobj_highlight_color exception for %r; " \
                      "for details use ATOM_DEBUG" % (obj,)
            hicolor = None
        return hicolor

    def drawcompass(self):
        #bruce 080910 moved body into its own file
        #bruce 080912 removed aspect argument
        #bruce 081015 put constant parts into a display list (possible speedup),
        # and created class Compass to make this easier
        self.compass.draw(
            self.aspect,
            self.quat,
            self.compassPosition,
            self.graphicsMode.compass_moved_in_from_corner,
            env.prefs[displayCompassLabels_prefs_key]
         )
        return

    pass # end of class

# ==

if "test same_vals during import": #bruce 080922, of interest to GLPane_image_methods
    # note: we don't want to define this test in utilities.Comparison itself,
    # since it should not import geometry.VQT. REVIEW: move it into VQT?
    from utilities.Comparison import same_vals, SAMEVALS_SPEEDUP
    # not a full test, just look for known bugs and print warnings if found
    ALWAYS_PRINT = False
    used_version = SAMEVALS_SPEEDUP and "C" or "python"
        # no way to test the other version (see comment where same_vals is defined)
    from geometry.VQT import Q
    if not same_vals( Q(1,0,0,0), Q(1,0,0,0) ):
        # this bug was in the C version but not the Python version;
        # Eric M fixed it in samevalshelp.c rev 14311, 080922
        print "BUG: not same_vals( Q(1,0,0,0), Q(1,0,0,0) ) [%s version]" % used_version
    elif ALWAYS_PRINT:
        print "fyi: same_vals( Q(1,0,0,0), Q(1,0,0,0) ) is True (correct) [%s version]" % used_version
    pass

# end