diff options
author | Bruce Smith <bruce@nanorex.com> | 2009-01-13 22:38:51 +0000 |
---|---|---|
committer | Bruce Smith <bruce@nanorex.com> | 2009-01-13 22:38:51 +0000 |
commit | d11cecd753c30445655ef317c0c91355daae8706 (patch) | |
tree | 240309f89d1f5c7ca1680ed28ae184de8909f8b5 | |
parent | 32fcee7aecec879452a51577263a55df7b60b99a (diff) | |
download | nanoengineer-theirix-d11cecd753c30445655ef317c0c91355daae8706.tar.gz nanoengineer-theirix-d11cecd753c30445655ef317c0c91355daae8706.zip |
line length, misc cleanup
-rw-r--r-- | cad/src/dna/model/DnaLadderRailChunk.py | 2 | ||||
-rwxr-xr-x | cad/src/graphics/drawables/handles.py | 29 | ||||
-rwxr-xr-x | cad/src/model/bonds.py | 486 | ||||
-rwxr-xr-x | cad/src/model/chem.py | 1275 | ||||
-rwxr-xr-x | cad/src/model/chunk.py | 617 |
5 files changed, 1492 insertions, 917 deletions
diff --git a/cad/src/dna/model/DnaLadderRailChunk.py b/cad/src/dna/model/DnaLadderRailChunk.py index 471df0281..3c0a6f222 100644 --- a/cad/src/dna/model/DnaLadderRailChunk.py +++ b/cad/src/dna/model/DnaLadderRailChunk.py @@ -80,7 +80,7 @@ class DnaLadderRailChunk(Chunk): def __init__(self, assy, name = None, chain = None, reuse_old_chunk_if_possible = False): """ @note: init method signature is compatible with _superclass.__init__, - since it needs to work in _superclass.copy_empty_shell_in_mapping + since it needs to work in _superclass._copy_empty_shell_in_mapping passing just assy and name, expecting us to have no atoms. """ # TODO: check if this arg signature is ok re undo, copy, etc; diff --git a/cad/src/graphics/drawables/handles.py b/cad/src/graphics/drawables/handles.py index d03f5bac3..e48ce9c97 100755 --- a/cad/src/graphics/drawables/handles.py +++ b/cad/src/graphics/drawables/handles.py @@ -111,12 +111,11 @@ class HandleSet: ## assert 0 def findHandles_exact(self, p1, p2, cutoff = 0.0, backs_ok = 1, offset = V(0,0,0)): """ - return a list of (dist, handle) pairs, in arbitrary order, - which includes, for each handle (spherical surface) hit by the ray from p1 thru p2, - its front-surface intersection with the ray, - unless that has dist < cutoff and backs_ok, - in which case include its back-surface intersection - (unless *that* has dist < cutoff). + @return: a list of (dist, handle) pairs, in arbitrary order, which + includes, for each handle (spherical surface) hit by the ray from p1 + thru p2, its front-surface intersection with the ray, unless that has + dist < cutoff and backs_ok, in which case include its back-surface + intersection (unless *that* has dist < cutoff). """ #e For now, just be simple, don't worry about speed. # Someday we can preprocess self.handlpos using Numeric functions, @@ -125,8 +124,10 @@ class HandleSet: hh = self.handles res = [] v = norm(p2-p1) - ## is this modifying the vector in-place, causing a bug?? offset += self.origin # treat our handles' pos as relative to this - ## I don't know, but one of the three instances of += was doing this!!! probably i was resetting the atom or mol pos.... + # is this modifying the vector in-place, causing a bug?? + ## offset += self.origin # treat our handles' pos as relative to this + # I don't know, but one of the three instances of += was doing this!!! + # probably i was resetting the atom or mol pos.... offset = offset + self.origin # treat our handles' pos as relative to this radius_multiplier = self.radius_multiplier for (pos,radius,info) in hh: @@ -146,12 +147,14 @@ class HandleSet: return res def frontDistHandle(self, p1, p2, cutoff = 0.0, backs_ok = 1, offset = V(0,0,0), copy_id = None): """ - return None, or the frontmost (dist, handle) pair, as computed by findHandles_exact; - but turn the handle into a pyobj for convenience of caller. + @return: None, or the frontmost (dist, handle) pair, as computed by + findHandles_exact; but turn the handle into a pyobj for convenience of + caller. """ - #####k i don't know if retval needs self.radius_multiplier... - #e will we need to let caller know whether it was the front or back surface we hit? - # or even the exact position on the sphere? if so, add more data to the returned pair. + # check: i don't know if retval needs self.radius_multiplier... + # review: will we need to let caller know whether it was the front or + # back surface we hit? or even the exact position on the sphere? if + # so, add more data to the returned pair. dhdh = self.findHandles_exact(p1, p2, cutoff, backs_ok, offset = offset) if not dhdh: return None dhdh.sort() diff --git a/cad/src/model/bonds.py b/cad/src/model/bonds.py index f4ead135d..4929a7b13 100755 --- a/cad/src/model/bonds.py +++ b/cad/src/model/bonds.py @@ -134,7 +134,7 @@ else: _bond_atoms_oldversion_noops_seen = {} #bruce 051216 -def bond_atoms_oldversion(a1, a2): #bruce 050502 renamed this from bond_atoms; it's called from the newer version of bond_atoms +def bond_atoms_oldversion(a1, a2): """ Make a new bond between atoms a1 and a2 (and add it to their lists of bonds), if they are not already bonded; if they are already bonded do nothing. @@ -149,7 +149,8 @@ def bond_atoms_oldversion(a1, a2): #bruce 050502 renamed this from bond_atoms; i This increases the number of bonds on each atom (when it makes a new bond) -- it never removes any singlets. Therefore it is mostly for low-level use. """ - # bruce 041109 split this out of [later removed] molecule.bond method. + #bruce 050502 renamed this from bond_atoms; it's called from the newer version of bond_atoms + #bruce 041109 split this out of [later removed] molecule.bond method. # Since it's the only caller of # Bond.__init__, what it does to the atoms could (and probably should) be put # inside the constructor. However, it should not simply be replaced with calls @@ -173,8 +174,9 @@ def bond_atoms_oldversion(a1, a2): #bruce 050502 renamed this from bond_atoms; i print_compact_stack("will remove one or both existing bonds, then make the requested new one: ") if b1: a1.bonds.remove(b1) - # probably no need for a1._changed_structure() since we'll do it in Bond(a1, a2) below; probably same for a2; - # but as a precaution in case of bugs (or misanalysis or future change of this code), + # probably no need for a1._changed_structure() since we'll do it + # in Bond(a1, a2) below; probably same for a2; but as a precaution + # in case of bugs (or misanalysis or future change of this code), # I'll do it anyway. [bruce 060322] a1._changed_structure() b1 = None @@ -187,13 +189,19 @@ def bond_atoms_oldversion(a1, a2): #bruce 050502 renamed this from bond_atoms; i ###e should we verify bond order is 1, otherwise complain more loudly?? if debug_flags.atom_debug: # print debug warning - #e refile this code -- only print warning once for each place in the code it can happen from - blame = compact_stack() # slow, but should be ok since this case should be rare - # known cases as of 051216 include only one: reading pdb files with redundant CONECT records + #e refile this code -- only print warning once for each place + # in the code it can happen from + blame = compact_stack() + # slow, but should be ok since this case should be rare + # known cases as of 051216 include only one: + # reading pdb files with redundant CONECT records if not _bond_atoms_oldversion_noops_seen.has_key(blame): - print_compact_stack( "atom_debug: note: bond_atoms_oldversion doing nothing since %r and %r already bonded: " % (a1, a2)) + msg = "atom_debug: note: bond_atoms_oldversion doing nothing " \ + "since %r and %r already bonded" % (a1, a2) + print_compact_stack( msg + ": ") if not _bond_atoms_oldversion_noops_seen: - print "(above message (bond_atoms_oldversion noop) is only printed once for each compact_stack that calls it)" + print "(above message (bond_atoms_oldversion noop) is " \ + "only printed once for each compact_stack that calls it)" _bond_atoms_oldversion_noops_seen[blame] = None pass return @@ -249,7 +257,9 @@ def bond_atoms_faster(at1, at2, v6): #bruce 050513; docstring corrected 050706 Return the new bond object (which is given the bond order code (aka valence) v6, which must be specified). """ - b = Bond(at1, at2, v6) # (this does all necessary invals, including _changed_structure on atoms, and asserts at1 is not at2) + b = Bond(at1, at2, v6) + # (this does all necessary invals, including _changed_structure on + # atoms, and asserts at1 is not at2) at1.bonds.append(b) at2.bonds.append(b) return b @@ -347,7 +357,8 @@ def bond_atoms(a1, a2, vnew = None, s1 = None, s2 = None, no_corrections = False if vnew is None: assert s1 is s2 is None assert no_corrections == False - bond_atoms_oldversion( a1, a2) # warning [obs??#k]: mol.copy might rely on this being noop when bond already exists! + bond_atoms_oldversion( a1, a2) + # warning [obs??#k]: mol.copy might rely on this being noop when bond already exists! return make_corrections = not no_corrections @@ -417,8 +428,10 @@ def bond_atoms(a1, a2, vnew = None, s1 = None, s2 = None, no_corrections = False if want_dir != - dir2: a2.fix_open_bond_directions(s2, - want_dir) - if 0 and debug_flags.atom_debug and (dir1 or dir2 or want_dir_a1 or want_dir_a2 or want_dir): - print "bond at open bonds with directions, %r and %r, => %r" % (s1 and s1.bonds[0], s2 and s2.bonds[0], want_dir) + if 0 and debug_flags.atom_debug and \ + (dir1 or dir2 or want_dir_a1 or want_dir_a2 or want_dir): + print "bond at open bonds with directions, %r and %r, => %r" % \ + (s1 and s1.bonds[0], s2 and s2.bonds[0], want_dir) pass @@ -485,21 +498,26 @@ def bond_direction(atom1, atom2): #bruce 070601 # == _changed_Bonds = {} # tracks all changes to Bonds: existence/liveness (maybe not needed), which atoms, bond order + # (for now, maps id(bond) -> bond, since a bond's atoms and .key can change) # - # Note: we don't yet have any explicit way to kill or destroy a Bond, and perhaps no Bond attrs change when a - # Bond is removed from its atoms (I'm not sure, and it might depend on which code does it); - # for now, this is ok, and it means those events needn't be tracked as changes to a Bond. - # If a Bond is later given a destroy method, that should remove it from this dict; - # If it has a kill or delete method (or one that's called when it's not on its atoms), - # that should count as a change in this dict (and perhaps it should also change its atom attrs). + # Note: we don't yet have any explicit way to kill or destroy a Bond, and + # perhaps no Bond attrs change when a Bond is removed from its atoms (I'm + # not sure, and it might depend on which code does it); for now, this is + # ok, and it means those events needn't be tracked as changes to a Bond. + # If a Bond is later given a destroy method, that should remove it from + # this dict; If it has a kill or delete method (or one that's called when + # it's not on its atoms), that should count as a change in this dict (and + # perhaps it should also change its atom attrs). # #bruce 060322 for Undo change-tracking; the related global dict - # global_model_changedicts.changed_bond_types should perhaps become a subscriber - # (though as of 071107 there are several things that get into _changed_Bonds - # but not into global_model_changedicts.changed_bond_types -- should REVIEW the correctness of that) - - ##e see comments about similar dicts in chem.py for how this will end up being used + # global_model_changedicts.changed_bond_types should perhaps become a + # subscriber (though as of 071107 there are several things that get into + # _changed_Bonds but not into global_model_changedicts.changed_bond_types + # -- should REVIEW the correctness of that) + # + # todo: see comments about similar dicts in chem.py for how this will end up + ##being used register_changedict( _changed_Bonds, '_changed_Bonds', ()) ###k related attrs arg?? #bruce 060329 @@ -541,8 +559,10 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite forgotten about (no need to kill or otherwise explicitly destroy them after they're not on their atoms). """ - pi_bond_obj = None #bruce 050718; used to memoize a perceived PiBondSpChain object (if any) which covers this bond - # sometimes I search for pi_bond_info when I want this; see also get_pi_info and pi_info + pi_bond_obj = None + #bruce 050718; used to memoize a perceived PiBondSpChain object (if + # any) which covers this bond. sometimes I search for pi_bond_info when + # I want this; see also get_pi_info and pi_info _s_undo_specialcase = UNDO_SPECIALCASE_BOND # This tells Undo what specialcase code to use for this class @@ -555,25 +575,38 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite _s_attr_v6 = S_DATA _s_attr__direction = S_DATA #bruce 070414 - _s_attr_atom1 = S_PARENT # too bad these can change, or we might not need them (not sure, might need them for saving a file... #k) + _s_attr_atom1 = S_PARENT + # too bad these can change, or we might not need them (not sure, might + # need them for saving a file... #k) _s_attr_atom2 = S_PARENT atom1 = atom2 = _valid_data = None # make sure these attrs always have values! _saved_geom = None - _direction = 0 # default value of private (except known to Atom.writemmp) instance variable for bond_direction [bruce 070414] - # _direction is 0 for most bonds; for directional bonds [###e term to be defined elsewhere] it will be 1 if atom1 comes first, + _direction = 0 + # default value of private (except known to Atom.writemmp) + # instance variable for bond_direction [bruce 070414] + # _direction is 0 for most bonds; for directional bonds + # [###e term to be defined elsewhere] it will be 1 if atom1 comes first, # or -1 if atom2 comes first, or 0 if the direction is not known. - # I think no code destructively swaps the atoms in a bond; if it ever does, it needs to invert self._direction too. - # That goes for the process of mmp save/load as well, and for copying of a bond (which *might* reverse the atoms) -- - # write and read this info with care, since it's the first time in NE1 that the order of a bond's atoms will matter. - # This is a private attr, since its meaning depends on atom order; public access methods must be - # passed one of self's atoms, so they can accept or return directions relative to that atom. - # For comments about how chains/rings of directional bonds might best be perceived, - # for purposes of inferring directions or warning about inconsistencies, - # see pi_bond_sp_chain.py's module docstring. - # [bruce 070414, mostly nim ###] - - _s_attr_key = S_DATA #bruce 060405, not sure why this wasn't needed before (or if it will help now) + # + # I think no code destructively swaps the atoms in a bond; + # if it ever does, it needs to invert self._direction too. + # That goes for the process of mmp save/load as well, and + # for copying of a bond (which *might* reverse the atoms) -- + # write and read this info with care, since it's the first + # time in NE1 that the order of a bond's atoms will matter. + # + # This is a private attr, since its meaning depends on atom + # order; public access methods must be passed one of self's atoms, + # so they can accept or return directions relative to that atom. + # + # For comments about how chains/rings of directional bonds might + # best be perceived, for purposes of inferring directions or + # warning about inconsistencies, see pi_bond_sp_chain.py's module + # docstring. [bruce 070414] + + _s_attr_key = S_DATA + #bruce 060405, not sure why this wasn't needed before (or if it will help now) # (update 060407: I don't if it did help, but it still seems needed in principle. # It's change-tracked by self._changed_atoms.) @@ -627,7 +660,9 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite atm = a2; res2 = atm is not None and archive.trackedobj_liveQ(atm) and self in atm.bonds if res1 != res2: print "bug: Bond._undo_aliveQ gets different answer on each atom; relevant data:", res1, res2, self - return True # not sure if this or False would be safer; using True so we're covered if any live atom refers to us + return True + # not sure if this or False would be safer; using True so + # we're covered if any live atom refers to us return res1 ## # I don't recall if a1 and a2 can ever be None, so be safe. ## # Warning: the following inlines Atom._undo_aliveQ for a1 and a2. [but does so wrongly as of 060406] @@ -684,7 +719,8 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite elif atom is self.atom2: self._direction = - direction else: - assert 0, "%r.set_bond_direction_from(%r, %r), but that bond doesn't have that atom" % (self, atom, direction) + assert 0, "%r.set_bond_direction_from(%r, %r), but that bond doesn't have that atom" % \ + (self, atom, direction) if debug_flags.atom_debug: assert self.bond_direction_from(atom) == direction assert self.bond_direction_from(self.other(atom)) == - direction @@ -693,13 +729,16 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite print "fyi: changed direction of %r from %r to %r" % (self, old, self._direction) self._changed_bond_direction() if propogate: - # propogate self's direction both ways, as far as possible, overwriting any prior directions encountered - # (and do this even if this bond's direction was already the same as the one we're setting) - # (note: the direction being set here can be 0) - # (note: if self can ever be a bond which *silently ignores* sets of direction, - # then for the following to be correct, we'd need to pass the initial direction to use, - # which would be an opposite one to each call. Not doing that now makes direction-switching bugs less likely - # or tests the default direction used by propogate_bond_direction_towards.) + # propogate self's direction both ways, as far as possible, + # overwriting any prior directions encountered (and do this even + # if this bond's direction was already the same as the one we're + # setting). (note: the direction being set here can be 0) (note: + # if self can ever be a bond which *silently ignores* sets of + # direction, then for the following to be correct, we'd need to + # pass the initial direction to use, which would be an opposite + # one to each call. Not doing that now makes direction-switching + # bugs less likely or tests the default direction used by + # propogate_bond_direction_towards.) ringQ = self.propogate_bond_direction_towards(self.atom1) if not ringQ: self.propogate_bond_direction_towards(self.atom2) @@ -726,14 +765,17 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite @see: _undo_update, which calls this, and the other self.changed*() methods. """ - # For Undo, we only worry about changes to "definitive" (not derived) state. That's only in self. - # Since Bonds use an optimized system for changetracking, we have to store self in a dictionary. - # (This is more efficient than calling atom._changed_structure on each of our atoms, + # For Undo, we only worry about changes to "definitive" (not derived) + # state. That's only in self. Since Bonds use an optimized system for + # changetracking, we have to store self in a dictionary. (This is more + # efficient than calling atom._changed_structure on each of our atoms, # since that would make Undo scan more attributes.) _changed_Bonds[id(self)] = self # tells Undo that something in this bond has changed - # Someday, doing that should also cover all modification notices to model object containers that contain us, - # such as the file we're saved in. It might already do that, but I'm not sure, so do that explicitly: + # Someday, doing that should also cover all modification notices to + # model object containers that contain us, such as the file we're + # saved in. It might already do that, but I'm not sure, so do that + # explicitly: self.changed() # tell the dna updater we changed the structure of both our atoms. @@ -768,13 +810,14 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite Return ringQ, a boolean which is True if self is part of a ring (as opposed to a linear chain) of directional bonds. """ - backdir = self.bond_direction_from(atom) # measured backwards, relative to direction in which we're propogating + backdir = self.bond_direction_from(atom) + # measured backwards, relative to direction in which we're propogating ringQ, listb, lista = grow_directional_bond_chain(self, atom) for b, a in zip(listb, lista): b.set_bond_direction_from(a, backdir) return ringQ - def is_directional(self): #bruce 070415 #e this might be replaced with an auto-updated attribute, self.directional + def is_directional(self): #bruce 070415 """ Does self have the atomtypes that make it want to have a direction? (We assume this property is independent of bond order, @@ -794,6 +837,7 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite each caller implements the high-level case, but most or all of those are channelled through one caller, Atom.directional_bond_chain_status(). """ + # maybe: this might be replaced with an auto-updated attribute, self.directional if self.atom1.element.bonds_can_be_directional and \ self.atom2.element.bonds_can_be_directional: return True @@ -812,13 +856,15 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite could support that, and the mmp reading code we're adding at the same time does support that. """ - # We'll support a bond_direction record which only writes atoms in bond_direction order. - # That way we needn't write out the direction itself, and the record format permits encoding - # a bond-chain in one record listing a chain of atomcodes - # (though we don't yet take advantage of that when writing). + # We'll support a bond_direction record which only writes atoms in + # bond_direction order. That way we needn't write out the direction + # itself, and the record format permits encoding a bond-chain in one + # record listing a chain of atomcodes (though we don't yet take + # advantage of that when writing). other = self.other(atom) direction = self.bond_direction_from(atom) - assert direction # otherwise we should not be writing this record (#e could extend API to let us return "" in this case) + assert direction # otherwise we should not be writing this record + #e (could extend API to let us return "" in this case) if direction < 0: atom, other = other, atom atomcodes = map( mapping.encode_atom, (atom, other) ) @@ -844,7 +890,6 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite return segment return segment - def getDnaStrand(self): """ @@ -862,7 +907,6 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite return chunk.getDnaStrand() return None - def getStrandName(self): # probably by Mark """ @@ -887,7 +931,7 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite return strand.name return "" - + def isStrandBond(self): # by Mark # Note: still used as of 080225. Not quite correct # for bonds on free-floating single strands -- probably @@ -1039,6 +1083,7 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite bondLength = "Distance " + str(a1) + "-" + str(a2) + ": " + nuclearDist + " A" return bondLength + # == def destroy(self): #bruce 060322 (not yet called) ###@@@ """ @@ -1060,10 +1105,13 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite for dict1 in _Bond_global_dicts: dict1.pop(key, None) if self.pi_bond_obj is not None: - self.pi_bond_obj.destroy() ###k is this safe, if that obj and ones its knows are destroyed? - ##e is this also needed in self._changed_atoms or _changed_v6??? see if bond orientation bugs are helped by that... + self.pi_bond_obj.destroy() + ###k is this safe, if that obj and ones its knows are destroyed? + ##e is this also needed in self._changed_atoms or _changed_v6??? + # see if bond orientation bugs are helped by that... #####@@@@@ [bruce 060322 comments] - self.__dict__.clear() ###k is this safe??? [see comments in Atom.destroy implem for ways we might change this ##e] + self.__dict__.clear() ###k is this safe??? [see comments in + # Atom.destroy implem for ways we might change this ##e] return def is_open_bond(self): #bruce 050727 @@ -1091,8 +1139,10 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite the final valence must be legal; not sure what should happen if no delta in [0, vdelta] makes it legal! For now, raise an AssertionError(?) exception then. #####@@@@@] """ - # we want the lowest permitted valence which is at least v_have - vdelta, i.e. in the range v_want to v_have. - # This code is very similar to that of increase_valence_noupdate, but differs in a few important places! + # we want the lowest permitted valence which is at least v_have - + # vdelta, i.e. in the range v_want to v_have. This code is very + # similar to that of increase_valence_noupdate, but differs in a few + # important places! assert vdelta >= 0 v_have = self.v6 v_want = v_have - vdelta @@ -1100,8 +1150,10 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite # make v_want legal (or raise exception if that's not possible) did_break = else_reached = 0 for v_to_try in BOND_VALENCES: # this list is in lowest-to-highest order - if v_want <= v_to_try <= v_have: # warning: order of comparison will differ in the sister method for "increase" - v_want = v_to_try # good thing we'll break now, since this assignment alters the meaning of the loop-test + if v_want <= v_to_try <= v_have: + # warning: order of comparison will differ in the sister method for "increase" + v_want = v_to_try # good thing we'll break now, since this + # assignment alters the meaning of the loop-test did_break = 1 break else: @@ -1134,17 +1186,21 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite the final valence must be legal; not sure what should happen if no delta in [0, vdelta] makes it legal! For now, raise an AssertionError(?) exception then. #####@@@@@] """ - # we want the highest permitted valence which is at most v_have + vdelta, i.e. in the range v_have to v_want. - # This code is very similar to that of reduce_valence_noupdate but several things are reversed. + # we want the highest permitted valence which is at most v_have + + # vdelta, i.e. in the range v_have to v_want. This code is very + # similar to that of reduce_valence_noupdate but several things are + # reversed. assert vdelta >= 0 v_have = self.v6 v_want = v_have + vdelta if not permit_illegal_valence: # make v_want legal (or raise exception if that's not possible) did_break = else_reached = 0 - for v_to_try in BOND_VALENCES_HIGHEST_FIRST: # this list is in highest-to-lowest order, unlike BOND_VALENCES + for v_to_try in BOND_VALENCES_HIGHEST_FIRST: + # note: this list is in highest-to-lowest order, unlike BOND_VALENCES if v_have <= v_to_try <= v_want: # warning: order of comparison differs in sister method - v_want = v_to_try # good thing we'll break now, since this assignment alters the meaning of the loop-test + v_want = v_to_try # good thing we'll break now, since this + # assignment alters the meaning of the loop-test did_break = 1 break else: @@ -1153,9 +1209,11 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite print "atom_debug: else clause reached in increase_valence_noupdate" if debug_flags.atom_debug: if not (did_break != else_reached): - print "atom_debug: i hoped for did_break != else_reached but it's not true, in increase_valence_noupdate" + print "atom_debug: i hoped for did_break != else_reached " \ + "but it's not true, in increase_valence_noupdate" if not did_break: - # no valence is legal! Not yet sure what to do in this case. (Or whether it ever happens.) + # no valence is legal! Not yet sure what to do in this case. + # (Or whether it ever happens.) assert 0, "no valence increase of %r from 0 to vdelta %r is legal!" % (v_have, vdelta) assert v_want in BOND_VALENCES # sanity check # now set valence to v_want @@ -1175,10 +1233,12 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite It does whatever invalidations that requires, but does no "updates". """ ###e update geometric things, using setup_invalidate?? ###@@@ - self.setup_invalidate() # not sure this is needed, but let's do it to make sure it's safe if/when it's needed [bruce 050502] + self.setup_invalidate() # not sure this is needed, but let's do it to + # make sure it's safe if/when it's needed [bruce 050502] # as of 060324, it's needed, since sim.getEquilibriumDistanceForBond depends on it # tell the atoms we're doing this - self.atom1._modified_valence = self.atom2._modified_valence = True # (this uses a private attr of class atom; might be revised) + self.atom1._modified_valence = self.atom2._modified_valence = True + # (this uses a private attr of class atom; might be revised) self._changed_bond_and_atom_appearances() # Fix for bug 886 [bruce 050811, revised 080210]: # Both atoms might look different if whether they have valence @@ -1238,10 +1298,13 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite records this fact.) """ self.atom1.changed() - self.atom2.changed() # for now, only the file (assy) records this, so 2nd call is redundant, but someday that will change. + self.atom2.changed() # for now, only the file (assy) records this, so + # 2nd call is redundant, but someday that will change. return - def numeric_valence(self): # has a long name so you won't be tempted to use it when you should use .v6 ###@@@ not yet used? + def numeric_valence(self): + # note: this method has a long name so you won't be tempted to use it + # when you should use .v6 ###@@@ not yet used? return self.v6 / 6.0 def _changed_atoms(self): @@ -1259,20 +1322,12 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite at2._changed_structure() assert at1 is not at2 - # This version helps atombase.c not to fail when atom keys + # This version of bond_key helps atombase.c not to fail when atom keys # exceed 32768, but is only unique for bonds attached to the # same atom. Within __eq__, it can only be considered a hash. # WARNING: as of 080402, __eq__ assumes this is the formula. self.bond_key = at1.key + at2.key - #self.bond_key = 65536 * min(at1.key, at2.key) + max(at1.key, at2.key) - # used only in __eq__ as of 051018; problematic (see comments there) - # !!!!!!! Nope, also used in chunk.standard_draw_atoms() - # such that keys must be unique within the chunk. But, - # with that use replaced with id(bond), we may be ok with - # this only being unique for bonds attached to a single - # atom. - #bruce 050317: debug warning for interpart bonds, or bonding killed atoms/chunks, # or bonding to chunks not yet added to any Part (but not warning about internal # bonds, since mol.copy makes those before a copied chunk is added to any Part). @@ -1459,8 +1514,9 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite # in this bond! That's a pain (and inefficient), so I might # replace it by a __getattr__ mol-coordsys-version-number check... # [and sometime after that, before 050719, I did.] - a1pos = atom1.baseposn() #e could optim, since their calcs of whether basepos is present are the same + a1pos = atom1.baseposn() a2pos = atom2.baseposn() + #e could optim, since their calcs of whether basepos is present are the same return self.geom_from_posns(a1pos, a2pos) def geom_from_posns(self, a1pos, a2pos): #bruce 050727 split this out @@ -1505,9 +1561,10 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite pass if attr[0] == '_': raise AttributeError, attr # be fast since probably common for __xxx__ - # after this, attr is either an updated_attr or a bug, so it's ok to assume we need to recompute if invalid... - # if any of the attrs used by recomputing geom are missing, we'll get infinite recursion; - # these are just atom1, atom2, and the ones used herein. + # after this, attr is either an updated_attr or a bug, so it's ok to + # assume we need to recompute if invalid... if any of the attrs used + # by recomputing geom are missing, we'll get infinite recursion; these + # are just atom1, atom2, and the ones used herein. current_data = (self.atom1.molecule.bond_inval_count, self.atom2.molecule.bond_inval_count) if self._valid_data != current_data: # need to recompute @@ -1519,7 +1576,9 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite else: geom = self._saved_geom # when valid, should always have been computed, thus be of proper length if attr == 'geom': - return geom # callers desiring speed should use this case, to get several attrs but only check validity once + # note: callers desiring speed should use this case, to get + # several attrs but only check validity once + return geom elif attr == 'a1pos': return geom[0] elif attr == 'c1': @@ -1532,7 +1591,8 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite return geom[4] elif attr == 'toolong': return geom[5] - elif attr == 'axis': # a2pos - a1pos (not normalized); in relative coords [bruce 050719 new feature] + elif attr == 'axis': + # a2pos - a1pos (not normalized); in relative coords [bruce 050719 new feature] return geom[4] - geom[0] else: raise AttributeError, attr @@ -1542,18 +1602,25 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite def get_pi_info(self, **kws): #bruce 050718 """ - Return the pi orbital orientation/occupancy info for this bond, if any [#doc the format], or None - if this is a single bond (which is not always a bug, e.g. in -C#C- chains, if we someday extend the - subrs this calls to do any bond inference -- presently they just trust the existing bond order, self.v6). - This info might be computed, and perhaps stored, or stored info might be used. It has to be computed - all at once for all pi bonds in a chain connected by sp atoms with 2 bonds. - If computed, and if it's partly arbitrary, **kws (out/up) might be used. + Return the pi orbital orientation/occupancy info for this bond, if any + [#doc the format], or None if this is a single bond (which is not + always a bug, e.g. in -C#C- chains, if we someday extend the subrs + this calls to do any bond inference -- presently they just trust the + existing bond order, self.v6). + + This info might be computed, and perhaps stored, or stored info might + be used. It has to be computed all at once for all pi bonds in a chain + connected by sp atoms with 2 bonds. + + If computed, and if it's partly arbitrary, **kws (out/up) might be used. """ if debug_flags.atom_debug: import model.pi_bond_sp_chain as pi_bond_sp_chain - reload_once_per_event(pi_bond_sp_chain) #bruce 050825 use reload_once_per_event to remove intolerable slowdown + reload_once_per_event(pi_bond_sp_chain) + #bruce 050825 use reload_once_per_event to remove intolerable slowdown from model.pi_bond_sp_chain import bond_get_pi_info - return bond_get_pi_info(self, **kws) # no need to pass an index -- that method can find one on self if it stored one + return bond_get_pi_info(self, **kws) + # no need to pass an index -- that method can find one on self if it stored one def potential_pi_bond(self): #bruce 050718 """ @@ -1589,7 +1656,8 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite return c1 else: assert mol in (c1, c2) - # this always fails (so it's ok if it's slow) -- it's just our "understandable error message" + # this always fails (so it's ok if it's slow) -- + # it's just our "understandable error message" pass def ubp(self, atom): @@ -1937,30 +2005,40 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite and should be renamed to reflect that, since its behavior can and should be specialized for that use. (E.g. it doesn't happen inside display lists; and it need not use glName at all.) """ - #Note: bool_fullBondLength represent whether full bond length to be drawn - #it is used only in select Chunks mode while highlighting the whole chunk and when - #the atom display is Tubes display -- ninad 070214 + #Note: bool_fullBondLength represents whether full bond length is to + #be drawn. It is used only in select Chunks mode while highlighting + #the whole chunk and when the atom display is Tubes display + # [ninad 070214] - highlighted = True # ninad 070214 - passing 'highlighted' to bond.draw instead of highlighted = bool + highlighted = True # ninad 070214 - passing 'highlighted' to + # bond.draw instead of highlighted = bool if self.killed(): - #bruce 050702, part of fix 2 of 2 redundant fixes for bug 716 (both fixes are desirable) + #bruce 050702, part of fix 2 of 2 redundant fixes for bug 716 + #(both fixes are desirable) return mol = self.atom1.molecule mol2 = self.atom2.molecule if mol is mol2: - # internal bond; geometric info is stored in chunk-relative coords; we need mol's help to use those + # internal bond; geometric info is stored in chunk-relative + # coords; we need mol's help to use those mol.pushMatrix() self.draw(glpane, mol.get_dispdef(glpane), color, mol.assy.drawLevel, highlighted, bool_fullBondLength ) - # sorry for all the kluges (e.g. 2 of those args) that beg for refactoring! The info passing in draw methods - # is not designed for drawing leaf nodes by themselves in a clean way! (#e should clean up somehow) - #bruce 050702 using shorten_tubes [as of 050727, this is done via highlighted = True] - # to help make room to mouseover-highlight the atoms, - # when in tubes mode (thus fixing bug 715-1); a remaining bug was that it's sometimes hard to - # highlight the tube bonds, apparently due to selatom seeming bigger even when not visible (not sure). - # In another commit, same day, GLPane.py (sort selobj candidates) and this file (don't shorten_tubes - # next to singlets [later moved to bond_drawer.py]), this has been fixed. + # sorry for all the kluges (e.g. 2 of those args) that beg for + # refactoring! The info passing in draw methods is not + # designed for drawing leaf nodes by themselves in a clean + # way! (#e should clean up somehow) + + #bruce 050702 using shorten_tubes [as of 050727, this is done + #via highlighted = True] to help make room to + #mouseover-highlight the atoms, when in tubes mode (thus + #fixing bug 715-1); a remaining bug was that it's sometimes + #hard to highlight the tube bonds, apparently due to selatom + #seeming bigger even when not visible (not sure). In another + #commit, same day, GLPane.py (sort selobj candidates) and this + #file (don't shorten_tubes next to singlets [later moved to + #bond_drawer.py]), this has been fixed. mol.popMatrix() else: # external bond -- draw it at max dispdef of those from its mols @@ -1999,7 +2077,8 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite # This last condition doesn't yet work right, not sure why: ## ... or not self in atom1.bonds # Problem: without it, this might be wrong if the bond was "busted" - # without either atom being killed. For now, just leave it out; fix this sometime. #####@@@@@ + # without either atom being killed. For now, just leave it out; + # fix this sometime. #####@@@@@ # Warning: that last condition is slow, too. # [later: see also ExternalBondSet._correct_bond, which # checks this itself, and its comments. [bruce 080702 comment]] @@ -2009,7 +2088,8 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite return True pass - def writepov(self, file, dispdef, col): #bruce 050727 moving implem to separate file; 050730 fixed bug in this method's name + def writepov(self, file, dispdef, col): + #bruce 050727 moving implem to separate file; 050730 fixed bug in this method's name """ Write this bond to a povray file (always using absolute coords, even for internal bonds). @@ -2017,10 +2097,15 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite writepov_bond(self, file, dispdef, col) return - def __str__(self): #bruce 050705 revised this; note that it contains chars not compatible with HTML unless quoted + def __str__(self): + #bruce 050705 revised this; note that it contains chars not compatible + #with HTML unless quoted + ## return str(self.atom1) + " <--> " + str(self.atom2) - # No quat is easily available here; better results if you call that subr directly and pass one; - # the right one is (in current code, AFAIK, 070415) glpane.quat for the glpane that will display the bond + + # No quat is easily available here; better results if you call that + # subr directly and pass one; the right one is (in current code, + # AFAIK, 070415) glpane.quat for the glpane that will display the bond # (whether or not it's an internal bond); let's try to guess that: quat = Q(1,0,0,0) try: @@ -2028,9 +2113,11 @@ class Bond(BondBase, StateMixin, Selobj_API): #bruce 041109 partial rewrite quat = glpane.quat except: if env.debug(): - print_compact_traceback("bug: exception in self.atom1.molecule.assy.o.quat: ") # don't print self here! + # don't print self here! + print_compact_traceback("bug: exception in self.atom1.molecule.assy.o.quat: ") pass - return bonded_atoms_summary(self, quat = quat) #bruce 070415 added quat arg and above code to guess it ###UNTESTED + return bonded_atoms_summary(self, quat = quat) + #bruce 070415 added quat arg and above code to guess it ###UNTESTED def __repr__(self): return str(self.atom1) + "::" + str(self.atom2) @@ -2084,11 +2171,12 @@ def bond_at_singlets(s1, s2, **opts): [I suspect this feature is no longer operative -- should review, update doc. -- bruce 080213] - It's an error if s1 == s2, or if they're on the same atom. It's a - warning (no error, but no bond made) if the real atoms of the singlets - are already bonded, unless we're able to make the existing bond have - higher bond order, which we'll only attempt if increase_bond_order = True, - and [bruce 050702] which is only possible if the bonded elements have suitable atomtypes. + It's an error if s1 == s2, or if they're on the same atom. It's a warning + (no error, but no bond made) if the real atoms of the singlets are already + bonded, unless we're able to make the existing bond have higher bond + order, which we'll only attempt if increase_bond_order = True, and [bruce + 050702] which is only possible if the bonded elements have suitable + atomtypes. If the singlets are bonded to their base atoms with different bond orders, it's an error if we're unable to adjust those to match... #####@@@@@ #k. @@ -2171,11 +2259,15 @@ class _bonder_at_singlets: "asked to bond atom %r to itself,\n" " from the same singlet %r (passed twice)" % (a1, s1)) # untested formatting if a1 is a2: #bruce 041119, part of fix for bug #203 - ###@@@ should we permit this as a way of changing the bonding pattern by summing the valences of these bonds? YES! [doit] - # [later comment, 050702:] this is low-priority, since it's difficult to do for a free atom - # (the atom tries to rotate to make it impossible) (tho I have to admit, for a bound C(sp3) - # with 2 open bonds left, it's not too hard, due to the arguable-bug in which only one of them moves) - # and since the context menu lets you do it more directly. ... even so, let's try it: + ###@@@ should we permit this as a way of changing the bonding + ###pattern by summing the valences of these bonds? YES! [doit] + # [later comment, 050702:] this is low-priority, since it's + # difficult to do for a free atom (the atom tries to rotate to + # make it impossible) (tho I have to admit, for a bound C(sp3) + # with 2 open bonds left, it's not too hard, due to the + # arguable-bug in which only one of them moves) and since the + # context menu lets you do it more directly. ... even so, let's + # try it: if self.increase_bond_order and a1.can_reduce_numbonds(): return self.merge_open_bonds() #bruce 050702 new feature else: @@ -2185,18 +2277,25 @@ class _bonder_at_singlets: if atoms_are_bonded(a1, a2): #bruce 041119, part of fix for bug #121 # not an error (so arg2 is None) - if self.increase_bond_order and a1.can_reduce_numbonds() and a2.can_reduce_numbonds(): - # we'll try that -- it might or might not work, but it has its own error messages if it fails - #bruce 050702 added can_reduce_numbonds conditions, which check whether the atoms can change their atomtypes appropriately + if self.increase_bond_order and \ + a1.can_reduce_numbonds() and \ + a2.can_reduce_numbonds(): + # we'll try that -- it might or might not work, but it has its + # own error messages if it fails + #bruce 050702 added can_reduce_numbonds conditions, which + #check whether the atoms can change their atomtypes + #appropriately return self.upgrade_existing_bond() else: if a1.can_reduce_numbonds() and a2.can_reduce_numbonds(): why = "won't" else: why = "can't" - return do_error("%s increase bond order between already-bonded atoms %r and %r" % (why, a1, a2), None) + return do_error("%s increase bond order between already-" \ + "bonded atoms %r and %r" % (why, a1, a2), None ) #####@@@@@ worry about s1 and s2 valences being different - # [much later, 050702: that might be obs, but worrying about their *permitted* valences might not be...] + # [much later, 050702: that might be obs, but worrying about + # their *permitted* valences might not be...] # (not sure exactly what we'll do then! # do bonds want to keep track of a range of permissible valences?? # it's a real concept (I think) and it's probably expensive to recompute. @@ -2210,7 +2309,8 @@ class _bonder_at_singlets: def bond_unbonded_atoms(self): s1, a1 = self.s1, self.a1 s2, a2 = self.s2, self.a2 - status = "bonded atoms %r and %r" % (a1, a2) #e maybe subr should make this?? #e subr might prefix it with bond-type made ###@@@ + status = "bonded atoms %r and %r" % (a1, a2) + #e maybe subr should make this?? #e subr might prefix it with bond-type made ###@@@ # we only consider "move m1" in the case of no preexisting bond, # so we only need it in this submethod (in fact, if it was in # the other one, it would never run, due to the condition about @@ -2261,9 +2361,11 @@ class _bonder_at_singlets: a1 = self.a1 a2 = self.a2 - #bruce 071018, stop using old code here, finally; might fix open bond direction; clean up if so ###TODO + #bruce 071018, stop using old code here, finally; + # might fix open bond direction; clean up if so ###TODO USE_OLD_CODE = debug_pref("Bonds: use OLD code for actually_bond?", - Choice_boolean_False # bruce 071019 change default to False since new code works now + Choice_boolean_False + # bruce 071019 change default to False since new code works now ) v1 = s1.singlet_v6() @@ -2286,12 +2388,16 @@ class _bonder_at_singlets: vnew = min(v1, v2) bond = bond_atoms(a1, a2, vnew, s1, s2) - # tell it the singlets to replace or reduce; let this do everything now, incl updates. - # can that fail? I don't think so; if it could, it'd need to have new API and return us an error message explaining why. - ###########@@@@@@@@@@ TODO bruce 071018: need to make this not harm any *other* singlets on these atoms, - # since some callers already recorded them and want to call this immediately again to make other bonds. - # it's good if it kills *these* singlets when that's correct, though. - # REVIEW: what's the status of that? + # tell it the singlets to replace or reduce; let this do + # everything now, incl updates. can that fail? I don't think so; + # if it could, it'd need to have new API and return us an error + # message explaining why. + + ###########@@@@@@@@@@ TODO bruce 071018: need to make this not + #harm any *other* singlets on these atoms, since some callers + #already recorded them and want to call this immediately again to + #make other bonds. it's good if it kills *these* singlets when + #that's correct, though. REVIEW: what's the status of that? vused = bond.v6 # this will be the created bond prefix = bond_type_names[vused] + '-' @@ -2311,31 +2417,47 @@ class _bonder_at_singlets: s1, a1 = self.s1, self.a1 s2, a2 = self.s2, self.a2 v1, v2 = s1.singlet_v6(), s2.singlet_v6() - if len(a1.bonds) == 2: # (btw, this method is only called when a1 and a2 have at least 2 bonds) - # Since a1 will have only one bond after this (if it's able to use up all of s1's valence), - # we might as well try to add even more valence to the bond, to correct any deficient valence on a1. - # But we'll never do this if there are uninvolved bonds to a1, since user might be planning - # to manually increase their valence after doing this operation. - # [bruce 051215 new feature, which ought to also fix bug 1221; other possible fixes seem too hard to do in isolation. - # In that bug, -N-N- temporarily became N=N and was then "corrected" to N-N rather than to N#N as would be better.] + if len(a1.bonds) == 2: + # (btw, this method is only called when a1 and a2 have at least 2 + # bonds) + # Since a1 will have only one bond after this (if it's able to use + # up all of s1's valence), we might as well try to add even more + # valence to the bond, to correct any deficient valence on a1. But + # we'll never do this if there are uninvolved bonds to a1, since + # user might be planning to manually increase their valence after + # doing this operation. + # [bruce 051215 new feature, which ought to also fix bug 1221; + # other possible fixes seem too hard to do in isolation. + # In that bug, -N-N- temporarily became N=N and was then "corrected" + # to N-N rather than to N#N as would be better.] v1 += a1.deficient_v6() # usually adds 0 if len(a2.bonds) == 2: v2 += a2.deficient_v6() - vdelta = min(v1, v2) # but depending on the existing bond, we might use less than this, or none + vdelta = min(v1, v2) + # but depending on the existing bond, we might use less than this, or none bond = find_bond(a1, a2) ## old_bond_v6 = bond.v6 #bruce 051215 debug code vdelta_used = bond.increase_valence_noupdate(vdelta) # increases as much as possible up to vdelta, to some legal value - # (ignores elements and other bond orders -- "legal" just means for any conceivable bond); - # returns actual amount of increase (maybe 0) + # (ignores elements and other bond orders -- "legal" just means + # for any conceivable bond); returns actual amount of increase + # (maybe 0) ## new_bond_v6 = bond.v6 #bruce 051215 debug code - ###@@@ why didn't we use vdelta_used in place of vdelta, below? (a likely bug, which would erroneously reduce valence; - # but so far I can't find a way to make it happen -- except dNdNd where it fixes preexisting valence errors! - # I will fix it anyway, since it obviously should have been written that way to start with. [bruce 051215]) + + ###@@@ why didn't we use vdelta_used in place of vdelta, below? (a + #likely bug, which would erroneously reduce valence; but so far I + #can't find a way to make it happen -- except dNdNd where it fixes + #preexisting valence errors! I will fix it anyway, since it obviously + #should have been written that way to start with. [bruce 051215]) + ## if debug_flags.atom_debug: #bruce 051215 -## print "atom_debug: bond_v6 changed from %r to %r; vdelta_used (difference) is %r; vdelta is %r" % (old_bond_v6, new_bond_v6, vdelta_used, vdelta) +## print "atom_debug: bond_v6 changed from %r to %r; " \ +## "vdelta_used (difference) is %r; vdelta is %r" % \ +## (old_bond_v6, new_bond_v6, vdelta_used, vdelta) if not vdelta_used: - return self.do_error("can't increase order of bond between atoms %r and %r" % (a1, a2), None) #e say existing order? say why not? + return self.do_error("can't increase order of bond between " \ + "atoms %r and %r" % (a1, a2), None) + #e say existing order? say why not? vdelta = vdelta_used #bruce 051215 fix unreported hypothetical bug (see comment above) s1.singlet_reduce_valence_noupdate(vdelta) # this might or might not kill it; @@ -2350,16 +2472,23 @@ class _bonder_at_singlets: # when it has a chance and wants to clean up structure, if this can ever be ambiguous # later when the current state (including positions of old singlets) is gone. a2.update_valence() - return (0, "increased bond order between atoms %r and %r" % (a1, a2)) #e say existing and new order? - # Note, bruce 060629: the new bond order would be hard to say, since later code in bond_updater.py is likely - # to decrease the value, but in a way it might be hard to predict at this point (it depends on what happens - # to the atomtypes which that code will also fix, and that depends on the other bonds we modify here; - # in theory we have enough info here, but the code is not well structured for this -- unless we save up this - # message here and somehow emit it later after that stuff has been resolved). Not an ideal situation.... - def merge_open_bonds(self): #bruce 050702 new feature; implem is a guess and might be partly obs when written + return (0, "increased bond order between atoms %r and %r" % (a1, a2)) + #e say existing and new order? + # Note, bruce 060629: the new bond order would be hard to say, + # since later code in bond_updater.py is likely to decrease the + # value, but in a way it might be hard to predict at this point + # (it depends on what happens to the atomtypes which that code + # will also fix, and that depends on the other bonds we modify + # here; in theory we have enough info here, but the code is not + # well structured for this -- unless we save up this message here + # and somehow emit it later after that stuff has been resolved). + # Not an ideal situation.... + + def merge_open_bonds(self): """ Merge the bond-valence of s1 into that of s2 """ + #bruce 050702 new feature; implem is a guess and might be partly obs when written s1, a1 = self.s1, self.a1 s2, a2 = self.s2, self.a2 v1, v2 = s1.singlet_v6(), s2.singlet_v6() @@ -2367,17 +2496,22 @@ class _bonder_at_singlets: bond1 = s1.bonds[0] bond2 = s2.bonds[0] #e should following permit illegal values? be a singlet method? - vdelta_used = bond2.increase_valence_noupdate(vdelta) # increases to legal value, returns actual amount of increase (maybe 0) + vdelta_used = bond2.increase_valence_noupdate(vdelta) + # increases to legal value, returns actual amount of increase (maybe 0) if not vdelta_used: - return self.do_error("can't merge these two bondpoints on atom %r" % (a1,), None) #e say existing orders? say why not? + return self.do_error("can't merge these two bondpoints on atom %r" % (a1,), None) + #e say existing orders? say why not? s1.singlet_reduce_valence_noupdate(vdelta) - a1.update_valence() # this can change the atomtype of a1 to match the fact that it deletes a singlet [bruce comment 050728] + a1.update_valence() + # this can change the atomtype of a1 to match the fact that it + # deletes a singlet [bruce comment 050728] return (0, "merged two bondpoints on atom %r" % (a1,)) pass # end of class _bonder_at_singlets, the helper for function bond_at_singlets # === -# some unused old code that would be premature to completely remove [moved here by bruce 050502] +# some unused old code that would be premature to completely remove +# [moved here by bruce 050502] ##def externs_except_to(mol, others): #bruce 041123; not yet used or tested ## # [written to help bond_at_singlets fix bug 150, but not used for that] diff --git a/cad/src/model/chem.py b/cad/src/model/chem.py index 7ec8ec4c6..1f7b08dad 100755 --- a/cad/src/model/chem.py +++ b/cad/src/model/chem.py @@ -185,7 +185,8 @@ from graphics.drawing.special_drawing import SPECIAL_DRAWING_STRAND_END DEBUG_1779 = False # do not commit with True, but leave the related code in for now [bruce 060414] -BALL_vs_CPK = 0.25 # ratio of default diBALL radius to default diTrueCPK radius [renamed from CPKvdW by bruce 060607] +BALL_vs_CPK = 0.25 # ratio of default diBALL radius to default diTrueCPK radius + # [renamed from CPKvdW by bruce 060607] # == @@ -228,14 +229,23 @@ def _undo_update_Atom_jigs(archive, assy): """ del archive if 1: - # bruce 060414 fix bug 1779 (more efficient than doing something in Atom._undo_update, for every atom) - # KLUGE: assume this always runs (true as of 060414), not only if there are jigs or under some other "when needed" conds. - # Note: it would be best to increment this counter at the start and end of every user op, but there's not yet any central place - # for code to run at times like that (except some undo-related code which runs at other times too). + # bruce 060414 fix bug 1779 (more efficient than doing something in + # Atom._undo_update, for every atom) + + # KLUGE: assume this always runs (true as of 060414), not only if + # there are jigs or under some other "when needed" conds. + + # Note: it would be best to increment this counter at the start and + # end of every user op, but there's not yet any central place for code + # to run at times like that (except some undo-related code which runs + # at other times too). # - # A more principled and safer fix would be either for kill functions participating in "prekill" to take - # an argument, unique per prekill/kill event, or to ensure the global counter (acting as if it was that argument) - # was unique by again incrementing it after the kill call returns within the same code that had initiated the prekill. + # A more principled and safer fix would be either for kill functions + # participating in "prekill" to take an argument, unique per + # prekill/kill event, or to ensure the global counter (acting as if it + # was that argument) was unique by again incrementing it after the + # kill call returns within the same code that had initiated the + # prekill. Utility._will_kill_count += 1 mols = assy.allNodes(assy.Chunk) # note: this covers all Parts, whereas assy.molecules only covers the current Part. jigs = assy.allNodes(Jig) @@ -249,25 +259,38 @@ def _undo_update_Atom_jigs(archive, assy): for m in mols: for a in m.atoms.itervalues(): if a.jigs: - _changed_structure_Atoms[a.key] = a #bruce 060322; try to only do this to atoms that need it - #k tracking this change is probably not needed by Undo but might be needed by future non-Undo subscribers - # to that dict; Undo itself needs to remember to clear its subscribed cache of it after this ###@@@DOIT - a.jigs = [] #e or del it if we make that optim in Jig (and review whether this needs to occur outside 'if a.jigs') + _changed_structure_Atoms[a.key] = a + #bruce 060322; try to only do this to atoms that need it + + #k tracking this change is probably not needed by Undo but + #might be needed by future non-Undo subscribers to that dict; + #Undo itself needs to remember to clear its subscribed cache + #of it after this ###@@@DOIT + a.jigs = [] + #e or del it if we make that optim in Jig (and review + #whether this needs to occur outside 'if a.jigs') for b in a.bonds: - #k maybe the S_CACHE decl will make this unnecessary? Not sure... maybe not, and it's safe. + #k maybe the S_CACHE decl will make this unnecessary? + # Not sure... maybe not, and it's safe. b.pi_bond_obj = None - # I hope refdecr is enough to avoid a memory leak; if not, give that obj a special destroy for us to call here. - # That obj won't remove itself from a.jigs on refdecr (no __del__); but it would in current implem of .destroy. + # I hope refdecr is enough to avoid a memory leak; if not, + # give that obj a special destroy for us to call here. That + # obj won't remove itself from a.jigs on refdecr (no __del__); + # but it would in current implem of .destroy. del b.pi_bond_obj # save RAM for j in jigs: for a in j.atoms: a.jigs.append(j) - _changed_structure_Atoms[a.key] = a #bruce 060322; see comment about same statement above + _changed_structure_Atoms[a.key] = a + #bruce 060322; see comment about same statement above for j in jigs: for a in j.atoms[:]: j.moved_atom(a) - # strictly speaking, this is beyond our scope, but Atom._undo_update can't do it since a.jigs isn't set. - # Also, whatever this does should really just be done by Jig._undo_update. So make that true, then remove this. ###@@@ + # strictly speaking, this is beyond our scope, but + # Atom._undo_update can't do it since a.jigs isn't set. Also, + # whatever this does should really just be done by + # Jig._undo_update. So make that true, then remove this. + # ###@@@ if j.killed(): #bruce 080120 added this (precaution) break j.changed_structure(a) #bruce 080120, might be needed by DnaMarker @@ -284,11 +307,17 @@ def _undo_update_Atom_jigs(archive, assy): # [bruce 071003 comment] register_undo_updater( _undo_update_Atom_jigs, updates = ('Atom.jigs', 'Bond.pi_bond_obj'), - after_update_of = ('Assembly', 'Node', 'Atom.bonds') # Node also covers its subclasses Chunk and Jig. - # We don't care if Atom is updated except for .bonds, nor whether Bond is updated at all, - # which is good because *we* are presumably a required part of updating both of those classes! - # FYI, we use 'Assembly' (string) rather than Assembly (class) to avoid a recursive import problem, - # and also to avoid an inappropriate import dependency (low-level -> high-level). + after_update_of = ('Assembly', 'Node', 'Atom.bonds') + # Node also covers its subclasses Chunk and Jig. + # We don't care if Atom is updated except for .bonds, + # nor whether Bond is updated at all, + # which is good because *we* are presumably a + # required part of updating both of those classes! + + # FYI, we use 'Assembly' (string) rather than Assembly (class) + # to avoid a recursive import problem, + # and also to avoid an inappropriate import dependency + # (low-level -> high-level). ) # == @@ -296,57 +325,69 @@ register_undo_updater( _undo_update_Atom_jigs, # changedicts for class Atom, used by Undo and by dna updater # [definitions moved from this file to global_model_changedicts.py, bruce 080510] -# These global dicts all map atom.key -> atom, for atoms which change in various ways (different for each dict). -# The dicts themselves (as opposed to their contents) never change (so other modules can permanently import them), -# but they are periodically processed and cleared. -# For efficiency, they're global and not weak-valued, -# so it's important to delete items from them when destroying atoms -# (which is itself nim, or calls to it are; destroying assy needs to do that ### TODO). +# These global dicts all map atom.key -> atom, for atoms which change in +# various ways (different for each dict). The dicts themselves (as opposed to +# their contents) never change (so other modules can permanently import them), +# but they are periodically processed and cleared. For efficiency, they're +# global and not weak-valued, so it's important to delete items from them when +# destroying atoms (which is itself nim, or calls to it are; destroying assy +# needs to do that ### TODO). -# obsolete comment: -# ###@@@ Note: These are not yet looked at, but the code to add atoms into them is supposedly completed circa bruce 060322. -# update 071106: some of them are looked at (and have been since Undo worked), but maybe not all of them. +# obsolete comment: ###@@@ Note: These are not yet looked at, but the code to +# add atoms into them is supposedly completed circa bruce 060322. update +# 071106: some of them are looked at (and have been since Undo worked), but +# maybe not all of them. from model.global_model_changedicts import _changed_parent_Atoms - # record atoms w/ changed assy or molecule or liveness/killedness - # (an atom's assy is atom.molecule.assy; no need to track changes here to the mol's .part or .dad) - # related attributes: __killed, molecule ###@@@ declare these?? - # not yet sure if that should be per-attr or not, re subclasses... - # WARNING: name is private, but it's directly accessed in many places in - # chunk.py [bruce 071106 comment] + # record atoms w/ changed assy or molecule or liveness/killedness (an + # atom's assy is atom.molecule.assy; no need to track changes here to the + # mol's .part or .dad) related attributes: __killed, molecule ###@@@ + # declare these?? not yet sure if that should be per-attr or not, re + # subclasses... WARNING: name is private, but it's directly accessed in + # many places in chunk.py [bruce 071106 comment] register_changedict( _changed_parent_Atoms, '_changed_parent_Atoms', ('__killed', ATOM_CHUNK_ATTRIBUTE_NAME) ) - #k or must we say _Atom__killed?? - # (It depends on whether that routine knows how to mangle it itself.) - # (As of long before 071018 that arg of register_changedict (related_attrs) - # is not yet used.) + #k or must we say _Atom__killed?? (It depends on whether that routine + #knows how to mangle it itself.) (As of long before 071018 that arg of + #register_changedict (related_attrs) is not yet used.) from model.global_model_changedicts import _changed_structure_Atoms # tracks changes to element, atomtype, bond set, bond direction (not bond order #k) - # WARNING: there is also a related but different global dict - # earlier than this one in the same file (global_model_changedicts.py), - # whose spelling differs only in 'A' vs 'a' in Atoms, and in having no initial underscore, - # namely, changed_structure_atoms. + + # WARNING: there is also a related but different global dict earlier than + # this one in the same file (global_model_changedicts.py), whose spelling + # differs only in 'A' vs 'a' in Atoms, and in having no initial + # underscore, namely, changed_structure_atoms. # - # This confusion should be cleaned up sometime, by letting that one just be a subscriber to this one, - # and if efficiency demands it, first splitting this one into the part equivalent to that one, and the rest. + # This confusion should be cleaned up sometime, by letting that one just + # be a subscriber to this one, and if efficiency demands it, first + # splitting this one into the part equivalent to that one, and the rest. # - # Ways this one has more atoms added to it than that one does: - # jigs, info, kill, bond direction. (See also the comment where the other one is defined.) - # See also: _changed_parent_Atoms, which also covers kill (probably in a better way). + # Ways this one has more atoms added to it than that one does: jigs, info, + # kill, bond direction. (See also the comment where the other one is + # defined.) + + # See also: _changed_parent_Atoms, which also covers kill (probably in a + # better way). # - # related attributes: bonds, element, atomtype, info, jigs # (not only '.jigs =', but '.jigs.remove' or '.jigs.append') - # (we include info since it's used for repeat-unit correspondences in extrude; this is questionable) - # (we include jigs since they're most like a form of structure, and in future might have physical effects, - # and since the jigs for pi bonds are structural) + # related attributes: bonds, element, atomtype, info, jigs # (not only + # '.jigs =', but '.jigs.remove' or '.jigs.append') + + # (we include info since it's used for repeat-unit correspondences in + # extrude; this is questionable) + + # (we include jigs since they're most like a form of structure, and in + # future might have physical effects, and since the jigs for pi bonds are + # structural) register_changedict( _changed_structure_Atoms, '_changed_structure_Atoms', ('bonds', 'element', 'atomtype', 'info', 'jigs') ) from model.global_model_changedicts import _changed_posn_Atoms - # tracks changes to atom._posn (not clear what it'll do when we can treat baseposn as defining state) + # tracks changes to atom._posn (not clear what it'll do when we can treat + # baseposn as defining state). # related attributes: _posn register_changedict( _changed_posn_Atoms, '_changed_posn_Atoms', ('_posn',) ) @@ -368,39 +409,45 @@ from model.global_model_changedicts import _changed_otherwise_Atoms register_changedict( _changed_otherwise_Atoms, '_changed_otherwise_Atoms', ('display', '_dnaBaseName') ) -# Notes (design scratch): -# for which Atom attrs is the attr value mutable in practice? bonds, jigs, maybe _posn (probably not). -# the rest could be handled by a setter in a new-style class, or by AtomBase -# and i wonder if it's simpler to just have one dict for all attrs... certainly it's simpler, so is it ok? -# The reason we have multiple dicts is so undo diff scanning is faster when (e.g.) lots of atoms change in _posn -# and nothing else (as after Minimize or movie playing or (for now) chunk moving). +# Notes (design scratch): for which Atom attrs is the attr value mutable in +# practice? bonds, jigs, maybe _posn (probably not). the rest could be handled +# by a setter in a new-style class, or by AtomBase and i wonder if it's +# simpler to just have one dict for all attrs... certainly it's simpler, so is +# it ok? The reason we have multiple dicts is so undo diff scanning is faster +# when (e.g.) lots of atoms change in _posn and nothing else (as after +# Minimize or movie playing or (for now) chunk moving). _Atom_global_dicts = [_changed_parent_Atoms, _changed_structure_Atoms, _changed_posn_Atoms, _changed_picked_Atoms, _changed_otherwise_Atoms] - # See also some code below class Atom, which registers these changedicts as being used with that class. - # That code has to occur after the class is defined, but we permit the above per-changedict registrations - # to come first so that they can help document the dicts near the top of the file. - # The dicts themselves needn't come first, since they're only looked up as module globals (or from external modules), - # but it's easier to read the code if they do. + # See also some code below class Atom, which registers these changedicts + # as being used with that class. That code has to occur after the class is + # defined, but we permit the above per-changedict registrations to come + # first so that they can help document the dicts near the top of the file. + # The dicts themselves needn't come first, since they're only looked up as + # module globals (or from external modules), but it's easier to read the + # code if they do. # == def Atom_prekill_prep(): #bruce 060328 """ - Prepare to kill some set of atoms (known to the caller) more efficiently than otherwise. - Return a value which the caller should pass to the _prekill method on all (and ONLY) those atoms, - before killing them. + Prepare to kill some set of atoms (known to the caller) more efficiently + than otherwise. Return a value which the caller should pass to the + _prekill method on all (and ONLY) those atoms, before killing them. - [#e Note: If we can ever kill atoms and chunks in the same operation, we'll need to revise some APIs - so they can all use the same value of _will_kill_count, if we want to make that most efficient.] + [#e Note: If we can ever kill atoms and chunks in the same operation, + we'll need to revise some APIs so they can all use the same value of + _will_kill_count, if we want to make that most efficient.] """ ###e this should be merged with similar code in class Node Utility._will_kill_count += 1 return Utility._will_kill_count class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): - #bruce 050610 renamed this from class atom, but most code still uses "atom" for now - # (so we have to assign atom = Atom, after this class definition, until all code has been revised) + #bruce 050610 renamed this from class atom, but most code still uses + # "atom" for now (so we have to assign atom = Atom, after this class + # definition, until all code has been revised) + # update, bruce 071113: I am removing that assignment below. See comment there. #bruce 080327 moved a lot of PAM-specific methods to mixin PAM_Atom_methods. """ @@ -430,14 +477,19 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): ghost = False #bruce 080529 _modified_valence = False #bruce 050502 info = None #bruce 050524 optim (can remove try/except if all atoms have this) - ## atomtype -- set when first demanded, or can be explicitly set using set_atomtype or set_atomtype_but_dont_revise_singlets - index = -1 #bruce 060311 add this as a precaution re bug 1661, and since it seems necessary in principle, - # given that we store it as undoable state, but (I guess, re that bug) don't always set it very soon - # after making an atom; -1 is also the correct value for an atom in a chunk but not yet indexed therein; - # in theory the value doesn't matter at all for a chunkless atom, but a removed atom (see Chunk.delatom) will have -1 here, - # so imitating that seems most correct. + ## atomtype -- set when first demanded, or can be explicitly set using + ## set_atomtype or set_atomtype_but_dont_revise_singlets + index = -1 + #bruce 060311 add this as a precaution re bug 1661, and since it seems + #necessary in principle, given that we store it as undoable state, but + #(I guess, re that bug) don't always set it very soon after making an + #atom; -1 is also the correct value for an atom in a chunk but not yet + #indexed therein; in theory the value doesn't matter at all for a + #chunkless atom, but a removed atom (see Chunk.delatom) will have -1 + #here, so imitating that seems most correct. - # _s_attr decls for state attributes -- children, parents, refs, bulky data, optional data [bruce 060223] + # _s_attr decls for state attributes -- children, parents, refs, bulky + # data, optional data [bruce 060223] _s_undo_specialcase = UNDO_SPECIALCASE_ATOM # This tells Undo what specialcase code to use for this class @@ -469,54 +521,76 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): _s_attr_bonds = S_CHILDREN _s_attr_molecule = S_PARENT # note: most direct sets of self.molecule are in chunk.py - # note: self.molecule is initially None. Later it's self's chunk. - # If self is then killed, it's _nullMol. Some buglike behavior might effectively - # kill or never-make-fully-alive self's chunk, without removing self from it - # (especially in nonstandardly-handled assys like the ones for the partlib). - # Finally, undoing to before self was created will cause its .molecule to become None again. - # In that state there is no direct way to figure out self's assy, - # but it might have one, since it might be in its undo_archive and be able - # to come back to life that way, by Redo. Or the redo stack might get cleared, - # but self might still be listed in some dicts in that undo_archive (not sure), - # and maybe in various global dicts (changedicts, glname dict, dna updater error dict, etc). - # This makes it hard to destroy self and all its storage, when self's assy is destroyed. - # To fix this, we might keep a dict in assy of all atoms ever in it, - # and/or keep a permanent _f_assy field in self. Not yet decided. Making _nullMol per-assy - # would not help unless we can always use it rather than None, but that's hard because - # atoms initially don't know assy, and Undo probably assumes the initial state of .molecule - # is None. So a lot of code would need analysis to fix that, whereas a new one-purpose - # system to know self's assy would be simpler. But it takes RAM and might not really be needed. - # The main potential need is to handle atoms found in changedicts with .molecule of None or _nullMol, - # to ask their assy if it's destroyed (updater should ignore atom) or not (updater should handle - # atom if it handles killed atoms). But do updaters ever need to handle killed atoms? - # [bruce 080219 comment] + + # note: self.molecule is initially None. Later it's self's chunk. If + # self is then killed, it's _nullMol. Some buglike behavior might + # effectively kill or never-make-fully-alive self's chunk, without + # removing self from it (especially in nonstandardly-handled assys + # like the ones for the partlib). Finally, undoing to before self was + # created will cause its .molecule to become None again. In that state + # there is no direct way to figure out self's assy, but it might have + # one, since it might be in its undo_archive and be able to come back + # to life that way, by Redo. Or the redo stack might get cleared, but + # self might still be listed in some dicts in that undo_archive (not + # sure), and maybe in various global dicts (changedicts, glname dict, + # dna updater error dict, etc). This makes it hard to destroy self and + # all its storage, when self's assy is destroyed. To fix this, we + # might keep a dict in assy of all atoms ever in it, and/or keep a + # permanent _f_assy field in self. Not yet decided. Making _nullMol + # per-assy would not help unless we can always use it rather than + # None, but that's hard because atoms initially don't know assy, and + # Undo probably assumes the initial state of .molecule is None. So a + # lot of code would need analysis to fix that, whereas a new + # one-purpose system to know self's assy would be simpler. But it + # takes RAM and might not really be needed. The main potential need is + # to handle atoms found in changedicts with .molecule of None or + # _nullMol, to ask their assy if it's destroyed (updater should ignore + # atom) or not (updater should handle atom if it handles killed + # atoms). But do updaters ever need to handle killed atoms? [bruce + # 080219 comment] assert ATOM_CHUNK_ATTRIBUTE_NAME == 'molecule' # must match this _s_attr_molecule decl attr name, # and all the atom.molecule refs in all files [bruce 071114] - _s_attr_jigs = S_CACHE # first i said S_REFS, but this is more efficient, and helps handle pi_bond_sp_chain.py's Jigs. + _s_attr_jigs = S_CACHE + # first i said S_REFS, but this is more efficient, and helps handle + # pi_bond_sp_chain.py's Jigs. + # [not sure if following comment written 060223 is obs as of 060224:] - # This means that restored state will unset the .jigs attr, for *all* atoms (???), and we'll have to recompute them somehow. - # The alg is easy (scan all jigs), but exactly how to organize it needs to be thought about. - # Is it worth thinking of Atom.jigs in general (not just re Undo) as a recomputable attribute? - # We could revise the incremental updaters to not worry if it's missing, - # and put in a __getattr__ which redid the entire model's atoms when it ran on any atom. - # (Just scan all jigs, and assume all atoms' .jigs are either missing or correct, and ignore correct ones.) - # But that approach would be wrong for pi_bond_sp_chain.py's Jigs since they would not be scanned (efficiently). - # So for them, they have to insist that if they exist, the atoms know about them. (Using a sister attr to .jigs?) - # (Or do we teach atoms how to look for them on nearby bonds? That's conceivable.) - # ... or if these fields are derived specifically from the atomsets of Jigs, then do we know which atoms to touch - # based on the manner in which Undo altered certain Jigs (i.e. does our update routine start by knowing the set of - # old and new values of all changed atomsets in Jigs)?? Certainly we could teach the diff-applyer to make that info - # available (using suitable attr decls so it knew it needed to)... but I don't yet see how this can work for pi_bond Jigs - # and for incremental Undo. - - #e we might want to add type decls for the bulky data (including the objrefs above), so it can be stored in compact arrays: - -#bruce 060322 zapping _s_attr_key = S_DATA decl -- should be unnecessary since .key never changes. ####@@@@ TEST -# NOTE: for using this for binary mmp files, it might be necessary -- review that when we have them. ###@@@ -## _s_attr_key = S_DATA # this is not yet related to Undo's concept of objkey (I think #k) [bruce 060223] + + # This means that restored state will unset the .jigs attr, for *all* + # atoms (???), and we'll have to recompute them somehow. The alg is + # easy (scan all jigs), but exactly how to organize it needs to be + # thought about. Is it worth thinking of Atom.jigs in general (not + # just re Undo) as a recomputable attribute? We could revise the + # incremental updaters to not worry if it's missing, and put in a + # __getattr__ which redid the entire model's atoms when it ran on any + # atom. (Just scan all jigs, and assume all atoms' .jigs are either + # missing or correct, and ignore correct ones.) But that approach + # would be wrong for pi_bond_sp_chain.py's Jigs since they would not + # be scanned (efficiently). So for them, they have to insist that if + # they exist, the atoms know about them. (Using a sister attr to + # .jigs?) (Or do we teach atoms how to look for them on nearby bonds? + # That's conceivable.) ... or if these fields are derived specifically + # from the atomsets of Jigs, then do we know which atoms to touch + # based on the manner in which Undo altered certain Jigs (i.e. does + # our update routine start by knowing the set of old and new values of + # all changed atomsets in Jigs)?? Certainly we could teach the + # diff-applyer to make that info available (using suitable attr decls + # so it knew it needed to)... but I don't yet see how this can work + # for pi_bond Jigs and for incremental Undo. + + #e we might want to add type decls for the bulky data (including the + #objrefs above), so it can be stored in compact arrays: + + #bruce 060322 zapping _s_attr_key = S_DATA decl -- should be unnecessary + #since .key never changes. ####@@@@ TEST + + # NOTE: for using this for binary mmp files, it might be necessary -- + # review that when we have them. ###@@@ + + ## _s_attr_key = S_DATA # this is not yet related to Undo's concept of objkey (I think #k) [bruce 060223] # storing .index as Undo state is no longer needed [bruce 060313] # note: a long comment removed on 070518 explained that when we had it, @@ -525,25 +599,33 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): _s_attr__posn = S_DATA #bruce 060308 rewrite _s_attr_element = S_DATA - # we'll want an "optional" decl on the following, so they're reset to class attr (or unset) when they equal it: + # we'll want an "optional" decl on the following, so they're reset to + # class attr (or unset) when they equal it: _s_attr_picked = S_DATA - _s_categorize_picked = 'selection' ##k this is noticed and stored, but I don't think it yet has any effect (??) [bruce 060313] + _s_categorize_picked = 'selection' + ##k this is noticed and stored, but I don't think it yet has any + ##effect (??) [bruce 060313] _s_attr_display = S_DATA _s_attr__dnaBaseName = S_DATA #bruce 080319 # decided to leave out _s_attr_ghost, for now [bruce 080530]: - ## _s_attr_ghost = S_DATA #bruce 080529; might not be needed (since no ops change this except on newly made atoms, so far) + ## _s_attr_ghost = S_DATA #bruce 080529; might not be needed + ## # (since no ops change this except on newly made atoms, so far) _s_attr_info = S_DATA - _s_attr__Atom__killed = S_DATA # Declaring (name-mangled) __killed seems needed just like for any other attribute... - # (and without it, reviving a dead atom triggered an assertfail, unsurprisingly) + _s_attr__Atom__killed = S_DATA + # Declaring (name-mangled) __killed seems needed just like for any + # other attribute... (and without it, reviving a dead atom triggered + # an assertfail, unsurprisingly) #e declare these later when i revise code to unset/redflt them most of the time: ###@@@ ## _picked_time = _picked_time_2 = -1 # note: atoms don't yet have individual colors, labels, names... - ###e need atomtype - hard part is when it's unset, might have to revise how we handle that, e.g. derive it from _hyb; - # actually, for pure scan, why is 'unset' hard to handle? i bet it's not. It just has no default value. - # It's down here under the 'optional' data since we should derive it from _hyb which is usually 'default for element'. + ###e need atomtype - hard part is when it's unset, might have to revise + #how we handle that, e.g. derive it from _hyb; actually, for pure scan, + #why is 'unset' hard to handle? i bet it's not. It just has no default + #value. It's down here under the 'optional' data since we should derive it + #from _hyb which is usually 'default for element'. _s_attr_atomtype = S_DATA @@ -613,28 +695,38 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # If it's a bug, do this in a later pass over atoms, I guess. # [bruce 080404] if self.molecule is None: - # new behavior as of 060409, needed to interact well with differential mash_attrs... - # i'm not yet fully comfortable with this (maybe we really need _nullMol in here??) ###@@@ + # new behavior as of 060409, needed to interact well with + # differential mash_attrs... i'm not yet fully comfortable with + # this (maybe we really need _nullMol in here??) ###@@@ print "bug (ignored): _undo_update on dead atom", self return - #bruce 060224 conservative guess -- invalidate everything we can find any other code in this file invalidating + #bruce 060224 conservative guess -- invalidate everything we can find + #any other code in this file invalidating for b in self.bonds: b.setup_invalidate() b.invalidate_bonded_mols() self.molecule.invalidate_attr('singlets') - self.molecule.invalidate_attr('externs') #bruce 070602: fix Undo bug after Make Crossover (crossovers.py) -- - # the bug was that bonds being (supposedly) deleted during this undo state-mashing would remain in mol.externs - # and continue to get drawn. I don't know why it didn't happen if bonds were also created on the same atoms - # (or perhaps on any atoms in the same chunk), but presumably that happened to invalidate externs in some other way. - # As for why the bug went uncaught until now, maybe no other operation creates external bonds without also - # deleting bonds on the same atoms (which evidently prevents the bug, as mentioned). For details of what was - # tried and how it affected what happened, see crossovers.py cvs history circa now. + self.molecule.invalidate_attr('externs') + #bruce 070602: fix Undo bug after Make Crossover (crossovers.py) + # -- the bug was that bonds being (supposedly) deleted during this + # undo state-mashing would remain in mol.externs and continue to + # get drawn. I don't know why it didn't happen if bonds were also + # created on the same atoms (or perhaps on any atoms in the same + # chunk), but presumably that happened to invalidate externs in + # some other way. + # As for why the bug went uncaught until now, maybe no other + # operation creates external bonds without also deleting bonds on + # the same atoms (which evidently prevents the bug, as mentioned). + # For details of what was tried and how it affected what happened, + # see crossovers.py cvs history circa now. self.molecule._f_lost_externs = True self.molecule._f_gained_externs = True self._changed_structure() self.changed() posn = self.posn() - self.setposn( posn - V(1,1,1) ) # i hope it doesn't optimize for posn being unchanged! just in case, set wrong then right + self.setposn( posn - V(1,1,1) ) + # i hope it doesn't optimize for posn being unchanged! just in + # case, set wrong then right self.setposn( posn ) # .picked might change... always recompute selatoms in external code ####@@@@ self.molecule.changeapp(1) @@ -647,18 +739,22 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): def __init__(self, sym, where, mol = None): #bruce 060612 let mol be left out """ - Create an Atom of element sym - (e.g. 'C' -- sym can be an element, atomtype, or element-symbol, or another atom to copy these from) - at location 'where' (e.g. V(36, 24, 36)) - belonging to molecule mol (can be None or missing). + Create an Atom of element sym (e.g. 'C' -- sym can be an element, + atomtype, or element-symbol, or another atom to copy these from) at + location 'where' (e.g. V(36, 24, 36)) belonging to molecule mol (can + be None or missing). + Atom initially has no real or open bonds, and default hybridization type. """ - # note: it's not necessary to track changes to self's attrs (in e.g. _changed_parent_Atoms) during __init__. [bruce 060322] + # note: it's not necessary to track changes to self's attrs (in e.g. + # _changed_parent_Atoms) during __init__. [bruce 060322] AtomBase.__init__(self) self.key = atKey.next() # unique key for hashing and/or use as a dict key; # also used in str(self) - _changed_parent_Atoms[self.key] = self # since this dict tracks all new atoms (i.e. liveness of atoms) (among other things) + _changed_parent_Atoms[self.key] = self + # since this dict tracks all new atoms (i.e. liveness of atoms) + # (among other things) # done later, since our assy is not yet known: [bruce 080220] ## self._glname = assy.alloc_my_glselect_name( self) @@ -671,14 +767,17 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # figure out atomtype to assume; atype = None means default atomtype for element will be set. - # [bruce 050707 revised this; previously the default behavior was to set no atomtype now - # (picking one when first asked for), not to set the default atomtype now. This worked ok - # when the atomtype guessed later was also the default one, but not once that was changed - # to guess it based on the number of bonds (when first asked for), since some old code - # would ask for it before creating all the bonds on a new atom. Now, the "guessing atomtype - # from bonds" behavior is still needed in some cases, and is best asked for by leaving it unset, - # but that is done by a special method or init arg ###doc, since it should no longer be what this init - # method normally does. BTW the docstring erroneously claimed we were already setting default atomtype.] + # [bruce 050707 revised this; previously the default behavior was to + # set no atomtype now (picking one when first asked for), not to set + # the default atomtype now. This worked ok when the atomtype guessed + # later was also the default one, but not once that was changed to + # guess it based on the number of bonds (when first asked for), since + # some old code would ask for it before creating all the bonds on a + # new atom. Now, the "guessing atomtype from bonds" behavior is still + # needed in some cases, and is best asked for by leaving it unset, but + # that is done by a special method or init arg ###doc, since it should + # no longer be what this init method normally does. BTW the docstring + # erroneously claimed we were already setting default atomtype.] #bruce 080327 revised this to not use try/except routinely # (possible optimization, and for clarity) @@ -699,7 +798,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # to allow, but is nim. [bruce 080514 comment] assert 0, "can't initialize Atom.element from %r" % (sym,) - #e could assert self.element is now an Elem, but don't bother -- if not, we'll find out soon enough + #e could assert self.element is now an Elem, but don't bother -- + # if not, we'll find out soon enough if atype is not None: assert atype.element is self.element # trivial in one of these cases, should improve #e @@ -740,7 +840,7 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): pass # (optional debugging code to show which code creates bad atoms:) ## if debug_flags.atom_debug: - ## self._source = compact_stack() + ## self._f_source = compact_stack() self.set_atomtype_but_dont_revise_singlets( atype) return # from Atom.__init__ @@ -782,7 +882,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # leave old assy, if any if self._f_assy is not None: - print "%r._f_set_assy: leaving old assy %r, for new one %r. Implem of this case is untested." % \ + print "%r._f_set_assy: leaving old assy %r, for new one %r. " \ + "Implem of this case is untested." % \ (self, self._f_assy, assy) self._f_assy.dealloc_my_glselect_name( self, self._glname) @@ -823,13 +924,16 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): def _undo_aliveQ(self, archive): #bruce 060406 """ - Would this (Atom) object be picked up as a child object in a (hypothetical) complete scan of children - (including change-tracked objects) by the given undo_archive (or, optional to consider, by some other one)? - The caller promises it will only call this when it's just done ... ###doc + Would this (Atom) object be picked up as a child object in a + (hypothetical) complete scan of children (including change-tracked + objects) by the given undo_archive (or, optional to consider, by some + other one)? The caller promises it will only call this when it's just + done ... ###doc """ - # This implem is only correct because atoms can only appear in the archive's current state - # if they are owned by chunks which appear there. - # (That's only true because we fix or don't save invalid _hotspots, since those can be killed bondpoints.) + # This implem is only correct because atoms can only appear in the + # archive's current state if they are owned by chunks which appear + # there. (That's only true because we fix or don't save invalid + # _hotspots, since those can be killed bondpoints.) mol = self.molecule return archive.childobj_liveQ(mol) and mol.atoms.has_key(self.key) @@ -885,8 +989,10 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): _changed_structure_Atoms[self.key] = self return - # For each entry in this dictionary, add a context menu command on atoms of the key element type - # allowing transmutation to each of the element types in the value list. + # For each entry in this dictionary, add a context menu command on atoms + # of the key element type allowing transmutation to each of the element + # types in the value list. + # (bruce 070412 addition: if the selected atoms are all one element, and # one of those is the context menu target, make this command apply to all # those selected atoms. @@ -976,8 +1082,10 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): if (transmute_entries.has_key(fromSymbol)): #bruce 070412 (enhancing EricM's recent new feature): # If unpicked, do it for just this atom; - # If picked, do it for all picked atoms, but only if they are all the same element. - # (But if they're not, still offer to do it for just this atom, clearly saying so if possible.) + # If picked, do it for all picked atoms, + # but only if they are all the same element. + # (But if they're not, still offer to do it for + # just this atom, clearly saying so if possible.) if self.picked: selatoms = self.molecule.assy.selatoms # there ought to be more direct access to this doall = True @@ -1013,9 +1121,11 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): #e could also say the number of atoms command = ( lambda arg1 = None, arg2 = None, atom = self, newElement = newElement: atom.Transmute_selection(newElement) ) - # Kluge: locate that command method on atom, used for access to selatoms, - # even though it's not defined to operate on atom (tho it does in this case). - # One motivation is to ease the upcoming emergency merge by not modifying more files/code + # Kluge: locate that command method on atom, used + # for access to selatoms, even though it's not + # defined to operate on atom (tho it does in this + # case). One motivation is to ease the upcoming + # emergency merge by not modifying more files/code # than necessary. else: cmdname = "Transmute this atom to %s" % toSymbol @@ -1069,18 +1179,28 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): def destroy(self): #bruce 060322 (not yet called) ###@@@ """ [see comments in Node.destroy or perhaps StateMixin.destroy] - Note: it should be legal to call this multiple times, in any order w/ other objs' destroy methods. - SEMANTICS ARE UNCLEAR -- whether it should destroy bonds in self.bonds (esp in light of rebond method). - See comments in assy_clear by bruce 060322 (the "misguided" ones, written as if that was assy.destroy, which it's not). - """ - # If this proves inefficient, we can dispense with most of it, since glselect dict can be made weak-valued - # (and will probably need to be anyway, for mmkit library part atoms), - # and change-tracking dicts (_Atom_global_dicts) are frequently cleared, and subscribers will ignore objects - # from destroyed assys. So it's only important here if we destroy atoms before their assy is destroyed - # (e.g. when freeing from redo or old undo diffs), and it's probably not enough for that anyway (not thought through). - # Ideally we'd remove cycles, not worry about transient refs in change-trackers, make change-trackers robust - # to being told about destroyed atoms, and that would be enough. Not yet sure whether that's practical. - # [bruce 060327 comment] + + @note: it should be (but may not now be) legal to call this multiple + times, in any order w/ other objs' destroy methods. + + @warning: SEMANTICS ARE UNCLEAR -- whether it should destroy bonds in + self.bonds (esp in light of rebond method). + + @see: comments in assy_clear by bruce 060322 (the "misguided" ones, + written as if that was assy.destroy, which it's not). + """ + # If this proves inefficient, we can dispense with most of it, since + # glselect dict can be made weak-valued (and will probably need to be + # anyway, for mmkit library part atoms), and change-tracking dicts + # (_Atom_global_dicts) are frequently cleared, and subscribers will + # ignore objects from destroyed assys. So it's only important here if + # we destroy atoms before their assy is destroyed (e.g. when freeing + # from redo or old undo diffs), and it's probably not enough for that + # anyway (not thought through). + # Ideally we'd remove cycles, not worry about transient refs in + # change-trackers, make change-trackers robust to being told about + # destroyed atoms, and that would be enough. Not yet sure whether + # that's practical. [bruce 060327 comment] if self._glname: #bruce 080917 revised this entire statement (never tested, before or after) assy = self._f_assy ###k if assy: @@ -1089,14 +1209,18 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): del self._glname key = self.key for dict1 in _Atom_global_dicts: - #e even this might be done by StateMixin if we declare these dicts to it for the class - # (but we'd have to tell it what key we use in them, e.g. provide a method or attr for that - # (like .key? no, needs _s_ prefix to avoid accidental definition)) + #e even this might be done by StateMixin if we declare these dicts + #to it for the class (but we'd have to tell it what key we use in + #them, e.g. provide a method or attr for that (like .key? no, + #needs _s_ prefix to avoid accidental definition)) dict1.pop(key, None) # remove self, if it's there - ###e we need to also tell the subscribers to those dicts that we're being destroyed, I think + ###e we need to also tell the subscribers to those dicts that + ###we're being destroyed, I think + # is the following in a superclass (StateMixin) method?? ###k self.__dict__.clear() ###k is this safe??? - ## self.bonds = self.jigs = self.molecule = self.atomtype = self.element = self.info = None # etc... + ## self.bonds = self.jigs = self.molecule = \ + ## self.atomtype = self.element = self.info = None # etc... return def unset_atomtype(self): #bruce 050707 @@ -1132,10 +1256,16 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): @warning: This is only correct for a new atom if it has already been given all its bonds (real or open). """ - #bruce 050702 revised this; 050707 using it much less often (only on special request ###doc what) - ###@@@ Bug: This does not yet [050707] guess correctly for all bond patterns; e.g. it probably never picks N/sp2(graphitic). - # That means there is presently no way to save and reload that atomtype in an mmp file, and only a "direct way" - # (specifying it as an atomtype, not relying on inferring from bonds) would work unless this bug is fixed here. + #bruce 050702 revised this; 050707 using it much less often + # (only on special request ###doc what) + + ###@@@ Bug: This does not yet [050707] guess correctly for all bond + #patterns; e.g. it probably never picks N/sp2(graphitic). That means + #there is presently no way to save and reload that atomtype in an mmp + #file, and only a "direct way" (specifying it as an atomtype, not + #relying on inferring from bonds) would work unless this bug is fixed + #here. + ###@@@ (need to report this bug) if self.__killed: # bruce 071018 new bug check and new mitigation (return default atomtype) @@ -1147,43 +1277,57 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # (I think the following cond (using self.element rather than elt) # is correct even when elt is passed -- bruce 050707) if self.element.atomtypes[0].numbonds != 0: # not a bug for noble gases! - if 1: ## or env.once_per_event("reguess_atomtype warning"): #bruce 060720, only warn once per user event - print_compact_stack( - ## "atom_debug: warning (once per event): reguess_atomtype(%s) sees %s with no bonds -- probably a bug: " % \ - "warning: reguess_atomtype(%s) sees non-killed %s with no bonds -- probably a bug: " % \ - (elt, self) ) + print_compact_stack( + "warning: reguess_atomtype(%s) sees non-killed %s with no bonds -- probably a bug: " % \ + (elt, self) ) return self.best_atomtype_for_numbonds(elt = elt) - def set_atomtype_but_dont_revise_singlets(self, atomtype): ####@@@@ should merge with set_atomtype; perhaps use more widely + def set_atomtype_but_dont_revise_singlets(self, atomtype): + ####@@@@ should merge with set_atomtype; perhaps use more widely """ #doc; atomtype is None means use default atomtype """ - atomtype = self.element.find_atomtype( atomtype) # handles all forms of the request; exception if none matches + atomtype = self.element.find_atomtype( atomtype) + # handles all forms of the request; exception if none matches assert atomtype.element is self.element # [redundant with find_atomtype] self.atomtype = atomtype - self._changed_structure() #bruce 050627; note: as of 050707 this is always called during Atom.__init__ + self._changed_structure() + #bruce 050627; note: as of 050707 this is always called during Atom.__init__ ###e need any more invals or updates for this method?? ###@@@ return def set_atomtype(self, atomtype, always_remake_bondpoints = False): """ [public method; not super-fast] - Set this atom's atomtype as requested, and do all necessary invalidations or updates, - including remaking our singlets as appropriate, and [###@@@ NIM] invalidating or updating bond valences. - It's ok to pass None (warning: this sets default atomtype even if current one is different!), - atomtype's name (specific to self.element) or fullname, or atomtype object. ###@@@ also match to fullname_for_msg()??? ###e + + Set this atom's atomtype as requested, and do all necessary + invalidations or updates, including remaking our singlets as + appropriate, and [###@@@ NIM] invalidating or updating bond valences. + + It's ok to pass None (warning: this sets default atomtype even if + current one is different!), atomtype's name (specific to self.element) + or fullname, or atomtype object. ###@@@ also match to + fullname_for_msg()??? ###e + The atomtype's element must match the current value of self.element -- we never change self.element (for that, see mvElement). - Special case: if new atomtype would be same as existing one (and that is already set), do nothing - (rather than killing and remaking singlets, or even correcting their positions), - unless always_remake_bondpoints is true. [not sure if this will be used in atomtype-setting menu-cmds ###@@@] - """ - # Note: mvElement sets self.atomtype directly; if it called this method, we'd have infrecur! - atomtype = self.element.find_atomtype( atomtype) # handles all forms of the request; exception if none matches - assert atomtype.element is self.element # [redundant with find_atomtype] #e or transmute if not?? + + Special case: if new atomtype would be same as existing one (and that + is already set), do nothing (rather than killing and remaking + singlets, or even correcting their positions), unless + always_remake_bondpoints is true. [not sure if this will be used in + atomtype-setting menu-cmds ###@@@] + """ + # Note: mvElement sets self.atomtype directly; if it called this + # method, we'd have infrecur! + atomtype = self.element.find_atomtype( atomtype) + # handles all forms of the request; exception if none matches + assert atomtype.element is self.element + # [redundant with find_atomtype] #e or transmute if not?? if always_remake_bondpoints or (self.atomtype_iff_set() is not atomtype): - self.direct_Transmute( atomtype.element, atomtype ) ###@@@ not all its needed invals/updates are implemented yet + self.direct_Transmute( atomtype.element, atomtype ) + ###@@@ not all its needed invals/updates are implemented yet # note: self.atomtype = atomtype is done in direct_Transmute when it calls mvElement return @@ -1233,19 +1377,22 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # note: print_compact_traceback is not informative -- the call stack might be, tho it's long. print_compact_stack("bug: atom position overflow for %r; setting position to 0,0,0: " % self) ##e history message too? only if we can prevent lots of them from coming out at once. - res = V(0,0,0) ##e is there any better choice of position for this purpose? e.g. a small random one, so it's unique? + res = V(0,0,0) ##e is there any better choice of position for this purpose? + # e.g. a small random one, so it's unique? self.setposn(res) # I hope this is safe here... we need the invals it does. return res def sim_posn(self): #bruce 060111 """ - Return our posn, as the simulator should see it -- same as posn except for Singlets, - which should pretend to be H and correct their distance from base atom accordingly. - Should work even for killed atoms (e.g. singlets with no bonds). + Return our posn, as the simulator should see it -- same as posn except + for Singlets, which should pretend to be H and correct their distance + from base atom accordingly. Should work even for killed atoms (e.g. + singlets with no bonds). - Note that if this is used on a corrected singlet position derived from a simulated H position - (as in the 060111 approximate fix of bug 1297), it's only approximate, since the actual H position - might not have been exactly its equilibrium position. + Note that if this is used on a corrected singlet position derived from + a simulated H position (as in the 060111 approximate fix of bug 1297), + it's only approximate, since the actual H position might not have been + exactly its equilibrium position. """ if self.element is Singlet and len(self.bonds) == 1: oa = self.bonds[0].other(self) # like self.singlet_neighbor() but fewer asserts @@ -1271,12 +1418,16 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): #new code from 050513: mol = self.molecule basepos = mol.__dict__.get('basepos') #bruce 050513 - if basepos is not None: ##[bruce 060308 zapped:]## and self.xyz == 'no': #bruce 050516 bugfix: fix sense of comparison to 'no' + if basepos is not None: + ##[bruce 060308 zapped:]## and self.xyz == 'no': + #bruce 050516 bugfix: fix sense of comparison to 'no' return basepos[self.index] - # note: since mol.basepos exists, mol.atlist does, so self.index is a valid index into both of them - # [bruce 060313 comment] + # note: since mol.basepos exists, mol.atlist does, so + # self.index is a valid index into both of them [bruce 060313 + # comment] # fallback to slower code from 041201: - return mol.quat.unrot(self.posn() - mol.basecenter) # this inlines mol.abs_to_base( self.posn() ) [bruce 060411 comment] + return mol.quat.unrot(self.posn() - mol.basecenter) + # this inlines mol.abs_to_base( self.posn() ) [bruce 060411 comment] def setposn(self, pos): """ @@ -1296,8 +1447,9 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): mol.changed_atom_posn() # also invalidate the bonds or jigs which depend on our position. - #e (should this be a separate method -- does anything else need it?) - # note: the comment mentions jigs, but this code doesn't alert them to the move. Bug?? [bruce 070518 question] + # (review: should this be a separate method -- does anything else need + # it?) note: the comment mentions jigs, but this code doesn't alert + # them to the move. Bug?? [bruce 070518 question] for b in self.bonds: b.setup_invalidate() return # from setposn @@ -1308,10 +1460,12 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): if self.jigs: #bruce 050718 added this, for bonds code for jig in self.jigs[:]: jig.moved_atom(self) - #e note: this does nothing for most kinds of jigs, - # so in theory we might optim by splitting self.jigs into two lists; - # however, there are other change methods for atoms in jigs (maybe changed_structure?), - # so it's not clear how many different lists are needed, so it's unlikely the complexity is justified. + #e note: this does nothing for most kinds of jigs, so in + #theory we might optim by splitting self.jigs into two lists; + #however, there are other change methods for atoms in jigs + #(maybe changed_structure?), so it's not clear how many + #different lists are needed, so it's unlikely the complexity + #is justified. return # from setposn_no_chunk_or_bond_invals def adjBaggage(self, atom, nupos): @@ -1476,7 +1630,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): if debug_pref("report bond_geometry_error_string set or clear?", Choice_boolean_False, prefs_key = True ): - print "fyi: check_bond_geometry(%r) set or cleared error string %r" % (self, error_string) + print "fyi: check_bond_geometry(%r) set or cleared error string %r" % \ + (self, error_string) self.bond_geometry_error_string = error_string pass if self.bond_geometry_error_string: @@ -1484,7 +1639,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # into the chunk display list or not (for explanation, see comment # in _f_invalidate_neighbor_geom) draw_externally = not not self.has_external_bonds() - self._f_draw_bond_geometry_error_indicator_externally = draw_externally # this attr might never be used + self._f_draw_bond_geometry_error_indicator_externally = draw_externally + # this attr might never be used if external == -1 or external == draw_externally: return self.bond_geometry_error_string return "" @@ -1606,7 +1762,9 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): pass return glname - def draw(self, glpane, dispdef, col, level, special_drawing_handler = None, special_drawing_prefs = USE_CURRENT): + def draw(self, glpane, dispdef, col, level, + special_drawing_handler = None, + special_drawing_prefs = USE_CURRENT): """ Draw this atom (self), using an appearance which depends on whether it is picked (selected) @@ -1671,7 +1829,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): pos = self.baseposn() if disp == diTUBES: - pickedrad = drawrad * 1.8 # this code snippet is now shared between draw and draw_in_abs_coords [bruce 060315] + pickedrad = drawrad * 1.8 + # this code snippet is now shared between draw and draw_in_abs_coords [bruce 060315] else: pickedrad = drawrad * 1.1 @@ -1759,13 +1918,16 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): and making it easier for L{draw_atom_sphere} to fallback to its default style when those conditions fail. """ - # WARNING: various routines make use of this return value in different ways, - # but these ways are not independent (e.g. one might draw a cone and one might estimate its size), - # so changes in any of the uses need to be reviewed for possibly needing changes in the others. [bruce 070409] + # WARNING: various routines make use of this return value in different + # ways, but these ways are not independent (e.g. one might draw a cone + # and one might estimate its size), so changes in any of the uses need + # to be reviewed for possibly needing changes in the others. [bruce + # 070409] if self.element is Singlet and len(self.bonds) == 1: # self is a bondpoint if debug_pref("draw bondpoints as stubs", Choice_boolean_False, prefs_key = True): - # current implem has cosmetic bugs (details commented there), so don't say non_debug = True + # current implem has cosmetic bugs (details commented there), + # so don't say non_debug = True return 'bondpoint-stub' #k this might need to correspond with related code in Bond.draw if self.element.bonds_can_be_directional: #bruce 070415, correct end-arrowheads # note: as of mark 071014, this can happen for self being a Singlet @@ -1820,10 +1982,15 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): return 'arrowhead-out' else: return 'do not draw' - #e REVIEW: does Bond.draw need to be updated due to this, if "draw bondpoints as stubs" is True? - #e REVIEW: Do we want to draw even an isolated Pe (with bondpoint) as a cone, in case it's in MMKit, - # since it usually looks like a cone when it's correctly used? Current code won't do that. - #e Maybe add option to draw the dir == 0 case too, to point out you ought to propogate the direction + #e REVIEW: does Bond.draw need to be updated due to this, if "draw + #bondpoints as stubs" is True? + + #e REVIEW: Do we want to draw even an isolated Pe (with bondpoint) + #as a cone, in case it's in MMKit, since it usually looks like a + #cone when it's correctly used? Current code won't do that. + + #e Maybe add option to draw the dir == 0 case too, to point out + #you ought to propogate the direction pass return "" @@ -1868,21 +2035,24 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # but sometimes the bond cyls project out beyond the bp cyls # after repositioning bondpoints, or for N(sp2(graphitic)), # and it looks bad for double bonds, - # and sometimes the highlighting cylfaces mix with the non-highlighted ones, - # as if same depth value. ###@@@ + # and sometimes the highlighting cylfaces mix with the + # non-highlighted ones, as if same depth value. ###@@@ other = self.singlet_neighbor() if abs_coords: otherpos = other.posn() else: otherpos = other.baseposn() - # note: this is only correct since self and other are guaranteed to belong to the same chunk -- - # if they didn't, their baseposns (pos and otherpos) would be in different coordinate systems. + # note: this is only correct since self and other are + # guaranteed to belong to the same chunk -- if they + # didn't, their baseposns (pos and otherpos) would be in + # different coordinate systems. rad = other.highlighting_radius(dispdef) # ok to pass None out = norm(pos - otherpos) buried = max(0, rad - vlen(pos - otherpos)) inpos = pos - 0.015 * out outpos = pos + (buried + 0.015) * out # be sure we're visible outside a big other atom - drawcylinder(color, inpos, outpos, drawrad, 1) #e see related code in Bond.draw; drawrad is slightly more than the bond rad + drawcylinder(color, inpos, outpos, drawrad, 1) + #e see related code in Bond.draw; drawrad is slightly more than the bond rad elif style.startswith('arrowhead-'): #arrowColor will be changed later arrowColor = color @@ -1893,9 +2063,9 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): bond = self.strand_end_bond() other = bond.other(self) otherdir = 1 - #Following implements custom arrowhead colors for the 3' and 5' end - #(can be changed using Preferences > Dna page) Feature implemented - #for Rattlesnake v1.0.1 + # Following implements custom arrowhead colors for the 3' and 5' end + # (can be changed using Preferences > Dna page). Feature implemented + # for Rattlesnake v1.0.1. bool_custom_arrowhead_color = special_drawing_prefs[ useCustomColorForFivePrimeArrowheads_prefs_key] if bool_custom_arrowhead_color and not abs_coords: @@ -1906,9 +2076,9 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): bond = self.strand_end_bond() other = bond.other(self) otherdir = -1 - #Following implements custom arrowhead colors for the 3' and 5' end - #(can be changed using Preferences > Dna page) Feature implemented - #for Rattlesnake v1.0.1 + # Following implements custom arrowhead colors for the 3' and 5' end + # (can be changed using Preferences > Dna page). Feature implemented + # for Rattlesnake v1.0.1. bool_custom_arrowhead_color = special_drawing_prefs[ useCustomColorForThreePrimeArrowheads_prefs_key] if bool_custom_arrowhead_color and not abs_coords: @@ -1922,8 +2092,11 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): otherpos = other.baseposn() # this would be wrong if it's in a different chunk! else: otherpos = self.molecule.abs_to_base(other.posn()) - ###BUG: this becomes wrong if the chunks move relative to each other! But we don't get updated then. ###FIX - ## color = gray # to indicate the direction is suspicious and might become invalid (for now) + ###BUG: this becomes wrong if the chunks move relative to + ###each other! But we don't get updated then. ###FIX + + ## color = gray # to indicate the direction is suspicious and + ## # might become invalid (for now) out = norm(otherpos - pos) * otherdir # Set the axis and arrow radius. @@ -1943,12 +2116,16 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): axis = out * drawrad arrowRadius = drawrad * 2 - # the following cone dimensions enclose the original sphere (and therefore the bond-cylinder end too) - # (when axis and arrowRadius have their default values above -- not sure if this remains true after [mark 071014]): - # cone base at pos - axis, radius = 2 * drawrad, cone midplane (radius = drawrad) at pos + axis, - # thus cone tip at pos + 3 * axis. - # WARNING: this cone would obscure the wirespheres, except for special cases in self.draw_wirespheres(). - # If you make the cone bigger you might need to change that code too. + # the following cone dimensions enclose the original sphere (and + # therefore the bond-cylinder end too) (when axis and arrowRadius + # have their default values above -- not sure if this remains true + # after [mark 071014]): cone base at pos - axis, radius = 2 * + # drawrad, cone midplane (radius = drawrad) at pos + axis, thus + # cone tip at pos + 3 * axis. + + # WARNING: this cone would obscure the wirespheres, except for + # special cases in self.draw_wirespheres(). If you make the cone + # bigger you might need to change that code too. drawpolycone(arrowColor, [[pos[0] - 2 * axis[0], @@ -2000,9 +2177,11 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): def draw_wirespheres(self, glpane, disp, pos, pickedrad, special_drawing_prefs = USE_CURRENT): #bruce 060315 split this out of self.draw so I can add it to draw_in_abs_coords if self._draw_atom_style(special_drawing_prefs = special_drawing_prefs).startswith('arrowhead-'): - # compensate for the cone (drawn by draw_atom_sphere in this case) being bigger than the sphere [bruce 070409] + # compensate for the cone (drawn by draw_atom_sphere + # in this case) being bigger than the sphere [bruce 070409] pickedrad *= debug_pref("Pe pickedrad ratio", Choice([1.8, 1.9, 1.7, 1.0])) #### - if self.picked: # (do this even if disp == diINVISIBLE or diLINES [bruce comment 050825]) + if self.picked: + # (do this even if disp == diINVISIBLE or diLINES [bruce comment 050825]) #bruce 041217 experiment: show valence errors for picked atoms by # using a different color for the wireframe. # (Since Transmute operates on picked atoms, and leaves them picked, @@ -2014,7 +2193,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): else: # russ 080530: Changed to pref from PickedColor constant (blue). color = env.prefs[selectionColor_prefs_key] - drawwiresphere(color, pos, pickedrad) ##e worry about glname hit test if atom is invisible? [bruce 050825 comment] + drawwiresphere(color, pos, pickedrad) + ##e worry about glname hit test if atom is invisible? [bruce 050825 comment] #bruce 050806: check valence more generally, and not only for picked atoms. self.draw_error_wireframe_if_needed(glpane, disp, pos, pickedrad, external = False, @@ -2035,9 +2215,11 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): return # The calling code in draw_wirespheres only checks for number of bonds. # Now that we have higher-order bonds, we also need to check valence more generally. - # The check for glpane class is a kluge to prevent this from showing in thumbviews: should remove ASAP. + # The check for glpane class is a kluge to prevent this from showing in thumbviews: + # should remove ASAP. #####@@@@@ need to do this in atom.getinfo(). - #e We might need to be able to turn this [what?] off by a preference setting; or, only do it in Build mode. + #e We might need to be able to turn this [what?] off by a preference setting; + # or, only do it in Build mode. # Don't check prefs until we know we need them, to avoid needless # gl_update when user changes prefs value. if not glpane.should_draw_valence_errors(): @@ -2113,14 +2295,17 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): @see: _dna_updater__error (not covered by this; maybe it should be) """ if self.element is Singlet: - # should be correct, but this case won't be used as of 041217 [probably no longer needed even if used -- 050511] + # should be correct, but this case won't be used as of 041217 + # [probably no longer needed even if used -- 050511] numbonds = 1 else: numbonds = self.atomtype.numbonds - return numbonds != len(self.bonds) ##REVIEW: this doesn't check bond valence at all... should it?? + return numbonds != len(self.bonds) + ##REVIEW: this doesn't check bond valence at all... should it?? def bad_valence(self, external = -1): - #bruce 050806; should review uses (or inlinings) of self.bad() to see if they need this too ##REVIEW + #bruce 050806; should review uses (or inlinings) of self.bad() + # to see if they need this too ##REVIEW #bruce 080406 added external option """ is this atom's valence clearly wrong, considering @@ -2128,8 +2313,10 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): @param external: see docstring of self.check_bond_geometry(). """ - # WARNING: keep the code of self.bad_valence() and self.bad_valence_explanation() in sync! - #e we might optimize this by memoizing it (in a public attribute), and letting changes to any bond invalidate it. + #WARNING: keep the code of self.bad_valence() and + #self.bad_valence_explanation() in sync! e we might optimize this by + #memoizing it (in a public attribute), and letting changes to any bond + #invalidate it. # REVIEW: see comments in bad_valence_explanation # NOTE: to include check_bond_geometry, we might need an "external" arg to pass it bonds = self.bonds @@ -2225,9 +2412,9 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): def deficient_valence(self): #bruce 051215 """ - If this atom clearly wants more valence (based on existing bond types), - return the minimum amount it needs (as an int or float valence number, NOT as a v6). - Otherwise return 0. + If this atom clearly wants more valence (based on existing bond + types), return the minimum amount it needs (as an int or float valence + number, NOT as a v6). Otherwise return 0. """ minv_junk, maxv = self.min_max_actual_valence() want_valence = self.atomtype.valence @@ -2283,32 +2470,41 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): #bruce 070409 bugfix (draw_atom_sphere); important if it's really a cone return - def draw_in_abs_coords(self, glpane, color, useSmallAtomRadius = False): #bruce 050610 - ###@@@ needs to be told whether or not to "draw as selatom"; now it does [i.e. it's misnamed] - """ - Draw this atom in absolute (world) coordinates, - using the specified color (ignoring the color it would naturally be drawn with). - See code comments about radius and display mode (current behavior might not be correct or optimal). - This is only called for special purposes related to mouseover-highlighting, - and should be renamed to reflect that, since its behavior can and should be specialized - for that use. (E.g. it doesn't happen inside display lists; and it need not use glName at all.) - In this case (Atom), this method (unlike the main draw method) will also - draw self's bond, provided self is a singlet with a bond which gets drawn, so that for an "open bond" - it draws the entire thing (bond plus bondpoint). In order for this to work, - the bond must (and does, in current code) borrow the glname - of self whenever it draws itself (with any method). - (This is only possible because bonds have at most one bondpoint.) + def draw_in_abs_coords(self, glpane, color, useSmallAtomRadius = False): """ + Draw this atom in absolute (world) coordinates, using the specified + color (ignoring the color it would naturally be drawn with). See code + comments about radius and display mode (current behavior might not be + correct or optimal). + + This is only called for special purposes related to + mouseover-highlighting, and should be renamed to reflect that, since + its behavior can and should be specialized for that use. (E.g. it + doesn't happen inside display lists; and it need not use glName at + all.) + + In this case (Atom), this method (unlike the main draw method) will + also draw self's bond, provided self is a singlet with a bond which + gets drawn, so that for an "open bond" it draws the entire thing (bond + plus bondpoint). In order for this to work, the bond must (and does, + in current code) borrow the glname of self whenever it draws itself + (with any method). (This is only possible because bonds have at most + one bondpoint.) + """ + #bruce 050610 + # todo: needs to be told whether or not to "draw as selatom"; + # for now it always does [i.e. it's misnamed] if self.__killed: return # I hope this is always ok... level = self.molecule.assy.drawLevel # this doesn't work if atom has been killed! pos = self.posn() - ###@@@ remaining code might or might not be correct (issues: larger radius, display-mode independence) + ###@@@ remaining code might or might not be correct + # (issues: larger radius, display-mode independence) if useSmallAtomRadius: drawrad = self.radius_for_chunk_highlighting() - # review: does this need to be bigger when self.bond_geometry_error_string - # to make tooltip work on self? + # review: does this need to be bigger when + # self.bond_geometry_error_string to make tooltip work on self? else: drawrad = self.highlighting_radius() # slightly larger than normal drawing radius if self.bond_geometry_error_string: @@ -2316,39 +2512,52 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): ## drawsphere(color, pos, drawrad, level) self.draw_atom_sphere(color, pos, drawrad, level, None, abs_coords = True) # always draw, regardless of display mode - #bruce 050825 comment: it's probably incorrect to do this even for invisible atoms. - # This probably caused the "highlighting part" of bug 870, but bug 870 has been fixed - # by other changes today, but this still might cause other bugs of highlighting - # otherwise-invisible atoms. Needs review. ###@@@ - # (Indirectly related: drawwiresphere acts like drawsphere for hit-test purposes.) - if len(self.bonds) == 1 and self.element is Singlet: #bruce 050708 new feature - bond is part of self for highlighting + + #bruce 050825 comment: it's probably incorrect to do this even for + #invisible atoms. This probably caused the "highlighting part" of + #bug 870, but bug 870 has been fixed by other changes today, but + #this still might cause other bugs of highlighting + #otherwise-invisible atoms. Needs review. ###@@@ + + # (Indirectly related: drawwiresphere acts like drawsphere for + # hit-test purposes.) + if len(self.bonds) == 1 and self.element is Singlet: + #bruce 050708 new feature - bond is part of self for highlighting dispdef = self.molecule.get_dispdef() - #bruce 050719 question: is it correct to ignore .display of self and its base atom? ###@@@ + #bruce 050719 question: is it correct to ignore .display of self + # and its base atom? ###@@@ #bruce 060630 guess answer: yes, since howdraw covers it. disp, drawradjunk = self.howdraw(dispdef) # (this arg is required) if disp in (diBALL, diTUBES): self.bonds[0].draw_in_abs_coords(glpane, color) - #bruce 060315 try to fix disappearing hover highlight when mouse goes over one of our wirespheres. - # We have to do it in the same coordinate system as the original wirespheres were drawn. - # (This will only work well if there is also a depth offset, or (maybe) an increased line thickness. - # I think there's a depth offset in the calling code, and it does seem to work. Note that it needs - # testing for rotated chunks, since until you next modify them, the wirespheres are also drawn rotated.) + #bruce 060315 try to fix disappearing hover highlight when mouse goes + #over one of our wirespheres. We have to do it in the same coordinate + #system as the original wirespheres were drawn. (This will only work + #well if there is also a depth offset, or (maybe) an increased line + #thickness. I think there's a depth offset in the calling code, and it + #does seem to work. Note that it needs testing for rotated chunks, + #since until you next modify them, the wirespheres are also drawn + #rotated.) self.molecule.pushMatrix() try: # note: the following inlines self.drawing_radius(picked_Radius = True), # but makes further use of intermediate values which that method # computes but does not return, so merging them would require a # variant method that returned more values. [bruce 080411 comment] - dispdef = self.molecule.get_dispdef() #e could optimize, since sometimes computed above -- but doesn't matter. + dispdef = self.molecule.get_dispdef() + #e could optimize, since sometimes computed above -- but doesn't matter. disp, drawrad = self.howdraw(dispdef) if disp == diTUBES: - pickedrad = drawrad * 1.8 # this code snippet is now shared between several places [bruce 060315/080411] + pickedrad = drawrad * 1.8 + # this code snippet is now shared between several places + # [bruce 060315/080411] else: pickedrad = drawrad * 1.1 pos = self.baseposn() self.draw_wirespheres(glpane, disp, pos, pickedrad) except: - print_compact_traceback("exception in draw_wirespheres part of draw_in_abs_coords ignored: ") + print_compact_traceback("exception in draw_wirespheres " \ + "part of draw_in_abs_coords ignored: ") pass self.molecule.popMatrix() return @@ -2483,7 +2692,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): self.molecule.add_some_atom_content(new & ~old) return - def howdraw(self, dispdef): # warning: if you add env.prefs[] lookups to this routine, modify selradius_prefs_values! + def howdraw(self, dispdef): + # warning: if you add env.prefs[] lookups to this routine, modify selradius_prefs_values! """ Tell how to draw the atom depending on its display mode (possibly inherited from dispdef, usually the molecule's effective dispdef). @@ -2522,9 +2732,12 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): if disp == diTUBES: rad = TubeRadius * 1.1 else: - #bruce 060307 moved all this into else clause; this might prevent some needless (and rare) - # gl_updates when all is shown in Tubes but prefs for other dispmodes are changed. - rad = self.element.rvdw # correct value for diTrueCPK (formerly, default); modified to produce values for other dispmodes + #bruce 060307 moved all this into else clause; this might prevent + #some needless (and rare) gl_updates when all is shown in Tubes + #but prefs for other dispmodes are changed. + rad = self.element.rvdw + # correct value for diTrueCPK (formerly, default); + # modified to produce values for other dispmodes if disp == diTrueCPK: rad = rad * env.prefs[cpkScaleFactor_prefs_key] if disp != diTrueCPK: @@ -2533,7 +2746,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): rad = rad * env.prefs[diBALL_AtomRadius_prefs_key] return (disp, rad) - def selradius_prefs_values(): # staticmethod in Atom #bruce 060317 for bug 1639 (and perhaps an analogue for other prefs) + def selradius_prefs_values(): # staticmethod in Atom + #bruce 060317 for bug 1639 (and perhaps an analogue for other prefs) """ Return a tuple of all prefs values that are ever used in computing any atom's selection radius (by selradius_squared). @@ -2542,7 +2756,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): selradius_prefs_values = staticmethod( selradius_prefs_values) - def selradius_squared(self): # warning: if you add env.prefs[] lookups to this routine, modify selradius_prefs_values! + def selradius_squared(self): + # warning: if you add env.prefs[] lookups to this routine, modify selradius_prefs_values! """ Return square of desired "selection radius", or -1.0 if atom should not be selectable (e.g. invisible). @@ -2667,8 +2882,10 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # (for writing only, not stored in our attrs) # [bruce 050404 to help fix bug 254] eltnum = Hydrogen.eltnum - posn = self.ideal_posn_re_neighbor( self.singlet_neighbor(), pretend_I_am = Hydrogen ) # see also self.sim_posn() - disp = "openbond" # kluge, meant as a comment in the file #bruce 051115 changed this from "singlet" to "openbond" + posn = self.ideal_posn_re_neighbor( self.singlet_neighbor(), pretend_I_am = Hydrogen ) + # see also self.sim_posn() + disp = "openbond" # kluge, meant as a comment in the file + #bruce 051115 changed this from "singlet" to "openbond" #bruce 051209 for history message in runSim (re bug 254): stats = mapping.options.get('dict_for_stats') if stats is not None: # might be {} @@ -3003,9 +3220,13 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): if self.molecule.assy.ppa2: try: ainfo += (". Distance between %s-%s is %.3f Angstroms." % \ - (self, self.molecule.assy.ppa2, vlen(self.posn()-self.molecule.assy.ppa2.posn()))) # fix bug 366-2 ninad060721 + (self, + self.molecule.assy.ppa2, + vlen(self.posn() - self.molecule.assy.ppa2.posn()))) + # fix bug 366-2 ninad060721 except: - print_compact_traceback("bug, fyi: ignoring exception in atom distance computation: ") #bruce 050218 + print_compact_traceback("bug, fyi: ignoring exception " \ + "in atom distance computation: ") #bruce 050218 pass # Include the angle between self, ppa2 and ppa3 in the info string. @@ -3183,7 +3404,7 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): if not r: disp, r = self.howdraw(disp) # bruce 041214: - # this is surely bad in only remaining use (depositMode.getCoords): + # following is surely bad in only remaining use (depositMode.getCoords): ## if self.picked and not iPic: return None dist, wid = orthodist(p1, v1, self.posn()) if wid > r: return None @@ -3200,12 +3421,18 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): return if self.filtered(): - return # mark 060303. [note: bruce 060321 has always thought it was nonmodular to check this here] - #bruce 060331 comment: we can't move this inside the conditional to optimize it, since we want it to affect - # whether we set picked_time, but we want to set that even for already-picked atoms. - # (Which are reasons of dubious value if this missed optim is important (don't know if it is), but are real ones.) + return # mark 060303. + # [note: bruce 060321 has always thought it was nonmodular to + # check this here] + + #bruce 060331 comment: we can't move this inside the conditional + #to optimize it, since we want it to affect whether we set + #picked_time, but we want to set that even for already-picked + #atoms. (Which are reasons of dubious value if this missed optim + #is important (don't know if it is), but are real ones.) - self._picked_time = self.molecule.assy._select_cmd_counter #bruce 051031, for ordering selected atoms; two related attrs + self._picked_time = self.molecule.assy._select_cmd_counter + #bruce 051031, for ordering selected atoms; two related attrs if not self.picked: self.picked = True _changed_picked_Atoms[self.key] = self #bruce 060321 for Undo (or future general uses) @@ -3231,23 +3458,29 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): return _picked_time = _picked_time_2 = -1 + def pick_order(self): #bruce 051031 """ - Return something which can be sorted to determine the order in which atoms were selected; include tiebreakers. - Legal to call even if self has never been selected or is not currently selected, - though results will not be very useful then. + Return something which can be sorted to determine the order in which + atoms were selected; include tiebreakers. Legal to call even if self + has never been selected or is not currently selected, though results + will not be very useful then. """ return (self._picked_time, self._picked_time_2, self.key) - def unpick(self, filtered = True): #bruce 060331 adding filtered = False option, as part of fixing bug 1796 + def unpick(self, filtered = True): """ Make this atom (self) unselected, if the selection filter permits this or if filtered = False. """ - # note: this is inlined (perhaps with filtered = False, not sure) into assembly.unpickatoms (in ops_select.py) + #bruce 060331 adding filtered = False option, as part of fixing bug 1796 + + # note: this is inlined (perhaps with filtered = False, not sure) + # into assembly.unpickatoms (in ops_select.py) # bruce 041214: singlets should never be picked, so Singlet test is not needed, # and besides if a singlet ever *does* get picked (due to a bug) you should let # the user unpick it! + ## if self.element is Singlet: return if self.picked: @@ -3326,19 +3559,25 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): def break_unmade_bond(self, origbond, origatom): #bruce 050524 """ - Add singlets (or do equivalent invals) as if origbond was copied from origatom - onto self (a copy of origatom), then broken; uses origatom - so it can find the other atom and know bond direction in space - (it assumes self might be translated but not rotated, wrt origatom). - For now this works like mol.copy used to, but later it might "inval singlets" instead. + Add singlets (or do equivalent invals) as if origbond was copied from + origatom onto self (a copy of origatom), then broken; uses origatom so + it can find the other atom and know bond direction in space (it + assumes self might be translated but not rotated, wrt origatom). + + For now this works like mol.copy used to, but later it might "inval + singlets" instead. """ - # compare to code in Bond.unbond() (maybe merge it? ####@@@@ need to inval things to redo singlets sometimes?) + # compare to code in Bond.unbond() (maybe merge it? ####@@@@ need to + # inval things to redo singlets sometimes?) a = origatom b = origbond numol = self.molecule - x = Atom('X', b.ubp(a), numol) ###k verify Atom.__init__ makes copy of posn, not stores original (tho orig ok if never mods it) + x = Atom('X', b.ubp(a), numol) + ###k verify Atom.__init__ makes copy of posn, not stores original + ###(tho orig ok if never mods it) na = self ## na = ndix[a.key] - bond_copied_atoms(na, x, origbond, origatom) # same properties as origbond... sensible in all cases?? ##k + bond_copied_atoms(na, x, origbond, origatom) + # same properties as origbond... sensible in all cases?? ##k return def unbond(self, b, make_bondpoint = True): @@ -3397,14 +3636,16 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): self.kill() # bruce 041115 added this and revised all callers else: # don't kill it, in this case [bruce 041115; I don't know if this ever happens] - from dna.updater.dna_updater_globals import get_dnaladder_inval_policy, DNALADDER_INVAL_IS_NOOP_BUT_OK - # can't be a toplevel import for now + from dna.updater.dna_updater_globals import get_dnaladder_inval_policy + from dna.updater.dna_updater_globals import DNALADDER_INVAL_IS_NOOP_BUT_OK + # can't be a toplevel import for now [review: still true? might be...] if get_dnaladder_inval_policy() == DNALADDER_INVAL_IS_NOOP_BUT_OK: pass # this now happens routinely during PAM conversion [bruce 080413] else: # I don't recall ever seeing this otherwise, but it's good # to keep checking for it [bruce 080413] - print "fyi: bug: unbond on a singlet %r finds unexpected bonds left over in it, %r" % (self, self.bonds) + print "fyi: bug: unbond on a singlet %r finds unexpected " \ + " bonds left over in it, %r" % (self, self.bonds) return None if not make_bondpoint: #bruce 070601 new feature @@ -3426,7 +3667,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): self.molecule.changeapp(0) return None if 1: - #bruce 060327 optim of Chunk.kill: if we're being killed right now, don't make a new bondpoint + #bruce 060327 optim of Chunk.kill: + # if we're being killed right now, don't make a new bondpoint if self._will_kill == Utility._will_kill_count: if DEBUG_1779: print "DEBUG_1779: self._will_kill %r == Utility._will_kill_count %r" % \ @@ -3497,7 +3739,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): """ return filter(lambda atom: atom.element is not Singlet, self.neighbors()) - def singNeighbors(self): #e when we have only one branch again, rename this singletNeighbors or bondpointNeighbors + def singNeighbors(self): + # todo: rename to singletNeighbors or bondpointNeighbors """ return a list of the singlets bonded to this atom """ @@ -3505,9 +3748,11 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): def baggage_and_other_neighbors(self): #bruce 051209 """ - Return a list of the baggage bonded to this atom (monovalent neighbors which should be dragged along with it), - and a list of the others (independent neighbors). Special case: in H2 (for example) there is no baggage - (so that there is some way to stretch the H-H bond); but singlets are always baggage, even in HX. + Return a list of the baggage bonded to this atom (monovalent neighbors + which should be dragged along with it), and a list of the others + (independent neighbors). Special case: in H2 (for example) there is no + baggage (so that there is some way to stretch the H-H bond); but + singlets are always baggage, even in HX. """ nn = self.neighbors() if len(nn) == 1: @@ -3571,21 +3816,25 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): """ if atomtype is None: atomtype = elt.atomtypes[0] - # Note: we do this even if self.element is elt and self.atomtype is not elt.atomtypes[0] ! - # That is, passing no atomtype is *always* equivalent to passing elt's default atomtype, - # even if this results in changing this atom's atomtype but not its element. + # Note: we do this even if self.element is elt and self.atomtype + # is not elt.atomtypes[0] ! That is, passing no atomtype is + # *always* equivalent to passing elt's default atomtype, even if + # this results in changing this atom's atomtype but not its + # element. assert atomtype.element is elt if debug_flags.atom_debug: if elt is Singlet: #bruce 041118 # this is unsupported; if we support it it would require - # moving this atom to its neighbor atom's chunk, too - # [btw we *do* permit self.element is Singlet before we change it] + # moving this atom to its neighbor atom's chunk, too [btw we + # *do* permit self.element is Singlet before we change it] print "atom_debug: fyi, bug?: mvElement changing %r to a singlet" % self if self.atomtype_iff_set() is atomtype: assert self.element is elt # i.e. assert that self.element and self.atomtype were consistent if debug_flags.atom_debug: #bruce 050509 - print_compact_stack( "atom_debug: fyi, bug?: mvElement changing %r to its existing element and atomtype" % self ) - return #bruce 050509, not 100% sure it's correct, but if not, caller probably has a bug (eg relies on our invals) + msg = "atom_debug: fyi, bug?: mvElement changing %r to its existing element and atomtype" % self + print_compact_stack( msg + ": " ) + return #bruce 050509, not 100% sure it's correct, but if not, + # caller probably has a bug (eg relies on our invals) # now we're committed to doing the change if (self.element is Singlet) != (elt is Singlet): # set of singlets is changing @@ -3633,7 +3882,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): if (not not better_alive_answer) != (not self.__killed): #bruce 060414 re bug 1779, but it never printed for it (worth keeping in for other bugs) #bruce 071018 fixed typo of () after debug_flags.atom_debug -- could that be why it never printed it?!? - print "debug: better_alive_answer is %r but (not self.__killed) is %r" % (better_alive_answer , not self.__killed) + print "debug: better_alive_answer is %r but (not self.__killed) is %r" % \ + (better_alive_answer , not self.__killed) return res def killed_with_debug_checks(self): # renamed by bruce 050702; was called killed(); by bruce 041029 @@ -3742,7 +3992,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # does this ever still happen? TODO: if so, document when & why. print_compact_traceback("fyi: Atom.kill: ignoring error in remove_atom %r from jig %r: " % (self, j) ) self.jigs = [] # mitigate repeated kills - _changed_structure_Atoms[self.key] = self #k not sure if needed; if it is, also covers .bonds below #bruce 060322 + _changed_structure_Atoms[self.key] = self + #k not sure if needed; if it is, also covers .bonds below #bruce 060322 # remove bonds selfmol = self.molecule @@ -3851,7 +4102,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): its_atype = neighbor.atomtype # presently we ignore the bond-valence between us and that neighbor atom, # even if this can vary for different bonds to it (for the atomtype it has) - newlen = my_atype.rcovalent + its_atype.rcovalent #k Singlet.atomtypes[0].rcovalent better be 0, check this + newlen = my_atype.rcovalent + its_atype.rcovalent + #k Singlet.atomtypes[0].rcovalent better be 0, check this return it + newlen * it_to_me_direction def Dehydrogenate(self): @@ -4000,13 +4252,15 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): return moveme - def Passivate(self): ###@@@ not yet modified for atomtypes since it's not obvious what it should do! [bruce 050511] + def Passivate(self): """ [Public method, does all needed invalidations:] Change the element type of this atom to match the number of bonds with other real atoms, and delete singlets. """ - # bruce 041215 modified docstring, added comments, capitalized name + # bruce 041215 modified docstring, added comments, capitalized name. + # REVIEW: not yet modified for atomtypes since it's not obvious what it + # should do! [bruce 050511] el = self.element PTsenil = PeriodicTable.getPTsenil() line = len(PTsenil) @@ -4029,7 +4283,9 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # singlets anyway -- which is fine def is_singlet(self): - return self.element is Singlet # [bruce 050502 comment: it's possible self is killed and len(self.bonds) is 0] + return self.element is Singlet + # [bruce 050502 comment: it's possible self is killed and + # len(self.bonds) is 0] def singlet_neighbor(self): """ @@ -4045,33 +4301,40 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): assert atom.element is not Singlet, "bug: a singlet %r is bonded to another singlet %r!!" % (self, atom) return atom - # higher-valence bonds methods [bruce 050502] [bruce 050627 comment: a lot of this might be obsolete. ###@@@] + # higher-valence bonds methods [bruce 050502] + # [bruce 050627 comment: a lot of this might be obsolete. ###@@@] def singlet_v6(self): assert self.element is Singlet, "%r should be a singlet but is %s" % (self, self.element.name) assert len(self.bonds) == 1, "%r should have exactly 1 bond but has %d" % (self, len(self.bonds)) return self.bonds[0].v6 - singlet_valence = singlet_v6 ###@@@ need to decide which name to keep! probably this one, singlet_valence. [050502 430pm] - def singlet_reduce_valence_noupdate(self, vdelta): # this might or might not kill it; # it might even reduce valence to 0 but not kill it, # letting base atom worry about that - # (and letting it take advantage of the singlet's position, when it updates things) - assert self.element is Singlet, "%r should be a singlet but is %s" % (self, self.element.name) - assert len(self.bonds) == 1, "%r should have exactly 1 bond but has %d" % (self, len(self.bonds)) - self.bonds[0].reduce_valence_noupdate(vdelta, permit_illegal_valence = True) # permits in-between, 0, or negative(?) valence + # (and letting it take advantage of the singlet's + # position, when it updates things) + assert self.element is Singlet, \ + "%r should be a singlet but is %s" % \ + (self, self.element.name) + assert len(self.bonds) == 1, \ + "%r should have exactly 1 bond but has %d" % \ + (self, len(self.bonds)) + self.bonds[0].reduce_valence_noupdate(vdelta, permit_illegal_valence = True) + # permits in-between, 0, or negative(?) valence return def update_valence(self, dont_revise_valid_bondpoints = False): """ - warning: following docstring used to be a comment, hasn't been verified recently: - repositions/alters existing bondpoints, updates bonding pattern, valence errors, etc; - might reorder bonds, kill bondpoints; but doesn't move the atom and doesn't alter - existing real bonds or other atoms; it might let atom record how it wants to move, - when it has a chance and wants to clean up structure, if this can ever be ambiguous - later, when the current state (including positions of old bondpoints) is gone. + warning: following docstring used to be a comment, hasn't been + verified recently: repositions/alters existing bondpoints, updates + bonding pattern, valence errors, etc; might reorder bonds, kill + bondpoints; but doesn't move the atom and doesn't alter existing real + bonds or other atoms; it might let atom record how it wants to move, + when it has a chance and wants to clean up structure, if this can ever + be ambiguous later, when the current state (including positions of old + bondpoints) is gone. Update 071019: if dont_revise_valid_bondpoints is passed, then only bondpoints with invalid valence (e.g. zero valence) are altered. @@ -4084,13 +4347,16 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): from model.bond_constants import V_ZERO_VALENCE, BOND_VALENCES _debug = False ## debug_flags.atom_debug is sometimes useful here if self._modified_valence: - self._modified_valence = False # do this first, so exceptions in the following only happen once + self._modified_valence = False + # do this first, so exceptions in the following only happen once if _debug: print "atom_debug: update_valence starting to updating it for", self - # the only easy part is to kill bondpoints with illegal valences, and warn if those were not 0. + # the only easy part is to kill bondpoints with illegal valences, + # and warn if those were not 0. zerokilled = badkilled = 0 - for sing in self.singNeighbors(): ###@@@ check out the other calls of this for code that might help us here... - sv = sing.singlet_valence() + for sing in self.singNeighbors(): + ###@@@ check out the other calls of this for code that might help us here... + sv = sing.singlet_v6() if sv == V_ZERO_VALENCE: sing.kill() zerokilled += 1 @@ -4099,11 +4365,15 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): sing.kill() badkilled += 1 if _debug: - print "atom_debug: update_valence %r killed %d zero-valence and %d bad-valence bondpoints" % \ + print "atom_debug: update_valence %r killed %d " \ + "zero-valence and %d bad-valence bondpoints" % \ (self, zerokilled, badkilled) - ###e now fix things up... not sure exactly under what conds, or using what code (but see existing code mentioned above) - #bruce 050702 working on bug 121, here is a guess: change atomtype to best match new total number of bonds - # (which we might have changed by killing some bondpoints). But only do this if we did actually kill bondpoints. + ###e now fix things up... not sure exactly under what conds, or + ###using what code (but see existing code mentioned above) + #bruce 050702 working on bug 121, here is a guess: change atomtype + #to best match new total number of bonds (which we might have + #changed by killing some bondpoints). But only do this if we did + #actually kill bondpoints. if zerokilled or badkilled: self.adjust_atomtype_to_numbonds( dont_revise_bondpoints = dont_revise_valid_bondpoints ) ### WARNING: if dont_revise_bondpoints is not set, @@ -4112,9 +4382,10 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # remaking and the moving can be bad for some callers; # thus the new option. It may need to be used more widely. # [bruce 071019] - #bruce 050728 temporary fix for bug 823 (in which C(sp2) is left with 2 order1 bondpoints that should be one bondpoint); - # but in the long run we need a more principled way to decide whether to remake bondpoints or change atomtype - # when they don't agree: + #bruce 050728 temporary fix for bug 823 (in which C(sp2) is left + #with 2 order1 bondpoints that should be one bondpoint); but in + #the long run we need a more principled way to decide whether to + #remake bondpoints or change atomtype when they don't agree: if len(self.bonds) != self.atomtype.numbonds and not dont_revise_valid_bondpoints: if _debug: print "atom_debug: update_valence %r calling remake_bondpoints" @@ -4127,17 +4398,20 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): """ [Public method, does all needed invals, might emit history messages #k] - If this atom's number of bonds (including open bonds) is better matched - by some other atomtype of its element than by its current atomtype, or if - its atomtype is not yet set, then change to the best atomtype and (if atomtype - was already set) emit a history message about this change. + If this atom's number of bonds (including open bonds) is better + matched by some other atomtype of its element than by its current + atomtype, or if its atomtype is not yet set, then change to the best + atomtype and (if atomtype was already set) emit a history message + about this change. - The comparison is of current atomtype to self.best_atomtype_for_numbonds(). + The comparison is of current atomtype to + self.best_atomtype_for_numbonds(). The change is done by set_atomtype if number of bonds is correct and - dont_revise_bondpoints is not passed (since set_atomtype also - remakes bondpoints in better positions), or by set_atomtype_but_dont_revise_singlets - otherwise (no attempt is made to correct the number of open bonds in that case). + dont_revise_bondpoints is not passed (since set_atomtype also remakes + bondpoints in better positions), or by + set_atomtype_but_dont_revise_singlets otherwise (no attempt is made to + correct the number of open bonds in that case). [See also self.can_reduce_numbonds().] """ @@ -4148,11 +4422,14 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): if best_atype is atype_now: return # never happens if atype_now is None if atype_now is not None: - env.history.message("changing %s atomtype from %s to %s" % (self, atype_now.name, best_atype.name)) - # this will often happen twice, plus a third message from Build that it increased bond order, - # so i'm likely to decide not to print it + env.history.message("changing %s atomtype from %s to %s" % \ + (self, atype_now.name, best_atype.name)) + # this will often happen twice, plus a third message from Build + # that it increased bond order, so i'm likely to decide not to + # print it if (not dont_revise_bondpoints) and best_atype.numbonds == len(self.bonds): - # right number of open bonds for new atype -- let's move them to better positions when we set it + # right number of open bonds for new atype -- + # let's move them to better positions when we set it self.set_atomtype( best_atype) else: # wrong number of open bonds -- leave them alone (in number and position); @@ -4160,41 +4437,53 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): self.set_atomtype_but_dont_revise_singlets( best_atype) return - def best_atomtype_for_numbonds(self, atype_now = None, elt = None): #bruce 050702; elt arg added 050707 - # see also best_atype in bond_utils.py, which does something different but related [bruce 060523] + def best_atomtype_for_numbonds(self, atype_now = None, elt = None): """ [Public method] Compute and return the best choice of atomtype for this atom's element - (or for the passed element <elt>, if any) - and number of bonds (including open bonds), - breaking ties by favoring <atype_now> (if provided), - otherwise favoring atomtypes which come earlier - in the list of this element's (or elt's) possible atomtypes. - - For comparing atomtypes which err in different directions (which I doubt can ever matter in - practice, since the range of numbonds of an element's atomtypes will be contiguous), - we'll say it's better for an atom to have too few bonds than too many. - In fact, we'll say any number of bonds too few (on the atom, compared to the atomtype) - is better than even one bond too many. - - This means: the "best" atomtype is the one with the right number of bonds, or the fewest extra bonds, - or (if all of them have fewer bonds than this atom) with the least-too-few bonds. - - (This method is used in Build mode, and might later be used when reading mmp files or pdb files, or in other ways. - As of 050707 it's also used in Transmute.) - [###k Should we also take into account positions of bonds, or their estimated orders, or neighbor elements??] + (or for the passed element <elt>, if any) and number of bonds + (including open bonds), breaking ties by favoring <atype_now> (if + provided), otherwise favoring atomtypes which come earlier in the list + of this element's (or elt's) possible atomtypes. + + For comparing atomtypes which err in different directions (which I + doubt can ever matter in practice, since the range of numbonds of an + element's atomtypes will be contiguous), we'll say it's better for an + atom to have too few bonds than too many. In fact, we'll say any + number of bonds too few (on the atom, compared to the atomtype) is + better than even one bond too many. + + This means: the "best" atomtype is the one with the right number of + bonds, or the fewest extra bonds, or (if all of them have fewer bonds + than this atom) with the least-too-few bonds. + + (This method is used in Build mode, and might later be used when + reading mmp files or pdb files, or in other ways. As of 050707 it's + also used in Transmute.) + + [###k Should we also take into account positions of bonds, or their + estimated orders, or neighbor elements??] """ + #bruce 050702; elt arg added 050707 + # see also best_atype in bond_utils.py, which does something different + # but related [bruce 060523] if elt is None: elt = self.element atomtypes = elt.atomtypes if len(atomtypes) == 1: return atomtypes[0] # optimization - nbonds = len(self.bonds) # the best atomtype has numbonds == nbonds. Next best, nbonds+1, +2, etc. Next best, -1,-2, etc. + nbonds = len(self.bonds) + # the best atomtype has numbonds == nbonds. Next best, nbonds+1, + # +2, etc. Next best, -1,-2, etc. items = [] for i, atype in zip(range(len(atomtypes)), atomtypes): - if atype is atype_now: # (if atype_now is None or is not for elt, this is a legal comparison and is always False) - i = -1 # best to stay the same (as atype_now), or to be earlier in the list of atomtypes, other things being equal + if atype is atype_now: + # (if atype_now is None or is not for elt, this is a legal + # comparison and is always False) + i = -1 + # best to stay the same (as atype_now), or to be earlier + # in the list of atomtypes, other things being equal numbonds = atype.numbonds if numbonds < nbonds: order = (1, nbonds - numbonds, i) # higher is worse @@ -4230,18 +4519,24 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): OR, maybe it's really a public method and should be renamed with no _ like the method on Jig.] - This must be called by all low-level methods which change this atom's or bondpoint's element, atomtype, - or set of bonds. It doesn't need to be called for changes to neighbor atoms, or for position changes, - or for changes to chunk membership of this atom, or when this atom is killed (but it will be called indirectly - when this atom is killed, when the bonds are broken, unless this atom has no bonds). Calling it when not needed - is ok, but might slow down later update functions by making them inspect this atom for important changes. - (For example, calling it on a PAM atom invalidates that atom's entire DnaLadder.) - - All user events which can call this (indirectly) should also call env.do_post_event_updates() when they're done. - """ - ####@@@@ I suspect it is better to also call this for all killed atoms or bondpoints, but didn't do this yet. [bruce 050725] + This must be called by all low-level methods which change this atom's + or bondpoint's element, atomtype, or set of bonds. It doesn't need to + be called for changes to neighbor atoms, or for position changes, or + for changes to chunk membership of this atom, or when this atom is + killed (but it will be called indirectly when this atom is killed, + when the bonds are broken, unless this atom has no bonds). Calling it + when not needed is ok, but might slow down later update functions by + making them inspect this atom for important changes. (For example, + calling it on a PAM atom invalidates that atom's entire DnaLadder.) + + All user events which can call this (indirectly) should also call + env.do_post_event_updates() when they're done. + """ + ####@@@@ I suspect it is better to also call this for all + # killed atoms or bondpoints, but didn't do this yet. [bruce 050725] ## before 051011 this used id(self) for key - #e could probably optim by importing this dict at toplevel, or perhaps even assigning a lambda in place of this method + #e could probably optim by importing this dict at toplevel, + # or perhaps even assigning a lambda in place of this method global_model_changedicts.changed_structure_atoms[ self.key ] = self _changed_structure_Atoms[ self.key ] = self #bruce 060322 # (see comment at _changed_structure_Atoms about how these two dicts are related) @@ -4261,17 +4556,19 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): # debugging methods (not yet fully tested; use at your own risk) - def invalidate_everything(self): # for an atom, remove it and then readd it to its chunk + def invalidate_everything(self): # in class Atom """ - debugging method + debugging method -- remove self from its chunk, then add it back """ if len(self.molecule.atoms) <= 1: - print "warning: invalidate_everything on the lone atom %r in chunk %r does nothing" % (self, self.molecule) + print "warning: invalidate_everything on the lone " \ + "atom %r in chunk %r does nothing" % (self, self.molecule) print " since otherwise it might kill that chunk as a side effect!" else: - #bruce 080318 bugfix: don't do this if only one atom; revise print above to say so - # note: delatom invals self.bonds - self.molecule.delatom(self) # note: this kills the chunk if it becomes empty! + # note: delatom invals self.bonds, + # and kills the chunk if it becomes empty! + # (which won't happen here due to the condition above) + self.molecule.delatom(self) self.molecule.addatom(self) return @@ -4354,8 +4651,10 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): if atomtype is None: if self.element is elt and \ len(self.bonds) == self.atomtype.numbonds: - ## this code might be used if we don't always return due to bond valence: ###@@@ - ## atomtype = self.atomtype # use current atomtype if we're correct for it now, even if it's not default atomtype + # this code might be used if we don't always return due to bond valence: ###@@@ + ## atomtype = self.atomtype + ## # use current atomtype if we're correct for it now, + ## # even if it's not default atomtype # return since elt and desired atomtype are same as now and # we're correct return @@ -4400,12 +4699,13 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): self.direct_Transmute( elt, atomtype) return - def Transmute_selection(self, elt): #bruce 070412; could use review for appropriate level, error handling, etc + def Transmute_selection(self, elt): """ [this may be a private method for use when making our cmenu; if not, it needs more options and a better docstring.] Transmute as many as possible of the selected atoms to elt. """ + #bruce 070412; could use review for appropriate level, error handling, etc selatoms = self.molecule.assy.selatoms atoms = selatoms.values() # not itervalues, too dangerous for atom in atoms: @@ -4437,20 +4737,26 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): elif b is bond: nbonds += 1 min_other_valence = len(bonds) # probably the only way this bonds list is used - permitted = {} # maps each permitted v6 to the list of atomtypes which permit it (in same order as self.element.atomtypes) + permitted = {} # maps each permitted v6 to the list of atomtypes which permit it + # (in same order as self.element.atomtypes) is_singlet = self.is_singlet() #k not sure if the cond that uses this is needed for atype in self.element.atomtypes: if nbonds > atype.numbonds: continue # atype doesn't permit enough bonds - # if we changed self to that atomtype, how high could bond's valence be? (expressed as its permitted valences) + # if we changed self to that atomtype, how high could bond's valence be? + # (expressed as its permitted valences) # Do we take into account min_other_valence, or not? Yes, I think. ##k review once this is tried. Maybe debug print whether this matters. #e - # There are two limits: atype.permitted_v6_list, and atype.valence minus min_other_valence. - # But the min_other_valence is the max of two things: other real bonds, or required numbonds (for this atype) - # minus 1 (really, that times the minimum bond order for this atomtype, if that's not 1, but minimum bond order - # not being 1 is nim in all current code). - # (BTW, re fixed bug 1944, I don't know why double is in N(sp2)g's permitted_v6_list, and that's wrong - # and remains unfixed, though our_min_other_valence makes it not matter in this code.) + + # There are two limits: atype.permitted_v6_list, and atype.valence + # minus min_other_valence. But the min_other_valence is the max of + # two things: other real bonds, or required numbonds (for this + # atype) minus 1 (really, that times the minimum bond order for + # this atomtype, if that's not 1, but minimum bond order not being + # 1 is nim in all current code). (BTW, re fixed bug 1944, I don't + # know why double is in N(sp2)g's permitted_v6_list, and that's + # wrong and remains unfixed, though our_min_other_valence makes it + # not matter in this code.) for v6 in atype.permitted_v6_list: our_min_other_valence = max(min_other_valence, atype.numbonds - 1) #bruce 060524 fix bug 1944 if is_singlet or v6 <= (atype.valence - our_min_other_valence) * V_SINGLE: @@ -4491,7 +4797,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): #bruce 060629 for bondpoint problem try: import operations.reposition_baggage as reposition_baggage # don't make this a toplevel import - reload_once_per_event(reposition_baggage) # this can be removed when devel is done, but doesn't need to be + reload_once_per_event(reposition_baggage) + # this can be removed when devel is done, but doesn't need to be reposition_baggage.reposition_baggage_0(self, baggage, planned_atom_nupos) except: # this is always needed, since some of the code for special alignment cases @@ -4519,7 +4826,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): for atom in bn: if not atom.is_singlet(): pass ###e record element and position - atom.mvElement(Singlet) ####k ??? #####@@@@@ kluge to kill it w/o replacing w/ singlet; better to just tell kill that + atom.mvElement(Singlet) ####k ??? #####@@@@@ kluge to kill it w/o + # replacing w/ singlet; better to just tell kill that atom.kill() # (since atom is a singlet, this kill doesn't replace it with a singlet) self.make_enough_bondpoints() ###e might pass old posns to ask this to imitate them if it can pass ###e now transmute the elts back to what they were, if you can, based on nearness @@ -4535,7 +4843,8 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): good positions relative to existing bonds (if any) (which are not changed, whether they are real or open bonds). """ - #bruce 050510 extending this to use atomtypes; all subrs still need to set singlet valence ####@@@@ + #bruce 050510 extending this to use atomtypes; + # all subrs still need to set singlet valence ####@@@@ if len(self.bonds) >= self.atomtype.numbonds: return # don't want any more bonds # number of existing bonds tells how to position new open bonds @@ -4607,30 +4916,47 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): a2 = a1.bonds[0].other(a1) a2pos = a2.posn() else: - #bruce 050728 new feature: for new pi bonds to sp atoms, use pi_info to decide where to pretend a2 lies. - # If we give up, it's safe to just say a2pos = a1.posn() -- twistor() apparently tolerates that ambiguity. + #bruce 050728 new feature: for new pi bonds to sp atoms, + # use pi_info to decide where to pretend a2 lies. + # If we give up, it's safe to just say a2pos = a1.posn() -- + # twistor() apparently tolerates that ambiguity. try: - # catch exceptions in case for some reason it's too early to compute this... - # note that, if we're running in Build mode (which just deposited self and bonded it to a1), - # then that new bond and that change to a1 hasn't yet been seen by the bond updating code, - # so an old pi_info object might be on the other bonds to a1, and none would be on the new bond a1-self. - # But we don't know if this bond is new, so if it has a pi_bond_obj we don't know if that's ok or not. - # So to fix a bug this exposed, I'm making bond.rebond warn the pi_bond_obj on its bond, immediately - # (not waiting for bond updating code to do it). - # [050729: this is no longer needed, now that we destroy old pi_bond_obj (see below), but is still done.] - b = self.bonds[0] # if there was not just one bond on self, we'd say find_bond(self,a1) - #bruce 050729: it turns out there can be incorrect (out of date) pi_info here, - # from when some prior singlets on self were still there -- need to get rid of this and recompute it. - # This fixes a bug I found, and am reporting, in my mail (not yet sent) saying bug 841 is Not A Bug. + # catch exceptions in case for some reason it's too + # early to compute this... note that, if we're running + # in Build mode (which just deposited self and bonded + # it to a1), then that new bond and that change to a1 + # hasn't yet been seen by the bond updating code, so + # an old pi_info object might be on the other bonds to + # a1, and none would be on the new bond a1-self. But + # we don't know if this bond is new, so if it has a + # pi_bond_obj we don't know if that's ok or not. So to + # fix a bug this exposed, I'm making bond.rebond warn + # the pi_bond_obj on its bond, immediately (not + # waiting for bond updating code to do it). + # [050729: this is no longer needed, now that we + # destroy old pi_bond_obj (see below), but is still + # done.] + b = self.bonds[0] + # if there was not just one bond on self, we'd say find_bond(self,a1) + #bruce 050729: it turns out there can be incorrect + # (out of date) pi_info here, from when some prior + # singlets on self were still there -- need to get rid + # of this and recompute it. This fixes a bug I found, + # and am reporting, in my mail (not yet sent) saying + # bug 841 is Not A Bug. if b.pi_bond_obj is not None: b.pi_bond_obj.destroy() - pi_info = b.get_pi_info(abs_coords = True) # without the option, vectors would be in bond's coordsys + pi_info = b.get_pi_info(abs_coords = True) + # without the option, vectors would be in bond's coordsys ((a1py, a1pz), (a2py, a2pz), ord_pi_y, ord_pi_z) = pi_info del ord_pi_y, ord_pi_z - # note that we don't know whether we're atom1 or atom2 in that info, but it shouldn't matter - # since self is not affecting it so it should not be twisted along b. - # So we'll pretend a1py, a1pz are about a1, though they might not be. - # We'll use a1pz as the relative place to imagine a1's neighbor, if a1 had been sp2 rather than sp. + # note that we don't know whether we're atom1 or atom2 + # in that info, but it shouldn't matter since self is + # not affecting it so it should not be twisted along + # b. So we'll pretend a1py, a1pz are about a1, though + # they might not be. We'll use a1pz as the relative + # place to imagine a1's neighbor, if a1 had been sp2 + # rather than sp. a2pos = a1.posn() + a1pz except: print_compact_traceback("exception ignored: ") @@ -4660,12 +4986,14 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): if 1: # see comment below [bruce 050614] spinsign = debug_pref("spinsign", Choice([1,-1])) for q in atype.quats: - # the following old code has the wrong sign on spin, thus causing bug 661: [fixed by bruce 050614] + # the following old code has the wrong sign on spin, + # thus causing bug 661: [fixed by bruce 050614] ## q = rq + q - rq - spin # this would be the correct code: ## q = rq + q - rq + spin - # but as an example of how to use debug_pref, I'll put in code that can do it either way, - # with the default pref value giving the correct behavior (moved just above, outside of this loop). + # but as an example of how to use debug_pref, I'll put in + # code that can do it either way, with the default pref value + # giving the correct behavior (moved just above, outside of this loop). q = rq + q - rq + spin * spinsign xpos = pos + q.rot(r) x = Atom('X', xpos, chunk) @@ -4732,11 +5060,14 @@ class Atom( PAM_Atom_methods, AtomBase, InvalMixin, StateMixin, Selobj_API): except: # [bruce 041215:] # fix unreported unverified bug (self at center of its neighbors): - # [bruce 050716 comment: one time this can happen is when we change atomtype of some C in graphite to sp3] + # [bruce 050716 comment: one time this can happen is when we + # change atomtype of some C in graphite to sp3] if debug_flags.atom_debug: - print "atom_debug: fyi: self at center of its neighbors (more or less) while making singlet", self, self.bonds + print "atom_debug: fyi: self at center of its neighbors " \ + "(more or less) while making singlet", self, self.bonds dir = norm(cross(s1pos - pos, s2pos - pos)) - # that assumes s1 and s2 are not opposite each other; #e it would be safer to pick best of all 3 pairs + # that assumes s1 and s2 are not opposite each other; + #e it would be safer to pick best of all 3 pairs opos = pos + atype.rcovalent * dir chunk = self.molecule x = Atom('X', opos, chunk) diff --git a/cad/src/model/chunk.py b/cad/src/model/chunk.py index a776fb83b..3de610d52 100755 --- a/cad/src/model/chunk.py +++ b/cad/src/model/chunk.py @@ -4,7 +4,7 @@ chunk.py -- provides class Chunk [formerly known as class molecule], for a bunch of atoms (not necessarily bonded together) which can be moved and selected as a unit. -@author: Josh +@author: Josh, Bruce, others @version: $Id$ @copyright: 2004-2009 Nanorex, Inc. See LICENSE file for details. @@ -16,30 +16,39 @@ lots of changes, by various developers split out of chem.py by bruce circa 041118 -bruce optimized some things, including using 'is' and 'is not' rather than '==', '!=' -for atoms, molecules, elements, parts, assys in many places (not all commented individually); 050513 - -bruce 060308 rewriting Atom and Chunk so that atom positions are always stored in the atom -(eliminating Atom.xyz and Chunk.curpos, adding Atom._posn, eliminating incremental update of atpos/basepos). -Motivation is to make it simpler to rewrite high-frequency methods in Pyrex. - -bruce 060313 splitting _recompute_atlist out of _recompute_atpos, and planning to remove atom.index from -undoable state. Rules for atom.index (old, reviewed now and reconfirmed): owned by atom.molecule; value doesn't matter -unless atom.molecule and its .atlist exist (but is set to -1 otherwise when this is convenient, to help catch bugs); -must be correct whenever atom.molecule.atlist exists (and is reset when it's made); correct means it's an index for -that atom into .atlist, .atpos, .basepos, whichever of those exist at the time (atlist always does). -This means a chunk's addatom, delatom, and _undo_update need to invalidate its .atlist, -and means there's no need to store atom.index as undoable state (making diffs more compact), -or to update a chunk's .atpos (or even .atlist) when making an undo checkpoint. - -(It would be nice for Undo to not store copies of changed .atoms dicts of chunks too, but that's harder. ###e) - -[update, bruce 060411: I did remove atom.index from undoable state, as well as chunk.atoms, and I made atoms always store -their own absposns. I forgot to summarize the new rules here -- maybe I did somewhere else. Looking at the code now, -atoms still try to get baseposns from their chunk, which still computes that before drawing them; moving a chunk -probably invalidates atpos and basepos (guess, but _recompute_atpos inval decl code would seem wrong otherwise) -and drawing it then recomputes them -- or maybe not, since it's only when remaking display list that it should need to. -Sometime I should review this and see if there is some obvious optimization needed.] +bruce 050513 optimized some things, including using 'is' and 'is not' rather than +'==', '!=' for atoms, molecules, elements, parts, assys in many places (not +all commented individually) + +bruce 060308 rewriting Atom and Chunk so that atom positions are always stored +in the atom (eliminating Atom.xyz and Chunk.curpos, adding Atom._posn, +eliminating incremental update of atpos/basepos). Motivation is to make it +simpler to rewrite high-frequency methods in Pyrex. + +bruce 060313 splitting _recompute_atlist out of _recompute_atpos, and planning +to remove atom.index from undoable state. Rules for atom.index (old, reviewed +now and reconfirmed): owned by atom.molecule; value doesn't matter unless +atom.molecule and its .atlist exist (but is set to -1 otherwise when this is +convenient, to help catch bugs); must be correct whenever atom.molecule.atlist +exists (and is reset when it's made); correct means it's an index for that +atom into .atlist, .atpos, .basepos, whichever of those exist at the time +(atlist always does). This means a chunk's addatom, delatom, and _undo_update +need to invalidate its .atlist, and means there's no need to store atom.index +as undoable state (making diffs more compact), or to update a chunk's .atpos +(or even .atlist) when making an undo checkpoint. + +(It would be nice for Undo to not store copies of changed .atoms dicts of +chunks too, but that's harder. ###e) + +[update, bruce 060411: I did remove atom.index from undoable state, as well as +chunk.atoms, and I made atoms always store their own absposns. I forgot to +summarize the new rules here -- maybe I did somewhere else. Looking at the +code now, atoms still try to get baseposns from their chunk, which still +computes that before drawing them; moving a chunk probably invalidates atpos +and basepos (guess, but _recompute_atpos inval decl code would seem wrong +otherwise) and drawing it then recomputes them -- or maybe not, since it's +only when remaking display list that it should need to. Sometime I should +review this and see if there is some obvious optimization needed.] bruce 080305 changed superclass from Node to NodeWithAtomContents """ @@ -196,30 +205,40 @@ class Chunk(NodeWithAtomContents, InvalMixin, """ #bruce 071114 renamed this from class molecule -> class Chunk - # class constants to serve as default values of attributes, and _s_attr decls for some of them + # class constants to serve as default values of attributes, and _s_attr + # decls for some of them _hotspot = None - _s_attr_hotspot = S_REF #bruce 060404 revised this in several ways; bug 1633 (incl. all subbugs) will need retesting. - # Note that this declares hotspot, not _hotspot, so that undo state never contains dead atoms. - # This is only ok because we provide _undo_setattr_hotspot as well. + _s_attr_hotspot = S_REF + #bruce 060404 revised this in several ways; + # bug 1633 (incl. all subbugs) will need retesting. + # Note that this declares hotspot, not _hotspot, so that undo state + # never contains dead atoms. This is only ok because we provide + # _undo_setattr_hotspot as well. # - # Note that we don't put this (or Jig.atoms) into the 'atoms' _s_attrlayer, since we still need to scan them as data. + # Note that we don't put this (or Jig.atoms) into the 'atoms' + # _s_attrlayer, since we still need to scan them as data. # - # Here are some old comments from when this declared _hotspot, still relevant: - #e we need to warn somehow if you hit a StateMixin object in S_REF but didn't store state for it - # (as could happen when we declared _hotspot as data, not child, and it could be a dead atom); - #e ideally we'd add debug code to detect the original error (declaring hotspot), - # due to presence of a _get_hotspot method; maybe we'd have an optional method (implemented by InvalMixin) - # to say whether an attr is legal for an undoable state decl. But (060404) there needs to be an exception, - # e.g. when _undo_setattr_hotspot exists, like now. + # Here are some old comments from when this declared _hotspot, still + # relevant: todo: warn somehow if you hit a StateMixin object in S_REF + # but didn't store state for it (as could happen when we declared + # _hotspot as data, not child, and it could be a dead atom); ideally + # we'd add debug code to detect the original error (declaring + # hotspot), due to presence of a _get_hotspot method; maybe we'd have + # an optional method (implemented by InvalMixin) to say whether an + # attr is legal for an undoable state decl. But (060404) there needs + # to be an exception, e.g. when _undo_setattr_hotspot exists, like + # now. _colorfunc = None _dispfunc = None - is_movable = True #mark 060120 [no need for _s_attr decl, since constant for this class -- bruce guess 060308] + is_movable = True #mark 060120 + # [no need for _s_attr decl, since constant for this class -- bruce guess 060308] - # Undoable/copyable attrs: (no need for _s_attr decls since copyable_attrs provides them) + # Undoable/copyable attrs: + # (no need for _s_attr decls since copyable_attrs provides them) # self.display overrides global display (GLPane.display) # but is overriden by atom value if not default @@ -256,11 +275,16 @@ class Chunk(NodeWithAtomContents, InvalMixin, # parameters determine which model to convert to, and whether to ever # convert. # - # [bruce 080321 for PAM3+5] ### TODO: use for conversion, and prevent ladder merge when different + # [bruce 080321 for PAM3+5] ### TODO: use for conversion, and prevent + # ladder merge when different - display_as_pam = "" # PAM model to use for displaying and editing PAM atoms in self (not set means use user pref) + display_as_pam = "" + # PAM model to use for displaying and editing PAM atoms in self (not + # set means use user pref) - save_as_pam = "" # PAM model to use for saving self (not normally set; not set means use save-op params) + save_as_pam = "" + # PAM model to use for saving self (not normally set; not set means + # use save-op params) copyable_attrs = _superclass.copyable_attrs + ('display', 'color', 'display_as_pam', 'save_as_pam', @@ -286,7 +310,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, # attribute. iconPath = "ui/modeltree/Chunk.png" - # no need to _s_attr_ decl basecenter and quat -- they're officially arbitrary, and get replaced when things get recomputed + # no need to _s_attr_ decl basecenter and quat -- they're officially + # arbitrary, and get replaced when things get recomputed # [that's the theory, anyway... bruce 060223] # flags to tell us that our ExternalBondSets need updating @@ -334,14 +359,17 @@ class Chunk(NodeWithAtomContents, InvalMixin, state). See also _f_invalidate_atom_lists_and_maybe_deallocate_displist, which is called (later) whether we are now alive or dead. """ - assert self.assy is not None #bruce 080227 guess (from docstring) [can fail, 080325, tom bug when updater turned on after separate @@@] + assert self.assy is not None #bruce 080227 guess (from docstring) + # [can fail, 080325, tom bug when updater turned on after separate @@@] # One thing we know is required: if self.atoms changes, invalidate self.atlist. - # This permits us to not store atom.index as undoable state, and to not update self.atpos before undo checkpoints. - # [bruce 060313] - self.invalidate_everything() # this is probably overkill, but its call of self.invalidate_atom_lists() is certainly needed + # This permits us to not store atom.index as undoable state, and to not update + # self.atpos before undo checkpoints. [bruce 060313] + self.invalidate_everything() # this is probably overkill, but its call + # of self.invalidate_atom_lists() is certainly needed self._colorfunc = None - del self._colorfunc #bruce 060308 precaution; might fix (or cause?) some "Undo in Extrude" bugs + del self._colorfunc #bruce 060308 precaution; might fix (or + # cause?) some "Undo in Extrude" bugs self._dispfunc = None del self._dispfunc @@ -355,20 +383,22 @@ class Chunk(NodeWithAtomContents, InvalMixin, # the destroy-like elements before the superclass implem.) return - def _undo_setattr_hotspot(self, hotspot, archive): #bruce 060404; 060410 use store_if_invalid to fix new bug 1829 + def _undo_setattr_hotspot(self, hotspot, archive): """ + [undo API method] + Undo is mashing changed state into lots of objects' attrs at once; - this lets us handle that specially, just for self.hotspot, but in unknown order (for now) - relative either to our attrs or other objects. + this lets us handle that specially, just for self.hotspot, but in + unknown order (for now) relative either to our attrs or other objects. """ + #bruce 060404; 060410 use store_if_invalid to fix new bug 1829 self.set_hotspot( hotspot, store_if_invalid = True) def _enable_deallocate_displist(self): - # to avoid duplicating this code; TODO: inline this method when it soon returns a constant True + del self res = debug_pref("GLPane: deallocate OpenGL display lists of killed chunks?", - Choice_boolean_True, - prefs_key = True, - non_debug = True ) + Choice_boolean_True, # False is only useful for debugging + prefs_key = True ) return res # == @@ -387,8 +417,10 @@ class Chunk(NodeWithAtomContents, InvalMixin, if not self.mticon: self.init_icons() self.init_InvalMixin() - ## dad = None #bruce 050216 removed dad from __init__ args, since no calls pass it - # and callers need to do more to worry about the location anyway (see comments above) + ## dad = None + #bruce 050216 removed dad from __init__ args, since no calls + # pass it and callers need to do more to worry about the + # location anyway (see comments above) _superclass.__init__(self, assy, name or gensym("Chunk", assy)) # atoms in a dictionary, indexed by atom.key @@ -433,8 +465,10 @@ class Chunk(NodeWithAtomContents, InvalMixin, # which is a reported bug which we hope to fix soon. self.memo_dict = {} - # for use by anything that wants to store its own memo data on us, using a key it's sure is unique [bruce 060608] - # (when we eventually have a real destroy method, it should zap this; maybe this will belong on class Node #e) + # for use by anything that wants to store its own memo data on us, + # using a key it's sure is unique [bruce 060608] + # (when we eventually have a real destroy method, it should zap + # this; maybe this will belong on class Node #e) # self.displist is allocated on demand by _get_displist [bruce 070523] @@ -794,7 +828,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, #Also assign the baseNames for the PAM atoms on the complementary #('mate') strand. strandAtomMate = atom.get_strand_atom_mate() - complementBaseName= getComplementSequence(str(baseName)) + complementBaseName = getComplementSequence(str(baseName)) if strandAtomMate is not None: strandAtomMate.setDnaBaseName(str(complementBaseName)) return @@ -1203,7 +1237,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, for extra_displist in self.extra_displists.values(): extra_displist.deallocate_displists() self.extra_displists = {} - if debug_pref("GLPane: print deleted display lists", Choice_boolean_False): #bruce 071205 made debug pref + if debug_pref("GLPane: print deleted display lists", Choice_boolean_False): + #bruce 071205 made debug pref print "fyi: deleted OpenGL display list %r belonging to %r" % (top, self) # keep this print around until this feature is tested on all platforms self.displist = None @@ -1343,11 +1378,16 @@ class Chunk(NodeWithAtomContents, InvalMixin, # make sure no other code forgot to call us and set it directly assert not 'hotspot' in self.__dict__.keys(), "bug in some unknown other code" if self._hotspot is not hotspot: - self.changed() #bruce 060324 fix bug 1532, and an unreported bug where this didn't mark file as modified + self.changed() + #bruce 060324 fix bug 1532, and an unreported bug where this + #didn't mark file as modified self._hotspot = hotspot - if not store_if_invalid: # (when that's true, it's important not to recompute self.hotspot, even in an assertion) - # now recompute self.hotspot from the new self._hotspot (to check whether it's valid) - self.hotspot # this has side effects we depend on! + if not store_if_invalid: + # (when that's true, it's important not to recompute self.hotspot, + # even in an assertion) + # now recompute self.hotspot from the new self._hotspot (to check + # whether it's valid) + self.hotspot # note: this has side effects we depend on! assert self.hotspot is hotspot or silently_fix_if_invalid, \ "getattr bug, or specified hotspot %s is invalid" % \ safe_repr(hotspot) @@ -1423,11 +1463,11 @@ class Chunk(NodeWithAtomContents, InvalMixin, self.hideicon.append( imagename_to_pixmap( "modeltree/" + name)) return - def node_icon(self, display_prefs): # bruce 050109 revised this [was seticon]; revised again 060608 - # Special case for protein icon. This only adds the icon to the MT. - # To add the protein icon for PM_SelectionListWidget, the attr iconPath - # was modified in isProteinChunk(). --Mark 2008-12-16. + def node_icon(self, display_prefs): if self.isProteinChunk(): + # Special case for protein icon (for MT only). + # (For PM_SelectionListWidget, the attr iconPath was modified in + # isProteinChunk() in separate code.) --Mark 2008-12-16. hd = get_display_mode_handler(diPROTEIN) if hd: return hd.get_icon(self.hidden) @@ -1436,13 +1476,12 @@ class Chunk(NodeWithAtomContents, InvalMixin, return self.hideicon[self.display] else: return self.mticon[self.display] - except IndexError: - # probably one of those new-fangled ChunkDisplayModes [bruce 060608] + # KLUGE: detect self.display being a ChunkDisplayMode [bruce 060608] hd = get_display_mode_handler(self.display) if hd: return hd.get_icon(self.hidden) - # hmm, some sort of bug + # else, some sort of bug return imagename_to_pixmap("modeltree/junk.png") pass @@ -1452,19 +1491,23 @@ class Chunk(NodeWithAtomContents, InvalMixin, """ Private method; should be the only way new atoms can be added to a Chunk - (except for optimized callers like Chunk.merge, and others with comments saying they inline it). - Add an existing atom (with no current Chunk, and with a valid literal + (except for optimized callers like Chunk.merge, and others with comments + saying they inline it). + + Add an existing atom (with no current Chunk, and with a valid literal .xyz field) to the Chunk self, doing necessary invals in self, but not yet giving the new atom an index in our curpos, basepos, etc (which will not yet include the new atom at all). - Details of invalidations: Curpos must be left alone (so as not + + Details of invalidations: Curpos must be left alone (so as not to forget the positions of other atoms); the other atom-position arrays (atpos, basepos) and atom lists (atlist) are defined to be complete, so they're invalidated, and so are whatever other attrs depend on them. In the future we might change this function to incrementally grow those arrays. This will be transparent to callers since they are now recomputed as needed by __getattr__. - (It's not worth tracking changes to the set of singlets in the mol, + + (It's not worth tracking changes to the set of singlets in the mol, so instead we recompute self.singlets and self.singlpos as needed.) """ ## atom.invalidate_bonds() # might not be needed @@ -1490,7 +1533,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, Private method; should be the only way atoms can be removed from a Chunk (except for optimized callers like Chunk.merge). - Remove atom from the Chunk self, preparing atom for being destroyed + + Remove atom from the Chunk self, preparing atom for being destroyed or for later addition to some other mol, doing necessary invals in self, and (for safety and possibly to break cycles of python refs) removing all connections from atom back to self. @@ -1559,8 +1603,9 @@ class Chunk(NodeWithAtomContents, InvalMixin, need = 1 try: del self.atlist - # this is what makes it ok for atom indices to be invalid, as they are when self.atoms changes, - # until self.atlist is next recomputed [bruce 060313 comment] + # this is what makes it ok for atom indices to be invalid, as + # they are when self.atoms changes, until self.atlist is next + # recomputed [bruce 060313 comment] except: pass else: @@ -1568,7 +1613,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, if need: # this causes trouble, not yet sure why: ## self.changed_attrs(['externs', 'atlist']) - ## AssertionError: validate_attr finds no attr 'externs' was saved, in <Chunk 'Ring Gear' (5167 atoms) at 0xd967440> + ## AssertionError: validate_attr finds no attr 'externs' was saved, + ## in <Chunk 'Ring Gear' (5167 atoms) at 0xd967440> # so do this instead: self.externs = self.atlist = -1 self.invalidate_attrs(['externs', 'atlist']) @@ -1606,7 +1652,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, self.invalidate_all_bonds() self.invalidate_atom_lists() # _undo_update depends on us calling this attrs = self.invalidatable_attrs() - # now this is done in that method: attrs.sort() # be deterministic even if it hides bugs for some orders + # now this is done in that method: + ## attrs.sort() # be deterministic even if it hides bugs for some orders for attr in attrs: self.invalidate_attr(attr) # (these might be sufficient: ['externs', 'atlist', 'atpos']) @@ -1616,7 +1663,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, def update_everything(self): attrs = self.invalidatable_attrs() - # now this is done in that method: attrs.sort() # be deterministic even if it hides bugs for some orders + # now this is done in that method: + ## attrs.sort() # be deterministic even if it hides bugs for some orders for attr in attrs: junk = getattr(self, attr) # don't actually remake display list, but next redraw will do that; @@ -1631,11 +1679,14 @@ class Chunk(NodeWithAtomContents, InvalMixin, One of self's atoms changed position; invalidate whatever we might own that depends on that. """ - # initial implem might be too conservative; should optimize, perhaps recode in a new Pyrex ChunkBase. - # Some code is copied from now-obsolete setatomposn; some of its comments might apply here as well. + # initial implem might be too conservative; should optimize, perhaps + # recode in a new Pyrex ChunkBase. Some code is copied from + # now-obsolete setatomposn; some of its comments might apply here as + # well. self.changed() self.havelist = 0 - self.invalidate_attr('atpos') #e should optim this ##k verify this also invals basepos, or add that to the arg of this call + self.invalidate_attr('atpos') #e should optim this + ##k verify this also invals basepos, or add that to the arg of this call return # for __getattr__, validate_attr, invalidate_attr, etc, see InvalMixin @@ -1698,7 +1749,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, def _get_polyhedron(self): # self.polyhedron return self.poly_evals_evecs_axis[0] -#bruce 060119 commenting these out since they are not used, though if we want them it's fine to add them back. +#bruce 060119 commenting these out since they are not used, +# though if we want them it's fine to add them back. #bruce 060608 renamed them with plural 's'. ## def _get_evals(self): # self.evals ## return self.poly_evals_evecs_axis[1] @@ -1724,7 +1776,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, self.havelist = 0 self.haveradii = 0 self.invalidate_attrs(['atlist', 'externs']) # invalidates everything, I think - assert not self.valid_attrs(), "full_inval_and_update forgot to invalidate something: %r" % self.valid_attrs() + assert not self.valid_attrs(), \ + "full_inval_and_update forgot to invalidate something: %r" % self.valid_attrs() # full update (but invals bonds): self.atpos # this invals all internal bonds (since it revises basecenter); we depend on that # self.atpos also recomputes some other things, but not the following -- do them all: @@ -1733,7 +1786,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, self.externs self.axis self.get_sel_radii_squared() - assert not self.invalid_attrs(), "full_inval_and_update forgot to update something: %r" % self.invalid_attrs() + assert not self.invalid_attrs(), \ + "full_inval_and_update forgot to update something: %r" % self.invalid_attrs() return # Primitive modifier methods will (more or less by convention) @@ -1758,7 +1812,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, Also set atom.index on each atom in the list, to its index in the list. """ atomitems = self.atoms.items() - atomitems.sort() # in order of atom keys; probably doesn't yet matter but makes order deterministic + atomitems.sort() + # in order of atom keys; probably doesn't yet matter, but makes order deterministic atlist = [atom for (key, atom) in atomitems] self.atlist = array(atlist, PyObject) #review: untested whether making it an array is good or bad for atom, i in zip(atlist, range(len(atlist))): @@ -1770,18 +1825,24 @@ class Chunk(NodeWithAtomContents, InvalMixin, # anyway we don't optim for that.) _inputs_for_basepos = ['atpos'] # also invalidated directly, but not often - def _recompute_atpos(self): #bruce 060308 major rewrite; #bruce 060313 splitting _recompute_atlist out of _recompute_atpos + def _recompute_atpos(self): """ recompute self.atpos and self.basepos and more; also change self's local coordinate system (used for basepos) [#doc more] """ - # Something must have been invalid to call us, so basepos must be invalid. So we needn't call changed_attr on it. + #bruce 060308 major rewrite + #bruce 060313 splitting _recompute_atlist out of _recompute_atpos + + # Something must have been invalid to call us, so basepos must be + # invalid. So we needn't call changed_attr on it. assert not self.__dict__.has_key('basepos') if self.assy is None: if debug_flags.atom_debug: - # [bruce comment 050702: this happens if you delete the chunk while dragging it by selatom in build mode] - print_compact_stack("atom_debug: fyi, recompute atpos called on killed mol %r: " % self) + # [bruce comment 050702: this happens if you delete the chunk + # while dragging it by selatom in build mode] + msg = "atom_debug: fyi, recompute atpos called on killed mol %r" % self + print_compact_stack(msg + ": ") # Optional debug code: # This might be called if basepos doesn't exist but atpos does. # I don't think that can happen, but if it can, I need to know. @@ -1793,7 +1854,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, ## print "fyi: _recompute_atpos sees %r already existing" % attr atlist = self.atlist # might call _recompute_atlist - atpos = map( lambda atom: atom.posn(), atlist ) # atpos, basepos, and atlist must be in same order + atpos = map( lambda atom: atom.posn(), atlist ) + # atpos, basepos, and atlist must be in same order atpos = A(atpos) # we must invalidate or fix self.atpos when any of our atoms' positions is changed! self.atpos = atpos @@ -1802,30 +1864,36 @@ class Chunk(NodeWithAtomContents, InvalMixin, self._recompute_average_position() # sets self.average_position from self.atpos self.basecenter = + self.average_position # not an invalidatable attribute - # unary '+' prevents mods to basecenter from affecting average_position; - # it might not be needed (that depends on Numeric.array += semantics). - # Note: basecenter is arbitrary, but should be somewhere near the atoms... - # except see set_basecenter_and_quat, used in extrudeMode -- it may be that it's not really arbitrary - # due to kluges in how that's used [still active as of 070411]. + # unary '+' prevents mods to basecenter from affecting + # average_position; it might not be needed (that depends on + # Numeric.array += semantics). + # Note: basecenter is arbitrary, but should be somewhere near the + # atoms... except see set_basecenter_and_quat, used in extrudeMode -- + # it may be that it's not really arbitrary due to kluges in how that's + # used [still active as of 070411]. if debug_messup_basecenter: # ... so this flag lets us try some other value to test that!! blorp = messupKey.next() self.basecenter += V(blorp, blorp, blorp) self.quat = Q(1,0,0,0) - # arbitrary value, except we assume it has this specific value to simplify/optimize the next line + # arbitrary value, except we assume it has this specific value to + # simplify/optimize the next line if self.atoms: self.basepos = atpos - self.basecenter - # set now (rather than when next needed) so it's still safe to assume self.quat == Q(1,0,0,0) + # set now (rather than when next needed) so it's still safe to + # assume self.quat == Q(1,0,0,0) else: self.basepos = [] # this has wrong type, so requires special code in mol.move etc - ###k Could we fix that by just assigning atpos to it (no elements, so should be correct)?? [bruce 060119 question] + ###k Could we fix that by just assigning atpos to it (no elements, + # so should be correct)?? [bruce 060119 question] assert len(self.basepos) == len(atlist) # note: basepos must be a separate (unshared) array object # (except when mol is frozen [which is no longer supported as of 060308]); - # as of 060308 atpos (when defined) is a separate array object, since curpos no longer exists. + # as of 060308 atpos (when defined) is a separate array object, + # since curpos no longer exists. self._changed_basecenter_or_quat_while_atoms_fixed() # (that includes self.changed_attr('basepos'), though an assert above # says that that would not be needed in this case.) @@ -2028,7 +2096,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, # piotr 080331 moved this assignment before visibility # and frustum culling tests self.glpane = glpane # needed for the edit method - Mark [2004-10-13] - # (and now also needed by BorrowerChunk during draw_dispdef's call of _dispfunc [bruce 060411]) + # (and now also needed by BorrowerChunk during draw_dispdef's call + # of _dispfunc [bruce 060411]) # ##e bruce 041109: couldn't we figure out self.glpane on the fly from self.dad? # (in getattr or in a special method) @@ -2073,7 +2142,9 @@ class Chunk(NodeWithAtomContents, InvalMixin, # to be sure that all objects are within the sphere. # piotr 080403: moved the correction here from GLPane.py is_chunk_visible = glpane.is_sphere_visible(self.bbox.center(), - self.bbox.scale() + (MAX_ATOM_SPHERE_RADIUS - BBOX_MIN_RADIUS) + 0.5) + self.bbox.scale() + + (MAX_ATOM_SPHERE_RADIUS - BBOX_MIN_RADIUS) + + 0.5 ) if indicate_overlapping_atoms and is_chunk_visible: self.draw_overlap_indicators_if_needed() @@ -2096,17 +2167,20 @@ class Chunk(NodeWithAtomContents, InvalMixin, # (presumably our arg, glpane, but we don't assume this right here) # how to find out when our display list next becomes invalid, # so it can know it needs to redraw us. - # (This is probably not actually needed at the moment, - # due to a special system used by self.changeapp() in place of self.track_change(), - # but it should be harmless.) + # (This is probably not actually needed at the moment, + # due to a special system used by self.changeapp() in place of + # self.track_change(), but it should be harmless.) self.track_use() drawLevel = self.assy.drawLevel # this might recompute it - # (if that happens and grabs the pref value, I think this won't subscribe our display list to it, - # since we're outside the begin/end for that, and that's good, since we include this in havelist - # instead, which avoids some unneeded redrawing, e.g. if pref changed and changed back while - # displaying a different Part. [bruce 060215]) - # update, bruce 080930: that point is probably moot, since drawLevel is part of havelist. + # (if that happens and grabs the pref value, I think this won't + # subscribe our display list to it, since we're outside the + # begin/end for that, and that's good, since we include this in + # havelist instead, which avoids some unneeded redrawing, e.g. if + # pref changed and changed back while displaying a different Part. + # [bruce 060215]) + # update, bruce 080930: that point is probably moot, since + # drawLevel is part of havelist. disp = self.get_dispdef(glpane) # piotr 080401: Moved it here, because disp is required by @@ -2186,8 +2260,9 @@ class Chunk(NodeWithAtomContents, InvalMixin, eltprefs = PeriodicTable.color_change_counter, PeriodicTable.rvdw_change_counter matprefs = drawing_globals.glprefs.materialprefs_summary() #bruce 051126 #bruce 060215 adding drawLevel to havelist - if self.havelist == (disp, eltprefs, matprefs, drawLevel): # value must agree with set of havelist, below - # our main display list is still valid -- use it + if self.havelist == (disp, eltprefs, matprefs, drawLevel): + # (note: value must agree with set of havelist, below) + # Our main display list is still valid -- use it. # Russ 081128: Switch from draw_dl() to draw() with selection arg. self.displist.draw(selected = self.picked) for extra_displist in self.extra_displists.itervalues(): @@ -2203,11 +2278,16 @@ class Chunk(NodeWithAtomContents, InvalMixin, #bruce 060608: record info to help per-chunk display modes # figure out whether they need to invalidate their memo data. if not self.havelist: - # only count when it was set to 0 externally, not just when it doesn't match and we reset it below. - # (Note: current code will also increment this every frame, when wantlist is false. - # I'm not sure what to do about that. Could we set it here to False rather than 0, so we can tell?? ##e) + # Only count when it was set to 0 externally, not + # just when it doesn't match and we reset it + # below. (Note: current code will also increment + # this every frame, when wantlist is false. I'm + # not sure what to do about that. Could we set it + # here to False rather than 0, so we can tell?? + # ##e) self._havelist_inval_counter += 1 - ##e in future we might also record eltprefs, matprefs, drawLevel (since they're stored in .havelist) + ##e in future we might also record eltprefs, matprefs, + ##drawLevel (since they're stored in .havelist) self.havelist = 0 #bruce 051209: this is now needed try: wantlist = not env.mainwindow().movie_is_playing #bruce 051209 @@ -2260,7 +2340,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, selected = self.picked, wantlist = wantlist) - pass # end of the case where our "main display list (and all extra lists) needs to be remade" + pass # end of the case where "main display list (and extra lists) needs to be remade" # REVIEW: is it ok that self.glname is exposed for the following # renderOverlayText drawing? Guess: yes, even though it means mouseover @@ -2272,14 +2352,17 @@ class Chunk(NodeWithAtomContents, InvalMixin, self.renderOverlayText(glpane) #@@ninad 070219 disabling the following-- - #self._draw_selection_frame(glpane, delegate_selection_wireframe, hd) #bruce 060608 moved this here + ## self._draw_selection_frame(glpane, delegate_selection_wireframe, hd) # piotr 080320 if hd: hd._f_drawchunk_realtime(glpane, self) - if self.hotspot is not None: # note, as of 050217 that can have side effects in getattr - self.overdraw_hotspot(glpane, disp) # only does anything for pastables as of 050316 (toplevel clipboard items) + if self.hotspot is not None: + # note: accessing self.hotspot can have side effects in getattr + self.overdraw_hotspot(glpane, disp) + # note: this only does anything for pastables + # (toplevel clipboard items) as of 050316 # russ 080409 Array to string formatting is slow, avoid it # when not needed. Use !=, not ==, to compare Numeric arrays. @@ -2347,9 +2430,10 @@ class Chunk(NodeWithAtomContents, InvalMixin, # Could we skip this if display mode "disp" never draws bonds? # No -- individual atoms might override that display mode. - # Someday we might decide to record whether that's true when recomputing externs, - # and to invalidate it as needed -- since it's rare for atoms to override display modes. - # Or we might even keep a list of all our atoms which override our display mode. ###e + # Someday we might decide to record whether that's true when + # recomputing externs, and to invalidate it as needed -- since it's + # rare for atoms to override display modes. Or we might even keep a + # list of all our atoms which override our display mode. ###e # [bruce 050513 comment] if draw_external_bonds and self.externs: self._draw_external_bonds(glpane, disp, drawLevel, is_chunk_visible) @@ -2471,7 +2555,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, repeated_bonds_dict[id(bond)] = bond if frustum_culling_for_external_bonds: # bond frustum culling test piotr 080401 - ### REVIEW: efficient under all settings of debug_prefs?? [bruce 080702 question] + ### REVIEW: efficient under all settings of debug_prefs?? + # [bruce 080702 question] c1, c2, radius = bond.bounding_lozenge() if not glpane.is_lozenge_visible(c1, c2, radius): continue # skip the bond drawing if culled @@ -2487,7 +2572,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, return # from _draw_external_bonds -## def _draw_selection_frame(self, glpane, delegate_selection_wireframe, hd): #bruce 060608 split this out of self.draw +## def _draw_selection_frame(self, glpane, delegate_selection_wireframe, hd): ## "[private submethod of self.draw]" ## if self.picked: ## if not delegate_selection_wireframe: @@ -2596,13 +2681,13 @@ class Chunk(NodeWithAtomContents, InvalMixin, if delegate_draw_chunk: hd._f_drawchunk(self.glpane, self) else: - self.standard_draw_chunk(glpane, disp0) + self._standard_draw_chunk(glpane, disp0) # draw the individual atoms and internal bonds (if desired) if delegate_draw_atoms: pass # nothing for this is implemented, or yet needed [as of bruce 060608] else: - self.standard_draw_atoms(glpane, disp0) + self._standard_draw_atoms(glpane, disp0) return def highlight_color_for_modkeys(self, modkeys): @@ -2757,22 +2842,28 @@ class Chunk(NodeWithAtomContents, InvalMixin, return - def standard_draw_chunk(self, glpane, disp0, highlighted = False): + def _standard_draw_chunk(self, glpane, disp0, highlighted = False): """ - [private submethod of self.draw:] + [private submethod of self.draw] Draw the standard representation of this chunk as a whole (except for chunk selection wireframe), - as if self's display mode was disp0; - this occurs inside our local coordinate system and display-list-making, - and it doesn't occur if chunk drawing is delegated to our display mode. - - @note: as of 080605 nothing is ever drawn for a chunk as a whole, - so this method does nothing (in this class or any subclass). - That might change, e.g. if we made chunks able to show their - axes, name, bbox, etc. - """ - #bruce 060608 split this out of _draw_for_main_display_list + as if self's display mode was disp0 (not a whole-chunk display mode). + + This is run inside our local coordinate system and display-list-making. + + It is not run if chunk drawing is delegated to a whole-chunk display mode. + + @note: as of 080605 or before, this is always a noop, but is kept around + since it might be used someday (see code comments) + """ + # note: we're keeping this for future expansion even though it's + # always a noop at the moment (even in subclasses). + # It's not used for whole-chunk display styles, but it might be used + # someday, e.g. to let chunks show their axes, name, bbox, etc, + # or possibly to help implement a low-level-of-detail form. + # [bruce 090113 comment] + del glpane, disp0, highlighted return def drawing_color(self): #bruce 080210 split this out, used in Atom.drawing_color @@ -2780,8 +2871,10 @@ class Chunk(NodeWithAtomContents, InvalMixin, Return the color tuple to use for drawing self, or None if per-atom colors should be used. """ - if self.picked and not (drawing_globals.allow_color_sorting and drawing_globals.use_color_sorted_dls): - #bruce disable this case when using drawing_globals.use_color_sorted_dls + if self.picked and not \ + (drawing_globals.allow_color_sorting and + drawing_globals.use_color_sorted_dls ) : + #bruce disabled this case when using drawing_globals.use_color_sorted_dls # since they provide a better way (fixes "stuck green" bug.) #ninad070405 Following draws the chunk as a colored selection @@ -2803,7 +2896,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, """ return color - def standard_draw_atoms(self, glpane, disp0): #bruce 060608 split this out of _draw_for_main_display_list + def _standard_draw_atoms(self, glpane, disp0): """ [private submethod of self.draw:] @@ -2812,12 +2905,16 @@ class Chunk(NodeWithAtomContents, InvalMixin, this occurs inside our local coordinate system and display-list-making; it doesn't occur if atom drawing is delegated to our display mode. """ + #bruce 060608 split this out of _draw_for_main_display_list drawLevel = self.assy.drawLevel drawn = {} ## self.externs = [] # bruce 050513 removing this - # bruce 041014 hack for extrude -- use _colorfunc if present [part 1; optimized 050513] - _colorfunc = self._colorfunc # might be None [as of 050524 we supply a default so it's always there] - _dispfunc = self._dispfunc #bruce 060411 hack for BorrowerChunk, might be more generally useful someday + # bruce 041014 hack for extrude -- use _colorfunc if present + # [part 1; optimized 050513] + _colorfunc = self._colorfunc # might be None + # [as of 050524 we supply a default so it's always there] + _dispfunc = self._dispfunc + #bruce 060411 hack for BorrowerChunk, might be more generally useful someday atomcolor = self.drawing_color() # None or a color # bruce 080210 bugfix (predicted) [part 1 of 2]: @@ -2827,24 +2924,27 @@ class Chunk(NodeWithAtomContents, InvalMixin, bondcolor = atomcolor # never changed below - for atom in self.atoms.itervalues(): #bruce 050513 using itervalues here (probably safe, speed is needed) + for atom in self.atoms.itervalues(): + #bruce 050513 using itervalues here (probably safe, speed is needed) try: color = atomcolor # might be modified before use disp = disp0 # might be modified before use - # bruce 041014 hack for extrude -- use _colorfunc if present [part 2; optimized 050513] + # bruce 041014 hack for extrude -- use _colorfunc if present + # [part 2; optimized 050513] if _colorfunc is not None: try: color = _colorfunc(atom) # None or a color except: - print_compact_traceback("bug in _colorfunc for %r and %r: " % (self, atom)) #bruce 060411 added errmsg + print_compact_traceback("bug in _colorfunc for %r and %r: " % (self, atom)) _colorfunc = None # report the error only once per displist-redraw color = None else: if color is None: color = atomcolor # bruce 080210 bugfix (predicted) [part 2 of 2] - #bruce 060411 hack for BorrowerChunk; done here and in this way in order to not make - # ordinary drawing inefficient, and to avoid duplicating this entire method: + #bruce 060411 hack for BorrowerChunk; done here and + # in this way in order to not make ordinary drawing + # inefficient, and to avoid duplicating this entire method: if _dispfunc is not None: try: disp = _dispfunc(atom) @@ -2875,8 +2975,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, # To make this safe, we'd need to not recompute externs here, # but that should be ok since they're computed separately anyway now. # So I'm removing that now, and doing this optim. - ###e (I might need to specialcase it for singlets so their bond-valence number is still drawn...) - # [bruce 050513] + ###e (I might need to specialcase it for singlets so their + # bond-valence number is still drawn...) [bruce 050513] #bruce 080212: this optim got a lot less effective since a few CPK bonds # are now also drawn (though most are not). @@ -2898,18 +2998,17 @@ class Chunk(NodeWithAtomContents, InvalMixin, # exception in drawing one atom. Ignore it and try to draw the # other atoms. #e In future, draw a bug-symbol in its place. print_compact_traceback("exception in drawing one atom or bond ignored: ") - # (this might mean some externs are missing; never mind that for now.) [bruce 050513 -- not anymore] try: print "current atom was:", atom except: print "current atom was... exception when printing it, discarded" try: - atom_source = atom._source # optional atom-specific debug info + atom_source = atom._f_source # optional atom-specific debug info except AttributeError: pass else: print "Source of current atom:", atom_source - return # from standard_draw_atoms (submethod of _draw_for_main_display_list) + return # from _standard_draw_atoms (submethod of _draw_for_main_display_list) def overdraw_hotspot(self, glpane, disp): #bruce 050131 """ @@ -2987,9 +3086,11 @@ class Chunk(NodeWithAtomContents, InvalMixin, # the hotspot to be set for this chunk (which is being read from an mmp file) (hs_num,) = val.split() hs = interp.atom(hs_num) - self.set_hotspot(hs) # this assertfails if hotspot is invalid [#k does caller handle that? ####@@@@] + self.set_hotspot(hs) + # this assertfails if hotspot is invalid [review: does caller handle that?] elif key == ['color']: #bruce 050505 - # val should be 3 decimal ints from 0-255; colors of None are not saved since they're the default + # val should be 3 decimal ints from 0-255; + # colors of None are not saved since they're the default r,g,b = map(int, val.split()) color = r/255.0, g/255.0, b/255.0 self.setcolor(color, repaint_in_MT = False) @@ -2997,7 +3098,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, # val should be one of the strings "", MODEL_PAM3, MODEL_PAM5; # if not recognized, use "" if val not in ("", MODEL_PAM3, MODEL_PAM5): - print "fyi: info chunk display_as_pam with unrecognized value %r" % (val,) # deferred_summary_message? + # maybe todo: use deferred_summary_message? + print "fyi: info chunk display_as_pam with unrecognized value %r" % (val,) val = "" #bruce 080523: silently ignore this, until the bug 2842 dust fully # settles. This is #1 of 2 changes (in the same commit) which @@ -3013,7 +3115,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, # val should be one of the strings "", MODEL_PAM3, MODEL_PAM5; # if not recognized, use "" if val not in ("", MODEL_PAM3, MODEL_PAM5): - print "fyi: info chunk save_as_pam with unrecognized value %r" % (val,) # deferred_summary_message? + # maybe todo: use deferred_summary_message? + print "fyi: info chunk save_as_pam with unrecognized value %r" % (val,) val = "" self.save_as_pam = val else: @@ -3381,17 +3484,20 @@ class Chunk(NodeWithAtomContents, InvalMixin, self.changed_attr('atpos', skip = ('bbox', 'basepos')) # we've moved one end of each external bond, so invalidate them... - # [bruce 050516 comment (95% sure it's right): note that we don't, and need not, inval internal bonds] + # [bruce 050516 comment (95% sure it's right): + # note that we don't, and need not, inval internal bonds] for bond in self.externs: bond.setup_invalidate() return def _set_atom_posns_from_atpos(self, atpos): #bruce 060308; revised 060313 """ - Set our atom's positions en masse from the given array, doing no chunk or bond invals - (caller must do whichever invals are needed, which depends on how the positions changed). - The array must be in the same order as self.atpos (its typical value, but we won't depend - on that and won't access or modify self.atpos) and self.atlist (which must already exist). + Set our atom's positions en masse from the given array, doing no chunk + or bond invals (caller must do whichever invals are needed, which + depends on how the positions changed). The array must be in the same + order as self.atpos (its typical value, but we won't depend on that + and won't access or modify self.atpos) and self.atlist (which must + already exist). """ assert self.__dict__.has_key('atlist') atlist = self.atlist @@ -3402,19 +3508,19 @@ class Chunk(NodeWithAtomContents, InvalMixin, def base_to_abs(self, anything): # bruce 041115 """ - map anything (which is accepted by quat.rot() and Numeric.array's '+' method) - from Chunk-relative coords to absolute coords; - guaranteed to never recompute basepos/atpos or modify the mol-relative - coordinate system it uses. Inverse of abs_to_base. + map anything (which is accepted by quat.rot() and Numeric.array's '+' + method) from Chunk-relative coords to absolute coords; guaranteed to + never recompute basepos/atpos or modify the mol-relative coordinate + system it uses. Inverse of abs_to_base. """ return self.basecenter + self.quat.rot( anything) def abs_to_base(self, anything): # bruce 041201 """ - map anything (which is accepted by quat.unrot() and Numeric.array's '-' method) - from absolute coords to mol-relative coords; - guaranteed to never recompute basepos/atpos or modify the mol-relative - coordinate system it uses. Inverse of base_to_abs. + map anything (which is accepted by quat.unrot() and Numeric.array's + '-' method) from absolute coords to mol-relative coords; guaranteed to + never recompute basepos/atpos or modify the mol-relative coordinate + system it uses. Inverse of base_to_abs. """ return self.quat.unrot( anything - self.basecenter) @@ -3425,23 +3531,24 @@ class Chunk(NodeWithAtomContents, InvalMixin, Change self's basecenter and quat to the specified values, as a way of moving self's atoms. - It's deprecated since basecenter and quat are replaced by - in-principle-arbitrary values every time certain recomputations are done - after self's geometry might have changed, but this method is only useful - if the caller knows what they are, and computes the new ones it wants - relative to what they are, which in practice means the caller must - be able to prevent modifications to self during an entire period - when it wants to be able to call this method repeatedly on self. - So it's much better to use self.pivot instead (or some combo of move, + It's deprecated since basecenter and quat are replaced by + in-principle-arbitrary values every time certain recomputations are + done after self's geometry might have changed, but this method is only + useful if the caller knows what they are, and computes the new ones it + wants relative to what they are, which in practice means the caller + must be able to prevent modifications to self during an entire period + when it wants to be able to call this method repeatedly on self. So + it's much better to use self.pivot instead (or some combo of move, rot, and pivot methods). """ # [written by bruce for extrude; moved into class Chunk by bruce 041104] # modified from mol.move and mol.rot as of 041015 night - self.basepos # bruce 050315 bugfix: recompute this if it's currently invalid! + self.basepos # recompute basepos if it's currently invalid # make sure mol owns its new basecenter and quat, # since it might destructively modify them later! self.basecenter = V(0,0,0) + basecenter - self.quat = Q(1,0,0,0) + quat #e +quat might be correct and faster... don't know; doesn't matter much + self.quat = Q(1,0,0,0) + quat + # review: +quat might be correct and faster... don't know; doesn't matter much self.bbox = None del self.bbox #e could optimize if quat is not changing self._changed_basecenter_or_quat_to_move_atoms() @@ -3576,8 +3683,10 @@ class Chunk(NodeWithAtomContents, InvalMixin, # REVIEW: do some inlines of changeapp need to do this too? # If they did, would that catch the redraws that currently # only Qt knows we need to do? [bruce 080305 question] - glpane = self.glpane # the last glpane that drew this chunk, or None if it was never drawn - # (if more than one can ever draw it at once, this code needs to be revised to scan them all ##k) + glpane = self.glpane + # the last glpane that drew this chunk, or None if it was never + # drawn (if more than one can ever draw it at once, this code + # needs to be revised to scan them all ##k) if glpane is not None: try: flag = glpane.wants_gl_update @@ -3601,7 +3710,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, """ Return the tooltip string for this chunk """ - #As of 2008-11-09, this is only implemented for a DnaStrand. + # As of 2008-11-09, this is only implemented for a DnaStrand. strand = self.getDnaStrand() toolTipInfoString = '' if strand: @@ -3613,19 +3722,22 @@ class Chunk(NodeWithAtomContents, InvalMixin, toolTipInfoString = segment.getDefaultToolTipInfo() return toolTipInfoString - - - def getinfo(self): - # Return information about the selected chunk for the msgbar [mark 2004-10-14] - - if self is self.assy.ppm: return + + def getinfo(self): # mark 2004-10-14 + """ + Return information about the selected chunk for the msgbar + """ + if self is self.assy.ppm: + return ele2Num = {} # Determine the number of element types in this Chunk. for a in self.atoms.values(): - if not ele2Num.has_key(a.element.symbol): ele2Num[a.element.symbol] = 1 # New element found - else: ele2Num[a.element.symbol] += 1 # Increment element + if not ele2Num.has_key(a.element.symbol): + ele2Num[a.element.symbol] = 1 # New element found + else: + ele2Num[a.element.symbol] += 1 # Increment element # String construction for each element to be displayed. natoms = self.natoms() # number of atoms in the chunk @@ -3636,19 +3748,19 @@ class Chunk(NodeWithAtomContents, InvalMixin, if item[0] == "X": # It is a Singlet nsinglets = int(item[1]) continue - else: eleStr = "[" + item[0] + ": " + str(item[1]) + "] " + else: + eleStr = "[" + item[0] + ": " + str(item[1]) + "] " einfo += eleStr - if nsinglets: # Add singlet info to end of info string - #bruce 041227 changed term "Singlets" to "Open bonds" + if nsinglets: eleStr = "[Open bonds: " + str(nsinglets) + "]" einfo += eleStr - natoms -= nsinglets # The number of real atoms in this chunk + natoms -= nsinglets # compute number of real atoms in this chunk minfo = "Chunk Name: [" + str (self.name) + "] Total Atoms: " + str(natoms) + " " + einfo - # ppm is self for next mol picked. + # set ppm to self for next mol picked. self.assy.ppm = self return minfo @@ -3663,11 +3775,14 @@ class Chunk(NodeWithAtomContents, InvalMixin, for a in self.atoms.itervalues(): if a.element.symbol == "X": stats.nsinglets +=1 - def pickatoms(self): # mark 060211. Could use a complementary unpickatoms() method. [not referring to the one in ops_select --bruce] + def pickatoms(self): # mark 060211. """ - Pick the atoms of self not already picked. Return the number of newly picked atoms. + Pick the atoms of self not already picked (selected). + Return the number of newly picked atoms. [overrides Node method] """ + # todo: Could use a complementary unpickatoms() method. [mark 060211] + # [fyi, that doesn't refer to the one in ops_select --bruce] self.assy.permit_pick_atoms() npicked = 0 for a in self.atoms.itervalues(): @@ -3675,7 +3790,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, if not a.picked: a.pick() if a.picked: - # Just in case it didn't get picked due to a selection filter. + # in case not picked due to selection filter npicked += 1 return npicked @@ -3693,10 +3808,11 @@ class Chunk(NodeWithAtomContents, InvalMixin, self.assy.permit_pick_parts() #bruce 050125 added this... hope it's ok! ###k ###@@@ # (might not be needed for other kinds of leaf nodes... not sure. [bruce 050131]) _superclass.pick(self) - #bruce 050308 comment: _superclass.pick (Node.pick) has ensured that we're in the current selection group, - # so it's correct to append to selmols, *unless* we recompute it now and get a version - # which already contains self. So, we'll maintain it iff it already exists. - # Let the Part figure out how best to do this. + #bruce 050308 comment: _superclass.pick (Node.pick) has ensured + # that we're in the current selection group, so it's correct to + # append to selmols, *unless* we recompute it now and get a version + # which already contains self. So, we'll maintain it iff it already + # exists. Let the Part figure out how best to do this. # [bruce 060130 cleaned this up, should be equivalent] if self.part: self.part.selmols_append(self) @@ -4077,10 +4193,13 @@ class Chunk(NodeWithAtomContents, InvalMixin, # Note: this must also be invalidated when one atom's display mode changes, # and it is, by atom.setDisplayStyle calling changeapp(1) on its chunk. disp = self.get_dispdef() ##e should caller pass this instead? - eltprefs = PeriodicTable.rvdw_change_counter # (color changes don't matter for this, unlike for havelist) - radiusprefs = Atom.selradius_prefs_values() #bruce 060317 -- include this in the tuple below, to fix bug 1639 + eltprefs = PeriodicTable.rvdw_change_counter + # (color changes don't matter for this, unlike for havelist) + radiusprefs = Atom.selradius_prefs_values() + #bruce 060317 -- include this in the tuple below, to fix bug 1639 if self.haveradii != (disp, eltprefs, radiusprefs): # value must agree with set, below - # don't have them, or have them for wrong display mode, or for wrong element-radius prefs + # don't have them, or have them for wrong display mode, or for + # wrong element-radius prefs try: res = self.compute_sel_radii_squared() except: @@ -4097,26 +4216,14 @@ class Chunk(NodeWithAtomContents, InvalMixin, else: return A( lis ) pass - - # Old methods for finding certain atoms or singlets - # [bruce 060313 removed even the commented-out forms, last present in rev. 1.109] - # - # [bruce 041207 comment: these [removed old methods] ought to be unified, and perhaps bugfixed. - # To help with this, I'm adding comments, listing their callers, - # and removing the ones with no callers. - # See also some relevant code used in extrudeMode.py, - # actually findHandles_exact in handles.py, - # which will be useful for postprocessing lists of atoms - # found by code like the following. - # ] - ## .... - + def nearSinglets(self, point, radius): # todo: rename """ return the bondpoints in the given sphere (point, radius), sorted by increasing distance from point """ # note: only used in AtomTypeDepositionTool (Build Atoms mode) + # (note: findHandles_exact in handles.py may be related code) if not self.singlets: return [] singlpos = self.singlpos # do this in advance, to help with debugging @@ -4141,7 +4248,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, return True def will_partly_copy_due_to_selatoms(self, sel): - assert 0, "should never be called, since a chunk does not *refer* to selatoms, or appear in atom.jigs" + assert 0, "should never be called, since a chunk does not " \ + "*refer* to selatoms, or appear in atom.jigs" return True # but if it ever is called, answer should be true def _copy_optional_attrs_to(self, numol): @@ -4163,7 +4271,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, # (but see existing copy code for self.user_specified_center) return - def copy_empty_shell_in_mapping(self, mapping): #bruce 070430 revised to honor mapping.assy + def _copy_empty_shell_in_mapping(self, mapping): """ [private method to help the public copy methods, all of which start with this except the deprecated mol.copy] @@ -4181,20 +4289,23 @@ class Chunk(NodeWithAtomContents, InvalMixin, Never refuses. Returns copy (a new chunk with no atoms). Ok to assume self has never yet been copied. """ + #bruce 070430 revised to honor mapping.assy numol = self.__class__(mapping.assy, self.name) #bruce 080316 Chunk -> self.__class__ (part of fixing this for Extrude of DnaGroup) - self.copy_copyable_attrs_to(numol) # copies .name (redundantly), .hidden, .display, .color... + self.copy_copyable_attrs_to(numol) + # copies .name (redundantly), .hidden, .display, .color... self._copy_optional_attrs_to(numol) mapping.record_copy(self, numol) return numol - def copy_full_in_mapping(self, mapping): # Chunk method [bruce 050526] #bruce 060308 major rewrite + def copy_full_in_mapping(self, mapping): # in class Chunk """ #doc; overrides Node method; only some atom copies get recorded in mapping (if we think it might need them) """ - numol = self.copy_empty_shell_in_mapping( mapping) + # bruce 050526; 060308 major rewrite + numol = self._copy_empty_shell_in_mapping( mapping) # now copy the atoms, all at once (including all their existing # singlets, even though those might get revised) # note: the following code is very similar to @@ -4213,14 +4324,11 @@ class Chunk(NodeWithAtomContents, InvalMixin, ndix[a.key] = na numol.invalidate_atom_lists() numol.atoms = nuatoms -## if 0: -## # I'm not sure how to make this correct, since it doesn't copy everything recomputed -## # when we recompute atlist/atpos/basepos; beside's it's often wasted work since caller plans to -## # move all the atoms after the copy, or so... so nevermind. -## numol.atlist = copy_val(self.atlist) -## numol.atpos = copy_val(self.atpos) # use copy_val in case length is 0 and type is unusual -## numol.basepos = copy_val(self.basepos) - + # note: we don't bother copying atlist, atpos, basepos, + # since it's hard to do correctly (e.g. not copying everything + # which depends on them would cause inval bugs), and it's wasted work + # for callers which plan to move all the atoms after + # the copy self._copy_atoms_handle_bonds_jigs( pairlis, ndix, mapping) # note: no way to handle hotspot yet, since how to do that might depend on whether # extern bonds are broken... so let's copy an explicit one, and tell the mapping @@ -4234,7 +4342,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, # we have an implicit but unambiguous hotspot: # might need to make it explicit in the copy [bruce 041123, revised 050524] copy_of_hotspot = ndix[self.singlets[0].key] - mapping.do_at_end( lambda ch = copy_of_hotspot, numol = numol: numol._preserve_implicit_hotspot(ch) ) + mapping.do_at_end( lambda ch = copy_of_hotspot, numol = numol: + numol._preserve_implicit_hotspot(ch) ) return numol # from copy_full_in_mapping def _copy_atoms_handle_bonds_jigs(self, pairlis, ndix, mapping): @@ -4245,11 +4354,12 @@ class Chunk(NodeWithAtomContents, InvalMixin, """ origid_to_copy = mapping.origid_to_copy extern_atoms_bonds = mapping.extern_atoms_bonds - #e could be integrated with mapping.do_at_end, + # maybe: could be integrated with mapping.do_at_end, # but it's probably better not to, so as to specialize it for speed; # even so, could clean this up to bond externs as soon as 2nd atom seen # (which might be more efficient, though that doesn't matter much - # since externs should not be too frequent); could do all this in a Bond method #e + # since externs should not be too frequent); + # could do all this in a Bond method for (a, na) in pairlis: if a.jigs: # a->na mapping might be needed if those jigs are copied, @@ -4266,8 +4376,9 @@ class Chunk(NodeWithAtomContents, InvalMixin, # [note, this code is being copied into the old .copy() # method too, by bruce 050715] if a.key < a2key: - # arbitrary condition which is true for exactly one ordering of the atoms; - # note both keys are for original atoms (it would also work if both were from + # arbitrary condition which is true for exactly one + # ordering of the atoms; note both keys are for + # original atoms (it would also work if both were from # copied atoms, but not if they were mixed) bond_copied_atoms(na, ndix[a2key], b, a) else: @@ -4279,7 +4390,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, extern_atoms_bonds.append( (a,b) ) # it's ok if this list has several entries for one 'a' origid_to_copy[id(a)] = na - # a->na mapping will be needed outside this method, to copy or break this bond + # a->na mapping will be needed outside this method, + # to copy or break this bond pass pass return # from _copy_atoms_handle_bonds_jigs @@ -4291,7 +4403,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, [#e hotspot? fix later if needed, hopefully by replacing that concept with a jig (see comment below for ideas).] """ - numol = self.copy_empty_shell_in_mapping( mapping) + numol = self._copy_empty_shell_in_mapping( mapping) all = list(atoms) for a in atoms: all.extend(a.singNeighbors()) @@ -4314,7 +4426,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, # implicit) or its base atom, put them in mapping, # and register some other func (than the one copy_in_mapping does) # to fix it up at the end. - # Could do this uniformly in copy_empty_shell_in_mapping, + # Could do this uniformly in _copy_empty_shell_in_mapping, # and here just be sure to tell mapping.record_copy. # # (##e But really we ought to simplify all this code by just @@ -4430,7 +4542,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, numol.name = newname #end 050531 kluges nuatoms = {} - for a in self.atlist: # 060308 changed similarly to copy_full_in_mapping (shares some code with it) + for a in self.atlist: + # 060308 changed similarly to copy_full_in_mapping (shares some code with it) na = a.copy() na.molecule = numol # no need for _changed_parent_Atoms[na.key] = na #bruce 060322 nuatoms[na.key] = na @@ -4461,8 +4574,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, else: # external bond - after loop done, make a singlet in the copy extern_atoms_bonds.append( (a,b) ) # ok if several times for one 'a' - if extern_atoms_bonds: - pass ## print "fyi: mol.copy didn't copy %d extern bonds..." % len(extern_atoms_bonds) + ## if extern_atoms_bonds: + ## print "fyi: mol.copy didn't copy %d extern bonds..." % len(extern_atoms_bonds) copied_hotspot = self.hotspot # might be None if cauterize: # do something about non-copied bonds (might be useful for extrude) @@ -4649,12 +4762,6 @@ class Chunk(NodeWithAtomContents, InvalMixin, pass # end of class Chunk -##Chunk = molecule #bruce 051227 permit this synonym; for A8 we'll probably rename the class this way -## -##del molecule #bruce 071113 along with revising all uses to refer to Chunk (except the classname itself) -## -### Note: we can't rename the class until all string literals 'molecule' are reviewed. [has been done now] - # == # The chunk _nullMol is never part of an assembly, but serves as the chunk |