summaryrefslogtreecommitdiff
path: root/cad/src/utilities/constants.py
blob: bc3325a7a355dc2c59b5d6ae1d86320f7ba19dae (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
# Copyright 2004-2009 Nanorex, Inc. See LICENSE file for details.
"""
constants.py -- constants and trivial functions used in multiple modules.

Everything defined here must require no imports except from builtin
modules or PyQt, and use names that we don't mind reserving throughout NE1.
(At the moment there are some exceptions to that, but these should be
cleaned up.)

(Ideally this module would also contain no state; probably we should move
gensym out of it for that reason.)

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

from PyQt4.Qt import Qt

import os

# ==

#Urmi 20080617: grid origin related constants: UM 20080616
PLANE_ORIGIN_LOWER_LEFT = 0
PLANE_ORIGIN_LOWER_RIGHT = 1
PLANE_ORIGIN_UPPER_LEFT = 2
PLANE_ORIGIN_UPPER_RIGHT = 3
LABELS_ALONG_ORIGIN = 0
LABELS_ALONG_PLANE_EDGES = 1

MULTIPANE_GUI = True
    # enable some code which was intended to permit the main window to contain
    # multiple PartWindows. Unfortunately we're far from that being possible,
    # but we're also (I strongly suspect, but am not sure) now dependent on
    # this value being True, having not maintained the False case for a long
    # time. If this is confirmed, we should remove the code for the False case
    # and remove this flag, and then decide whether the singleton partWindow
    # should continue to exist. [bruce 071008, replacing a debug_pref with
    # this flag]

DIAMOND_BOND_LENGTH = 1.544
    #bruce 051102 added this based on email from Damian Allis:
    # > The accepted bond length for diamond is 1.544 ("Interatomic
    # > Distances,' The Chemical Society, London, 1958, p.M102)....

# ==

RECENTFILES_QSETTINGS_KEY = '/Nanorex/NE1/recentFiles'

# ==

shiftModifier = 33554432
cntlModifier = 67108864
    # note: in Qt/Mac, this flag indicates the command key, not the control key.
altModifier = 134217728
    # note: in Qt/Mac, this flag indicates the Alt/Option modifier key.

# Todo: it would be better if we replaced the above by the equivalent
# named constants provided by Qt.
# [namely, Qt.ShiftModifier, Qt.ControlModifier, Qt.AltModifier.]
# [later, before 080728: This has been done for all of their uses in the code
# except in the definition of debugModifiers (below), probably during the port
# to Qt4.]
#
# Before doing this, we should find out how they correspond on each platform --
# for example, I don't know whether Qt's named constant for the control key
# will have the same numeric value on Windows and Mac, as our own named constant
# 'cntlModifier' does. So no one should replace the above numbers by
# Qt's declared names before they check this out on each platform.
# [bruce 040916]

# debugModifiers should be an unusual combination of modifier keys, used
# to bring up an undocumented debug menu intended just for developers
# (if a suitable preference is set). The following value is good for
# the Mac; someone on Windows or Linux can decide what value would be
# good for those platforms, and make this conditional on the
# platform. (If that's done, note that sys.platform on Mac might not
# be what you'd guess -- it can be either "darwin" or "mac" (I think),
# depending on the python installation.)  -- bruce 040916

debugModifiers = cntlModifier | shiftModifier | altModifier
    # note: on the mac, this really means command-shift-alt

# ==

# Trivial functions that might be needed early during app startup
# (good to put here to avoid recursive import problems involving other modules)
# or in many modules.
# (Only a very few functions are trivial enough to be put here,
#  and their names always need to be suitable for using up in every module.)


def noop(*args, **kws):
    pass

def intRound(num): #bruce 080521
    """
    Round a number (int or float) to the closest int.

    @warning: int(num + 0.5) is *not* a correct formula for this
              (when num is negative), since Python int() rounds
              towards zero, not towards negative infinity
              [http://docs.python.org/lib/built-in-funcs.html].
    """
    return int(round(num))

def str_or_unicode(qstring): #bruce 080529
    """
    Return str(qstring), unless that fails with UnicodeEncodeError,
    in which case return unicode(qstring).

    @param qstring: anything, but typically a QString object.
    """
    try:
        return str(qstring)
    except UnicodeEncodeError:
        return unicode(qstring)
    pass

def genKey(start = 1):
    #bruce 050922 moved this here from chem.py and Utility.py, added start arg
    """
    produces generators that count indefinitely
    """
    i = start
    while 1:
        yield i
        i += 1
    pass

atKey = genKey(start = 1)
    # generator for atom.key attribute, also used for fake atoms.
    # [moved here from chem.py to remove import cycle, bruce 080510]
    # As of bruce 050228, we now make use of the fact that this produces keys
    # which sort in the same order as atoms are created (e.g. the order they're
    # read from an mmp file), so we now require this in the future even if the
    # key type is changed. [Note: this comment appears in two files.]

# ==

_gensym_counters = {} # holds last-used value for each fixed prefix (default 0)

def _fix_gensym_prefix(prefix): #bruce 070604
    """
    [private helper function for gensym and relatives]
    """
    assert type(prefix) in (type(""), type(u""))
    if prefix and prefix[-1].isdigit():
        # This special behavior guarantees that every name gensym returns is
        # unique. As of bruce 070603, I think it never happens, based on the
        # existing calls of gensym.

        # Note: someday we might change the added char to ' ' if prefix
        # contains ' ', and/or also do this if prefix ends with a letter (so
        # most gensym callers can rely on this rule rather than adding '-'
        # themselves).

        # NOTE: this special rule is still needed, even if we never want
        # default new node names to contain '-' or ' '. It's ok since they
        # also won't include digits in the prefix, so this rule won't happen.
        # [bruce 080407 comment]
        prefix = prefix + '-'
    return prefix

def gensym(prefix, assy = None):
    #bruce 070603 rewrite, improved functionality (replaces three separate
    # similar definitions)
    #bruce 080407: new option to pass assy, used for names_to_avoid
    """
    Return prefix with a number appended, where the number is 1 more
    than the last time we were called for the same prefix, or 1 the first time
    we see that prefix. Note that this means we maintain an independent counter
    for each different prefix we're ever called with.

    In order to ensure that every name we ever return is unique (in spite of our
    independent counters reusing the same values for different prefixes), we append
    '-' to prefix if it ends with a digit already, before looking up and appending
    the counter for that prefix.

    (The prefix is typically related to a Node classname, but can be more or less
    specialized, e.g. when making chunks of certain kinds (like DNA) or copying nodes
    or library parts.)

    @param prefix: prefix for generated name.

    @param assy: if provided and not None, don't duplicate any node name
                 presently used in assy.
    """
    # REVIEW: maybe: move this code into a new method in class Assembly
    prefix = _fix_gensym_prefix(prefix)
    names_to_avoid = {}
        # maps name -> anything, for 0 or more names we don't want to generate
    # fill names_to_avoid with node names from assy, if provided
    if assy is not None:
        def avoid_my_name(node):
            names_to_avoid[node.name] = None
        assy.root.apply2all( avoid_my_name )
            # could optim and not delve inside nodes that hide contents
    new_value = _gensym_counters.get(prefix, 0) + 1
    name = prefix + str(new_value)
    while name in names_to_avoid:
        new_value += 1
        name = prefix + str(new_value)
    _gensym_counters[prefix] = new_value
    return name

def permit_gensym_to_reuse_name(prefix, name): #bruce 070604
    """
    This gives gensym permission to reuse the given name which it returned based on the given prefix,
    if it can do this and still follow its other policies. It is not obligated to do this.
    """
    prefix = _fix_gensym_prefix(prefix)
    last_used_value = _gensym_counters.get(prefix, 0)
    last_used_name = prefix + str(last_used_value)
    if name == last_used_name:
        # this is the only case in which we can safely do anything.
        corrected_last_used_value = last_used_value - 1
        assert corrected_last_used_value >= 0
            # can't happen if called on names actually returned from gensym
        _gensym_counters[prefix] = corrected_last_used_value
    return

# ==

def average_value(seq, default = 0.0):
    """
    Return the numerical average value of seq (a Python sequence or equivalent),
    or (by default) 0.0 if seq is empty.

    Note: Numeric contains a function named average, which is why we don't use that name.
    """
    #bruce 070412; renamed and moved from selectMode.py to constants.py 070601
    #e should typetest seq if we can do so efficiently
    # WARNING: this uses <built-in function sum>, not Numeric.sum.
    if not seq:
        return default
    return sum(seq) / len(seq)

# ==

def common_prefix( seq1, *moreseqs ):
    """
    Given one or more python sequences (as separate arguments),
    return the initial common prefix of them (determined by != of elements)
    (as a list and/or the sequence type of the first of these sequences --
     which of these sequence types to use is not defined by this function's
     specification, but it will be one of those three types)
    (it's also undefined whether the retval might be the same mutable object
     as the first argument!)
    """
    #bruce 080626, modified from a version in node_indices.py
    length_ok = len(seq1)
    for seq2 in moreseqs:
        if len(seq2) < length_ok:
            length_ok = len(seq2)
        for i in xrange(length_ok):
            if seq1[i] != seq2[i]:
                length_ok = i
                break
    return seq1[0:length_ok] # might be all or part of seq1, or 0-length

# ==

# Display styles (aka display modes)

# TODO: this entire section ought to be split into its own file,
# or perhaps more than one (constants, globals, helper functions).
# (Two helper functions have since been moved to mmp_dispnames.py --
#  perhaps more defs or code should join them there, not sure. [bruce 090116])
#
# BUT, the loop below, initializing ATOM_CONTENT_FOR_DISPLAY_STYLE,
# needs to run before dispNames, or (preferably) on a copy of it
# from before it's modified, by external init code. [bruce 080324 comment]
# [that has a grammar error, not quite sure what was intended -- bruce 090116]

# The global variables are: remap_atom_dispdefs, dispNames, new_dispNames,
# dispLabel.

remap_atom_dispdefs = {} #bruce 080324 moved this here from displaymodes.py

# These are arranged in order of increasing thickness of the bond
# representation. They are indices of dispNames and dispLabel. [Josh 11/2]
diDEFAULT = 0 # the fact that diDEFAULT == 0 is public. [bruce 080206]
diINVISIBLE = 1
diTrueCPK = 2 # CPK
    # [renamed from old name diVDW, bruce 060607; corresponding UI change was
    # by mark 060307] (This is not yet called diCPK, to avoid confusion, since
    # that name was used for diBALL until today. After some time goes by, we
    # can rename this to just diCPK.)
diLINES = 3
diBALL = 4 # "Ball and Stick"
    # [renamed from old incorrect name diCPK, bruce 060607; corresponding UI
    #  change was by mark 060307]
diTUBES = 5
# WARNING (kluge):
# the order of the following constants has to match how the lists dispNames and
# dispLabel (defined below) are extended by side effects of imports of
# corresponding display styles in startup_misc.py. (Needs cleanup.)
# [bruce 080212 comment; related code has comments with same signature]
diDNACYLINDER = 6
diCYLINDER = 7
diSURFACE = 8
diPROTEIN = 9

# note: some of the following lists are extended later at runtime
# [as of bruce 060607]
dispNames = ["def", "inv", "vdw", "lin", "cpk", "tub"]
    # these dispNames can't be easily revised, since they are used in mmp files;
    # cpk and vdw are misleading as of 060307.
    # NOTE: as of bruce 080324, dispNames is now private.
    # Soon it will be renamed and generalized to permit aliases for the names.
    # Then it will become legal to read (but not yet to write) the new forms
    # of the names which are proposed in bug 2662.

new_dispNames = ["def", "Invisible", "CPK", "Lines", "BallAndStick", "Tubes"]
    #bruce 080324 re bug 2662; permit for reading, but don't write them yet

# Note: some defs were moved from here to mmp_dispnames.py [bruce 090116]


# <properDisplayNames> used by write_qutemol_pdb_file() in qutemol.py only.
# Set _qxDNACYLINDER to "def" until "dnacylinder" is supported in QuteMolX.
_qxDNACYLINDER = "def"
properDisplayNames = ["def", "inv", "cpk", "lin", "bas", "tub", _qxDNACYLINDER]

#dispLabel = ["Default", "Invisible", "VdW", "Lines", "CPK", "Tubes"]
dispLabel = ["Default", "Invisible", "CPK", "Lines", "Ball and Stick", "Tubes"]
# Changed "CPK" => "Ball and Stick" and "VdW" => "CPK".  mark 060307.

def _f_add_display_style_code( disp_name, disp_label, allowed_for_atoms):
    """
    [friend function for displaymodes.py]
    """
    #bruce 080324 split this out of displaymodes.py, to permit making
    # these globals (dispNames, dispLabel) private soon
    if disp_name in dispNames:
        # this is only legal [nim] if the classname is the same;
        # in that case, we ought to replace things (useful for reload
        # during debugging)
        assert 0, "reload during debug for display modes " \
               "is not yet implemented; or, non-unique " \
               "mmp_code %r (in dispNames)" % (disp_name,)
    if disp_name in new_dispNames: #bruce 080415
        # same comment applies as above
        assert 0, "reload during debug for display modes " \
               "is not yet implemented; or, non-unique " \
               "mmp_code %r (in new_dispNames)" % (disp_name,)
    assert len(dispNames) == len(dispLabel)
    assert len(dispNames) == len(new_dispNames) #bruce 080415
    dispNames.append(disp_name)
    new_dispNames.append(disp_name) #bruce 080415 fix bug 2809 in
        # saving nodes with "chunk display styles" set (not in .rc1)
    dispLabel.append(disp_label)
    ind = dispNames.index(disp_name) # internal value used by setDisplayStyle
        # note: this always works, since we appended the same disp_name to
        # *both* dispNames and new_dispNames [bruce 080415 comment]
    if not allowed_for_atoms:
        remap_atom_dispdefs[ind] = diDEFAULT # kluge?
    return ind

# ==

# display style for new glpanes (#e should be a user preference) [bruce 041129]
# Now in user prefs db, set in GLPane.__init__ [Mark 050715]
# WARNING: this is no longer used as the default global display style,
# and now has a different value from that, but it is still used in other ways,
# which would need analysis in order to determine whether they can be replaced
# with the actual default global display style. Needs cleanup.
# [bruce 080606 comment]
default_display_mode = diTUBES

TubeRadius = 0.3 # (i.e. "TubesSigmaBondRadius")
diBALL_SigmaBondRadius = 0.1
diDNACYLINDER_SigmaBondRadius = 1.3

# ==

# atom content flags [bruce 080306]

# (so far, we only have these for display style, but more might be added
#  for other aspects of atoms, such as kind of element, whether selected,
#  whether highlighted, whether has error, etc;
#  in spite of the term "atom content" we might also add some for nodes,
#  e.g. all the same ones mentioned for atoms.)

ATOM_CONTENT_FOR_DISPLAY_STYLE = []
    # modified by the loop below to be same length as dispNames
AC_HAS_INDIVIDUAL_DISPLAY_STYLE = 1
AC_INVISIBLE = 1 << diINVISIBLE
    # note: fewer bits than ATOM_CONTENT_FOR_DISPLAY_STYLE[diINVISIBLE]
for _disp in range(len(dispNames)):
    # WARNING:
    # - this must run before dispNames is modified by external code
    # - it assumes no styles defined in displaymodes.py can apply to atoms
    if not _disp:
        assert _disp == diDEFAULT
        _content_for_disp = 0
    elif _disp == diINVISIBLE:
        # don't redundantly count this as "individual display style"
        _content_for_disp = AC_INVISIBLE
    else:
        _content_for_disp = \
                          (AC_HAS_INDIVIDUAL_DISPLAY_STYLE + (1 << _disp))
        # this uses bits 1 through len(dispNames) - 1,
        # plus bit 0 for "any of those"
    ATOM_CONTENT_FOR_DISPLAY_STYLE.append(_content_for_disp)

# ==

# constants related to bounding boxes containing atoms and bonds [piotr 080402]

# The estimated maximum sphere radius in any display style.
# The maximum VdW atom radius is 5.0 A.
# It can be increased by 25% in User Preferences.
# Highlighting increases this radius by 0.2A.
# Total = 5.0A * 1.25 + 0.2A = 6.2A
MAX_ATOM_SPHERE_RADIUS = 6.2

# Margin value for bounding box (used in BoundingBox.py)
BBOX_MARGIN = 1.8

# The minimal bounding sphere radius for a single atom of VdW radius = 0.0,
# calculated as follows: BB_MIN_RADIUS = sqrt(3 * (BBOX_MARGIN) ^ 2)
BBOX_MIN_RADIUS = 3.118

# ==

# PAM models. (Possible values of atom.element.pam, besides None,
#  and of some "info chunk" attributes in the mmp format, besides "".
#  Values must never be changed (unless info chunk read/write code
#  is revised to hide the change), since they are part of mmp format.)
#
# [bruce 080321]

MODEL_PAM3 = 'PAM3'
MODEL_PAM5 = 'PAM5'

PAM_MODELS = (MODEL_PAM3, MODEL_PAM5)

MODEL_MIXED = 'PAM_MODEL_MIXED' # review: good this is not in PAM_MODELS?

# Dna constants presently needed outside of dna package.
# After sufficient refactoring, these could be moved inside it.

Pl_STICKY_BOND_DIRECTION = 1 # should be 1 or -1;
    # the bond direction from Pl to the Ss it wants to stay with when possible.
    # (This value (1) is consistent with which strand-ends get Pls
    #  in the PAM5 generator as of 080312, and with other evidence #doc)
    # [bruce 080118/080326] #e rename?

# ==

# constants for bondpoint_policy [bruce 080603]

BONDPOINT_LEFT_OUT = "BONDPOINT_LEFT_OUT"
BONDPOINT_UNCHANGED = "BONDPOINT_UNCHANGED" # not yet specifiable
BONDPOINT_ANCHORED = "BONDPOINT_ANCHORED" # not yet specifiable
BONDPOINT_REPLACED_WITH_HYDROGEN = "BONDPOINT_REPLACED_WITH_HYDROGEN"

# ==

# constants for readmmp
SUCCESS = 'SUCCESS'
ABORTED = 'ABORTED'
READ_ERROR = 'READ ERROR'

# ==

def filesplit(pathname):
    """
    Splits pathname into directory part (not ending with '/'),
    basename, and extension (including '.', or can be "")
    and returns them in a 3-tuple.
    For example, filesplit('~/foo/bar/gorp.xam') ==> ('~/foo/bar', 'gorp', '.xam').
    Compare with _fileparse (deprecated), whose returned dir ends with '/'.
    """
    #bruce 050413 _fileparse variant: no '/' at end of dirname
    #bruce 071030 moved this from movieMode to constants
    dir1, file1 = os.path.split(pathname)
    base, ext = os.path.splitext(file1)
    return dir1, base, ext

# ==

def remove_prefix(str1, prefix):
    # TODO: put this into a new file, utilities.string_utils?
    """
    Remove an optional prefix from a string:
    if str1 starts with prefix, remove it (and return the result),
    otherwise return str1 unchanged.

    @param str1: a string that may or may not start with prefix.
    @type str1: string

    @param prefix: a string to remove if it occurs at the beginning of str1.
    @type prefix: string

    @return: a string, equal to str1 with prefix removed, or to str1.
    """
    if str1.startswith(prefix):
        return str1[len(prefix):]
    else:
        return str1
    pass

# ==

# ave_colors() logically belongs in some "color utilities file",
# but is here so it is defined early enough for use in computing
# default values of user preferences in prefs_constants.py.

def ave_colors(weight, color1, color2):
    """
    Return a weighted average of two colors,
    where weight gives the amount of color1 to include.
    (E.g., weight of 1.0 means use only color1, 0.0 means use only color2,
    and ave_colors(0.8, color, black) makes color slightly darker.)

    Color format is a 3-tuple of RGB components from 0.0 to 1.0
    (e.g. black is (0.0, 0.0, 0.0), white is (1.0, 1.0, 1.0)).
    This is also the standard format for colors in our preferences database
    (which contains primitive Python objects encoded by the shelve module).

    Input color components can be ints, but those are coerced to floats,
    NOT treated as in the range [0,255] like some other color-related functions do.
    Output components are always floats.

    Input colors can be any 3-sequences (including Numeric arrays);
    output color is always a tuple.
    """
    #bruce 050805 moved this here from handles.py, and revised it
    #e (perhaps we could optimize this using some Numeric method)
    weight = float(weight)
    return tuple([weight * c1 + (1 - weight) * c2
                  for c1, c2 in zip(color1, color2)])

def colors_differ_sufficiently(color1, color2, minimum_difference = 0.51 ):
    """
    Return True if the difference between color1 and color2
    (as vectors in an RGB unit cube) is greater than minimum_difference
    (0.51 by default). Otherwise, return False.
    """
    # [probably by Mark, circa 080710]
    # [revised by bruce 080711 to remove import cycle involving VQT]
    # bruce 080910 renamed this from color_difference, since it does
    # not return the color difference, and revised default value since
    # all calls were passing the same value of minimum_difference.
    color_diff_squared = sum([(color2[i] - color1[i]) ** 2
                              for i in (0, 1, 2)
                              ])
    if color_diff_squared > minimum_difference ** 2:
        return True
    return False

def getTextHaloColor(textColor):
    """
    @return: a good halo color, given a text color.
             The halo color will be either light gray or dark gray
             and will not be too close to textColor.
    """
    if colors_differ_sufficiently(lightgray, textColor):
        return lightgray
    else:
        return darkgray
    pass

# colors
# [note: some of the ones whose names describe their function
#  are default values for user preferences]

black =  (0.0, 0.0, 0.0)
white =  (1.0, 1.0, 1.0)
darkblue =  (0.0, 0.0, 0.6) # Was blue. Mark 2008-06-27
blue = (0.0, 0.0, 1.0)
aqua =   (0.15, 1.0, 1.0)
orange = (1.0, 0.25, 0.0)
darkorange = (0.6, 0.3, 0.0)
red =    (1.0, 0.0, 0.0)
lightred_1 = (0.99, 0.501, 0.505) #reddish pink color
yellow = (1.0, 1.0, 0.0)
green =  (0.0, 1.0, 0.0)
lightgreen = (0.45, 0.8, 0.45) # bruce 080206
lightgreen_2 = (0.596, 0.988, 0.596)
darkgreen =  (0.0, 0.6, 0.0)
magenta = (1.0, 0.0, 1.0)
cyan = (0.0, 1.0, 1.0)
lightgray = (0.8, 0.8, 0.8)
gray =   (0.5, 0.5, 0.5)
darkgray = (0.3, 0.3, 0.3)
navy =   (0.0, 0.09, 0.44)
darkred = (0.6, 0.0, 0.2)
violet = (0.6, 0.1, 0.9)
purple = (0.4, 0.0, 0.6)
darkpurple = (0.3, 0.0, 0.3)
pink = (0.8, 0.4, 0.4)
olive = (0.3, 0.3, 0.0)
steelblue = (0.3, 0.4, 0.5)
brass = (0.5, 0.5, 0.0)
copper = (0.3, 0.3, 0.1)
mustard = (0.78, 0.78, 0.0)
applegreen = (0.4, 0.8, 0.4)
banana = (0.8901, 0.8117, 0.3411)
silver = (0.7529, 0.7529, 0.7529)
gold = (1, 0.843, 0)
ivory = (1, 1, 0.9411)
#ninad20060922 using it while drawing origin axis
lightblue = ave_colors(0.03, white, blue)
    # Note: that color is misnamed -- it's essentially just blue.
    # Or maybe the definition has a typo?
    # This needs cleanup... in the meantime,
    # consider also lighterblue
lighterblue = ave_colors( 0.5, white, blue)

# Following color is used to draw the back side of a reference plane.
#Better call it brownish yellow or greenish brown?? lets just call it brown
#(or suggest better name by looking at it. ) - ninad 20070615
brown = ave_colors(0.5, black, yellow)

# The background gradient types/values.
# Gradient values are one more than the gradient constant values in
# Preferences.py (i.e. bgBLUE_SKY =  BG_BLUE_SKY + 1).
bgSOLID = 0
bgBLUE_SKY = 1
bgEVENING_SKY = 2
bgSEAGREEN = 3

# GLPane "Blue Sky" gradient
bluesky = (0.9, 0.9, 0.9), (0.9, 0.9, 0.9), (0.33, 0.73, 1.0), (0.33, 0.73, 1.0)

# GLPane "Evening Sky" gradient
eveningsky = (0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.3), (0.0, 0.0, 0.3)

# GLPane "Sea Green" gradient
bg_seagreen = ((0.905, 0.905, 0.921),
               (0.905, 0.905, 0.921),
               (0.6, 0.8, 0.8),
               (0.6, 0.8, 0.8) )

bg_seagreen_UNUSED_FOR_DEBUG = (0.894, 0.949, 0.894), (0.862, 0.929, 0.862), \
(0.686, 0.843, 0.843), (0.905, 0.905, 0.921), \
(0.862, 0.929, 0.862), (0.839, 0.921, 0.839), \
(0.67, 0.835, 0.835), (0.686, 0.843, 0.843), \
(0.686, 0.843, 0.843), (0.67, 0.835, 0.835), \
(0.6, 0.8, 0.8), (0.6, 0.8, 0.8), \
(0.905, 0.905, 0.921), (0.686, 0.843, 0.843), \
(0.6, 0.8, 0.8), (0.701, 0.85, 0.85)

## PickedColor = (0.0, 0.0, 1.0) # no longer used as of 080603
ErrorPickedColor = (1.0, 0.0, 0.0) # for atoms with wrong valence, etc

elemKeyTab =  [('H', Qt.Key_H, 1),
               ('B', Qt.Key_B, 5),
               ('C', Qt.Key_C, 6),
               ('N', Qt.Key_N, 7),
               ('O', Qt.Key_O, 8),
               ('F', Qt.Key_F, 9),
               ('Al', Qt.Key_A, 13),
               ('Si', Qt.Key_Q, 14),
               ('P', Qt.Key_P, 15),
               ('S', Qt.Key_S, 16),
               ('Cl', Qt.Key_L, 17)]

# ==

# values for assy.selwhat variable [moved here from assembly.py by bruce 050519]

# bruce 050308 adding named constants for selwhat values;
# not yet uniformly used (i.e. most code still uses hardcoded 0 or 2,
#  and does boolean tests on selwhat to see if chunks can be selected);
# not sure if these would be better off as assembly class constants:
# values for assy.selwhat: what to select: 0=atoms, 2 = molecules.
# SELWHAT_NAMES is for use in human-readable messages.
SELWHAT_ATOMS = 0
SELWHAT_CHUNKS = 2
SELWHAT_NAMES = {SELWHAT_ATOMS: "Atoms", SELWHAT_CHUNKS: "Chunks"}

# mark 060206 adding named constants for selection shapes.
SELSHAPE_LASSO = 'LASSO'
SELSHAPE_RECT = 'RECTANGLE'

# mark 060206 adding named constants for selection logic.
SUBTRACT_FROM_SELECTION = 'Subtract Inside'
OUTSIDE_SUBTRACT_FROM_SELECTION = 'Subtract Outside'
    # OUTSIDE_SUBTRACT_FROM_SELECTION is used only in CrystalShape.py
ADD_TO_SELECTION = 'Add'
START_NEW_SELECTION = 'New'
DELETE_SELECTION = 'Delete'

# ==

DEFAULT_COMMAND = 'SELECTMOLS' # commandName of default command

# command level constants [bruce 080725]

CL_DEFAULT_MODE = 'CL_DEFAULT_MODE'
CL_ENVIRONMENT_PROVIDING = 'CL_ENVIRONMENT_PROVIDING'
CL_MISC_TOPLEVEL = 'CL_MISC_TOPLEVEL'
CL_SUBCOMMAND = 'CL_SUBCOMMAND'
CL_EDIT_GENERIC = 'CL_EDIT_GENERIC'
CL_EXTERNAL_ACTION = 'CL_EXTERNAL_ACTION'
CL_GLOBAL_PROPERTIES = 'CL_GLOBAL_PROPERTIES'
CL_VIEW_CHANGE = 'CL_VIEW_CHANGE'

CL_REQUEST = 'CL_REQUEST' # for a request command (only one level?)

CL_ABSTRACT = 'CL_ABSTRACT' # for abstract command classes
    # (warning if instantiated directly)

CL_UNUSED = 'CL_UNUSED' # for command classes thought to be presently unused
    # (warning if instantiated directly, or (if practical) if a subclass is
    #  instantiated)

# ==

# The far clipping plane normalized z value, actually it's a little closer
# than the actual far clipping plane to the eye. This is used to draw the blue
# sky backround polygon, and also used to check if user click on empty space
# on the screen.
GL_FAR_Z = 0.999

# ==

# Determine CAD_SRC_PATH.
# For developers, this is the directory containing
# NE1's toplevel Python code, namely .../cad/src;
# for users of a built release, this is the directory
# containing the same toplevel Python modules as that does,
# as they're built into NE1 and importable while running it.
# Note that if ALTERNATE_CAD_SRC_PATH is defined, it will influence
# the value of this constant (i.e. this constant always honors
# that value).
# [bruce 080111, comment revised 080721]

try:
    __file__
except:
    # CAD_SRC_PATH can't be determined (by the present code)
    # (does this ever happen?)
    print "can't determine CAD_SRC_PATH"
    CAD_SRC_PATH = None
else:
    CAD_SRC_PATH = os.path.dirname(__file__)
    assert os.path.basename(CAD_SRC_PATH) == "utilities"
    CAD_SRC_PATH = os.path.dirname(CAD_SRC_PATH)
    #print "CAD_SRC_PATH = %r" % CAD_SRC_PATH
    # [review: in a built Mac release, CAD_SRC_PATH might be
    # .../Contents/Resources/Python/site-packages.zip, or a related pathname
    # containing one more directory component; but an env var RESOURCEPATH
    # (spelling?) should also be available (only in a release and only on Mac),
    # and might make more sense to use then.]
    pass

# end