diff options
author | Bruce Smith <bruce@nanorex.com> | 2009-01-16 06:33:26 +0000 |
---|---|---|
committer | Bruce Smith <bruce@nanorex.com> | 2009-01-16 06:33:26 +0000 |
commit | 344ed9faba9721c54efaf286df6ef64c458a2ec6 (patch) | |
tree | 22106f120a035b20174453cc0a0b83ca34ff239c | |
parent | 1c39d3fbfc5b8b7a784742d9d05b9f4bcdc3332c (diff) | |
download | nanoengineer-344ed9faba9721c54efaf286df6ef64c458a2ec6.tar.gz nanoengineer-344ed9faba9721c54efaf286df6ef64c458a2ec6.zip |
split Chunk_Dna_methods out of class Chunk; misc related cleanup
-rwxr-xr-x | cad/src/ExecSubDir.py | 3 | ||||
-rw-r--r-- | cad/src/cnt/commands/BuildNanotube/BuildNanotube_EditCommand.py | 9 | ||||
-rw-r--r-- | cad/src/cnt/commands/EditNanotube/EditNanotube_EditCommand.py | 9 | ||||
-rwxr-xr-x | cad/src/commands/SelectChunks/SelectChunks_Command.py | 15 | ||||
-rw-r--r-- | cad/src/dna/commands/BuildDna/BuildDna_EditCommand.py | 9 | ||||
-rw-r--r-- | cad/src/dna/commands/DnaSegment/DnaSegment_EditCommand.py | 13 | ||||
-rw-r--r-- | cad/src/dna/commands/DnaStrand/DnaStrand_EditCommand.py | 14 | ||||
-rw-r--r-- | cad/src/dna/commands/MakeCrossovers/ListWidgetItems_GraphicsMode_Mixin.py | 10 | ||||
-rw-r--r-- | cad/src/model/Chunk_Dna_methods.py | 651 | ||||
-rwxr-xr-x | cad/src/model/chunk.py | 720 | ||||
-rwxr-xr-x | cad/src/ne1_ui/MWsemantics.py | 16 |
11 files changed, 807 insertions, 662 deletions
diff --git a/cad/src/ExecSubDir.py b/cad/src/ExecSubDir.py index edbda287e..f6d6a3bee 100755 --- a/cad/src/ExecSubDir.py +++ b/cad/src/ExecSubDir.py @@ -27,12 +27,11 @@ and it will get it right. @copyright: 2007 Nanorex, Inc. See LICENSE file for details. """ - import sys if (__name__ == '__main__'): if (len(sys.argv) < 2): - print >>sys.stderr, "usage: %s fileToRun.py" % sys.argv[0] + print >> sys.stderr, "usage: %s fileToRun.py" % sys.argv[0] sys.exit(1) execfile(sys.argv[1]) diff --git a/cad/src/cnt/commands/BuildNanotube/BuildNanotube_EditCommand.py b/cad/src/cnt/commands/BuildNanotube/BuildNanotube_EditCommand.py index 1153ba961..62e5cbcfd 100644 --- a/cad/src/cnt/commands/BuildNanotube/BuildNanotube_EditCommand.py +++ b/cad/src/cnt/commands/BuildNanotube/BuildNanotube_EditCommand.py @@ -1,10 +1,10 @@ -# Copyright 2007-2008 Nanorex, Inc. See LICENSE file for details. +# Copyright 2007-2009 Nanorex, Inc. See LICENSE file for details. """ BuildNanotube_EditCommand.py @author: Ninad @version: $Id$ -@copyright: 2007-2008 Nanorex, Inc. See LICENSE file for details. +@copyright: 2007-2009 Nanorex, Inc. See LICENSE file for details. History: Ninad 2008-01-11: Created @@ -96,7 +96,7 @@ class BuildNanotube_EditCommand(EditCommand): def makeMenus(self): """ Create context menu for this command. (Build Nanotube mode) - @see: chunk.make_glpane_context_menu_items + @see: chunk.make_glpane_cmenu_items @see: EditNanotube_EditCommand.makeMenus """ if not hasattr(self, 'graphicsMode'): @@ -122,6 +122,5 @@ class BuildNanotube_EditCommand(EditCommand): highlightedChunk = chunk1 if highlightedChunk is not None: - highlightedChunk.make_glpane_context_menu_items(self.Menu_spec, - command = self) + highlightedChunk.make_glpane_cmenu_items(self.Menu_spec, self) return diff --git a/cad/src/cnt/commands/EditNanotube/EditNanotube_EditCommand.py b/cad/src/cnt/commands/EditNanotube/EditNanotube_EditCommand.py index 3fbc04388..c68b8550d 100644 --- a/cad/src/cnt/commands/EditNanotube/EditNanotube_EditCommand.py +++ b/cad/src/cnt/commands/EditNanotube/EditNanotube_EditCommand.py @@ -1,10 +1,10 @@ -# Copyright 2008 Nanorex, Inc. See LICENSE file for details. +# Copyright 2008-2009 Nanorex, Inc. See LICENSE file for details. """ EditNanotube_EditCommand.py @author: Ninad, Mark -@copyright: 2008 Nanorex, Inc. See LICENSE file for details. @version: $Id$ +@copyright: 2008-2009 Nanorex, Inc. See LICENSE file for details. While in this command, user can (a) Highlight and then left drag the resize handles located at the @@ -701,7 +701,7 @@ class EditNanotube_EditCommand(State_preMixin, EditCommand): def makeMenus(self): """ - Create context menu for this command. (Build Dna mode) + Create context menu for this command. """ if not hasattr(self, 'graphicsMode'): return @@ -727,6 +727,5 @@ class EditNanotube_EditCommand(State_preMixin, EditCommand): if highlightedChunk is None: return - highlightedChunk.make_glpane_context_menu_items(self.Menu_spec, - command = self) + highlightedChunk.make_glpane_cmenu_items(self.Menu_spec, self) return diff --git a/cad/src/commands/SelectChunks/SelectChunks_Command.py b/cad/src/commands/SelectChunks/SelectChunks_Command.py index c7ac766dd..f6b01cc75 100755 --- a/cad/src/commands/SelectChunks/SelectChunks_Command.py +++ b/cad/src/commands/SelectChunks/SelectChunks_Command.py @@ -104,17 +104,17 @@ class SelectChunks_Command(Select_Command): self.__previous_command_stack_change_indicator = indicator self.assy.selectChunksWithSelAtoms_noupdate() - + return call_makeMenus_for_each_event = True - #bruce 050914 enable dynamic context menus - # [fixes an unreported bug analogous to 971] + def makeMenus(self): # mark 060303. """ Make the GLPane context menu for Select Chunks. """ self.Menu_spec = [] + selobj = self.glpane.selobj highlightedChunk = None if isinstance(selobj, Chunk): @@ -133,8 +133,7 @@ class SelectChunks_Command(Select_Command): ] if highlightedChunk is not None: - highlightedChunk.make_glpane_context_menu_items(self.Menu_spec, - command = self) + highlightedChunk.make_glpane_cmenu_items(self.Menu_spec, self) return _numberOfSelectedChunks = self.o.assy.getNumberOfSelectedChunks() @@ -145,8 +144,7 @@ class SelectChunks_Command(Select_Command): elif _numberOfSelectedChunks == 1: selectedChunk = self.o.assy.selmols[0] - selectedChunk.make_glpane_context_menu_items(self.Menu_spec, - command = self) + selectedChunk.make_glpane_cmenu_items(self.Menu_spec, self) elif _numberOfSelectedChunks > 1: self._makeEditContextMenus() self.Menu_spec.extend([None]) # inserts separator @@ -167,7 +165,6 @@ class SelectChunks_Command(Select_Command): """ Insert the 'standard' menu items for the GLPane context menu. """ - self.Menu_spec.extend( [('Edit Color Scheme...', self.w.colorSchemeCommand)]) @@ -196,7 +193,7 @@ class SelectChunks_Command(Select_Command): mol.invalidate_everything() for atm in self.o.assy.selatoms.values(): atm.invalidate_everything() - + return def update_selection(self): #bruce 041115 (debugging method) """ diff --git a/cad/src/dna/commands/BuildDna/BuildDna_EditCommand.py b/cad/src/dna/commands/BuildDna/BuildDna_EditCommand.py index 0c07fe72e..9d48777f4 100644 --- a/cad/src/dna/commands/BuildDna/BuildDna_EditCommand.py +++ b/cad/src/dna/commands/BuildDna/BuildDna_EditCommand.py @@ -1,10 +1,10 @@ -# Copyright 2007-2008 Nanorex, Inc. See LICENSE file for details. +# Copyright 2007-2009 Nanorex, Inc. See LICENSE file for details. """ BuildDna_EditCommand.py @author: Ninad @version: $Id$ -@copyright: 2007-2008 Nanorex, Inc. See LICENSE file for details. +@copyright: 2007-2009 Nanorex, Inc. See LICENSE file for details. History: Ninad 2008-01-11: Created @@ -375,7 +375,7 @@ class BuildDna_EditCommand(EditCommand): def makeMenus(self): """ Create context menu for this command. (Build Dna mode) - @see: chunk.make_glpane_context_menu_items + @see: chunk.make_glpane_cmenu_items @see: DnaSegment_EditCommand.makeMenus """ if not hasattr(self, 'graphicsMode'): @@ -401,7 +401,6 @@ class BuildDna_EditCommand(EditCommand): highlightedChunk = chunk1 if highlightedChunk is not None: - highlightedChunk.make_glpane_context_menu_items(self.Menu_spec, - command = self) + highlightedChunk.make_glpane_cmenu_items(self.Menu_spec, self) return diff --git a/cad/src/dna/commands/DnaSegment/DnaSegment_EditCommand.py b/cad/src/dna/commands/DnaSegment/DnaSegment_EditCommand.py index c674ad5d0..d515f9fc2 100644 --- a/cad/src/dna/commands/DnaSegment/DnaSegment_EditCommand.py +++ b/cad/src/dna/commands/DnaSegment/DnaSegment_EditCommand.py @@ -1,4 +1,4 @@ -# Copyright 2008 Nanorex, Inc. See LICENSE file for details. +# Copyright 2008-2009 Nanorex, Inc. See LICENSE file for details. """ DnaSegment_EditCommand provides a way to edit an existing DnaSegment. @@ -23,8 +23,8 @@ While in this command, user can @author: Ninad -@copyright: 2008 Nanorex, Inc. See LICENSE file for details. @version: $Id$ +@copyright: 2008-2009 Nanorex, Inc. See LICENSE file for details. History: Ninad 2008-01-18: Created @@ -1191,7 +1191,7 @@ class DnaSegment_EditCommand(State_preMixin, EditCommand): def makeMenus(self): """ - Create context menu for this command. (Build Dna mode) + Create context menu for this command. """ if not hasattr(self, 'graphicsMode'): return @@ -1218,7 +1218,9 @@ class DnaSegment_EditCommand(State_preMixin, EditCommand): return if self.hasValidStructure(): - + ### REVIEW: these early returns look wrong, since they skip running + # the subsequent call of highlightedChunk.make_glpane_cmenu_items + # for no obvious reason. [bruce 090114 comment, in two commands] dnaGroup = self.struct.parent_node_of_class(self.assy.DnaGroup) if dnaGroup is None: return @@ -1230,6 +1232,5 @@ class DnaSegment_EditCommand(State_preMixin, EditCommand): self.Menu_spec.append(item) return - highlightedChunk.make_glpane_context_menu_items(self.Menu_spec, - command = self) + highlightedChunk.make_glpane_cmenu_items(self.Menu_spec, self) diff --git a/cad/src/dna/commands/DnaStrand/DnaStrand_EditCommand.py b/cad/src/dna/commands/DnaStrand/DnaStrand_EditCommand.py index c374d313f..a5f56eb00 100644 --- a/cad/src/dna/commands/DnaStrand/DnaStrand_EditCommand.py +++ b/cad/src/dna/commands/DnaStrand/DnaStrand_EditCommand.py @@ -1,8 +1,8 @@ -# Copyright 2008 Nanorex, Inc. See LICENSE file for details. +# Copyright 2008-2009 Nanorex, Inc. See LICENSE file for details. """ @author: Ninad -@copyright: 2008 Nanorex, Inc. See LICENSE file for details. -@version:$Id$ +@version: $Id$ +@copyright: 2008-2009 Nanorex, Inc. See LICENSE file for details. History: Created on 2008-02-14 @@ -967,7 +967,7 @@ class DnaStrand_EditCommand(State_preMixin, EditCommand): def makeMenus(self): """ - Create context menu for this command. (Build Dna mode) + Create context menu for this command. """ if not hasattr(self, 'graphicsMode'): return @@ -994,6 +994,9 @@ class DnaStrand_EditCommand(State_preMixin, EditCommand): return if self.hasValidStructure(): + ### REVIEW: these early returns look wrong, since they skip running + # the subsequent call of highlightedChunk.make_glpane_cmenu_items + # for no obvious reason. [bruce 090114 comment, in two commands] if (self.struct is highlightedChunk) or \ (self.struct is highlightedChunk.parent_node_of_class( self.assy.DnaStrand)): @@ -1012,6 +1015,5 @@ class DnaStrand_EditCommand(State_preMixin, EditCommand): self.Menu_spec.append(item) return - highlightedChunk.make_glpane_context_menu_items(self.Menu_spec, - command = self) + highlightedChunk.make_glpane_cmenu_items(self.Menu_spec, self) diff --git a/cad/src/dna/commands/MakeCrossovers/ListWidgetItems_GraphicsMode_Mixin.py b/cad/src/dna/commands/MakeCrossovers/ListWidgetItems_GraphicsMode_Mixin.py index 657f48521..02313a86a 100644 --- a/cad/src/dna/commands/MakeCrossovers/ListWidgetItems_GraphicsMode_Mixin.py +++ b/cad/src/dna/commands/MakeCrossovers/ListWidgetItems_GraphicsMode_Mixin.py @@ -1,9 +1,9 @@ -# Copyright 2008 Nanorex, Inc. See LICENSE file for details. +# Copyright 2008-2009 Nanorex, Inc. See LICENSE file for details. """ @author: Ninad -@copyright: 2008 Nanorex, Inc. See LICENSE file for details. -@version:$Id$ +@version: $Id$ +@copyright: 2008-2009 Nanorex, Inc. See LICENSE file for details. History: @@ -31,8 +31,8 @@ class ListWidgetItems_GraphicsMode_Mixin: def chunkLeftUp(self, a_chunk, event): """ - Overrides superclass method. If add or remove segmets tool is active, - upon leftUp , when this method gets called, it modifies the list + Overrides superclass method. If add or remove segments tool is active, + upon leftUp, when this method gets called, it modifies the list of segments by self.command. @see: self.update_cursor_for_no_MB() @see: self.end_selection_from_GLPane() diff --git a/cad/src/model/Chunk_Dna_methods.py b/cad/src/model/Chunk_Dna_methods.py new file mode 100644 index 000000000..c802aff97 --- /dev/null +++ b/cad/src/model/Chunk_Dna_methods.py @@ -0,0 +1,651 @@ +# Copyright 2007-2009 Nanorex, Inc. See LICENSE file for details. +""" +Chunk_Dna_methods.py -- dna-related methods for class Chunk + +@author: Bruce, Ninad +@version: $Id$ +@copyright: 2007-2009 Nanorex, Inc. See LICENSE file for details. + +History: + +Bruce 090115 split this out of class Chunk. (Not long or old enough +for svn copy to be worthwhile, so see chunk.py for older svn history.) +""" + +import re + +from utilities.constants import noop +from utilities.constants import MODEL_PAM3, MODEL_PAM5 + +from utilities.debug import print_compact_stack + +from dna.model.Dna_Constants import getComplementSequence + +from operations.bond_chains import grow_directional_bond_chain + + +class Chunk_Dna_methods: # REVIEW: inherit NodeWithAtomContent to mollify pylint? + """ + Dna-related methods to be mixed in to class Chunk. + """ + # Note: some of the methods in this class are never used except on + # Dna-related subclasses of Chunk, and should be moved into those + # subclasses. Unfortunately it's not trivial to figure out for which ones + # that's guaranteed to be the case. + + + # PAM3+5 attributes (these only affect PAM atoms in self, if any): + # + # ### REVIEW [bruce 090115]: can some of these be deprecated and removed? + # + # self.display_as_pam can be MODEL_PAM3 or MODEL_PAM5 to force conversion on input + # to the specified PAM model for display and editing of self, or can be + # "" to use global preference settings. (There is no value which always + # causes no conversion, but there may be preference settings which disable + # ever doing conversion. But in practice, a PAM chunk will be all PAM3 or + # all PAM5, so this can be set to the model the chunk uses to prevent + # conversion for that chunk.) + # + # The value MODEL_PAM3 implies preservation of PAM5 data when present + # (aka "pam3+5" or "pam3plus5"). The allowed values are "", MODEL_PAM3, MODEL_PAM5. + # + # self.save_as_pam can be MODEL_PAM3 or MODEL_PAM5 to force conversion on save + # to the specified PAM model. When not set, global settings or save + # 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 + + 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) + + # this mixin's contribution to Chunk.copyable_attrs + _dna_copyable_attrs = ('display_as_pam', 'save_as_pam', ) + + + def invalidate_ladder(self): #bruce 071203 + """ + Subclasses which have a .ladder attribute + should call its ladder_invalidate_if_not_disabled method. + """ + return + + def invalidate_ladder_and_assert_permitted(self): #bruce 080413 + """ + Subclasses which have a .ladder attribute + should call its ladder_invalidate_and_assert_permitted method. + """ + return + + def in_a_valid_ladder(self): #bruce 071203 + """ + Is this chunk a rail of a valid DnaLadder? + [subclasses that might be should override] + """ + return False + + + def _make_glpane_cmenu_items_Dna(self, contextMenuList): # by Ninad + """ + Private helper for Chunk.make_glpane_cmenu_items; + does the dna-related part. + """ + #bruce 090115 split this out of Chunk.make_glpane_cmenu_items + + def _addDnaGroupMenuItems(dnaGroup): + if dnaGroup is None: + return + item = (("DnaGroup: [%s]" % dnaGroup.name), noop, 'disabled') + contextMenuList.append(item) + item = (("Edit DnaGroup Properties..."), + dnaGroup.edit) + contextMenuList.append(item) + return + + if self.isStrandChunk(): + strandGroup = self.parent_node_of_class(self.assy.DnaStrand) + + if strandGroup is None: + strand = self + else: + #dna_updater case which uses DnaStrand object for + #internal DnaStrandChunks + strand = strandGroup + + dnaGroup = strand.parent_node_of_class(self.assy.DnaGroup) + + if dnaGroup is None: + #This is probably a bug. A strand should always be contained + #within a Dnagroup. But we'll assume here that this is possible. + item = (("%s" % strand.name), noop, 'disabled') + else: + item = (("%s of [%s]" % (strand.name, dnaGroup.name)), + noop, + 'disabled') + contextMenuList.append(None) # adds a separator in the contextmenu + contextMenuList.append(item) + item = (("Edit DnaStrand Properties..."), + strand.edit) + contextMenuList.append(item) + contextMenuList.append(None) # separator + + _addDnaGroupMenuItems(dnaGroup) + + # add menu commands from our DnaLadder [bruce 080407] + # REVIEW: should these be added regardless of command? [bruce 090115 question] + # REVIEW: I think self.ladder is not valid except in dna-specific subclasses of Chunk. + # Probably that means this code should be moved there. [bruce 090115] + if self.ladder: + menu_spec = self.ladder.dnaladder_menu_spec(self) + # note: this is empty when self (the arg) is a Chunk. + # [bruce 080723 refactoring a recent Mark change] + if menu_spec: + # append separator?? ## contextMenuList.append(None) + contextMenuList.extend(menu_spec) + + elif self.isAxisChunk(): + segment = self.parent_node_of_class(self.assy.DnaSegment) + dnaGroup = segment.parent_node_of_class(self.assy.DnaGroup) + if segment is not None: + contextMenuList.append(None) # separator + if dnaGroup is not None: + item = (("%s of [%s]" % (segment.name, dnaGroup.name)), + noop, + 'disabled') + else: + item = (("%s " % segment.name), + noop, + 'disabled') + + contextMenuList.append(item) + item = (("Edit DnaSegment Properties..."), + segment.edit) + contextMenuList.append(item) + contextMenuList.append(None) # separator + # add menu commands from our DnaLadder [bruce 080407] + # REVIEW: see comments for similar code above. [bruce 090115] + if segment.picked: + selectedDnaSegments = self.assy.getSelectedDnaSegments() + if len(selectedDnaSegments) > 0: + item = (("Resize Selected DnaSegments "\ + "(%d)..." % len(selectedDnaSegments)), + self.assy.win.resizeSelectedDnaSegments) + contextMenuList.append(item) + contextMenuList.append(None) + if self.ladder: + menu_spec = self.ladder.dnaladder_menu_spec(self) + if menu_spec: + contextMenuList.extend(menu_spec) + + _addDnaGroupMenuItems(dnaGroup) + pass + pass + return + + + # START of Dna-Strand-or-Axis chunk specific code ========================== + + # Note: all these methods will be removed from class Chunk once the + # dna data model is always active. [bruce 080205 comment] + + # Assign a strand sequence (or get that information from a chunk). + # MEANT ONLY FOR THE DNA CHUNK. THESE METHODS NEED TO BE MOVED TO AN + # APPROPRIATE FILE IN The dna_model PACKAGE -- Ninad 2008-01-11 + # [And revised to use DnaMarkers for sequence alignment as Ninad suggests below. + # The sequence methods will end up as methods of DnaStrand with + # possible helper methods on objects it owns, like DnaStrandChunk + # (whose bases are in a known order) or DnaMarker or internal objects + # they refer to. -- Bruce 080117/080205 comment] + + def getStrandSequence(self): # probably by Ninad or Mark + """ + Returns the strand sequence for this chunk (strandChunk) + @return: strand Sequence string + @rtype: str + """ + sequenceString = "" + for atom in self.get_strand_atoms_in_bond_direction(): + baseName = str(atom.getDnaBaseName()) + if baseName: + sequenceString = sequenceString + baseName + + return sequenceString + + def setStrandSequence(self, sequenceString): # probably by Ninad or Mark + """ + Set the strand sequence i.e.assign the baseNames for the PAM atoms in + this strand AND the complementary baseNames to the PAM atoms of the + complementary strand ('mate strand') + @param sequenceString: The sequence to be assigned to this strand chunk + @type sequenceString: str + """ + sequenceString = str(sequenceString) + #Remove whitespaces and tabs from the sequence string + sequenceString = re.sub(r'\s', '', sequenceString) + + #May be we set this beginning with an atom marked by the + #Dna Atom Marker in dna data model? -- Ninad 2008-01-11 + # [yes, see my longer reply comment above -- Bruce 080117] + atomList = [] + for atom in self.get_strand_atoms_in_bond_direction(): + if not atom.is_singlet(): + atomList.append(atom) + + for atom in atomList: + atomIndex = atomList.index(atom) + if atomIndex > (len(sequenceString) - 1): + #In this case, set an unassigned base ('X') for the remaining + #atoms + baseName = 'X' + else: + baseName = sequenceString[atomIndex] + + atom.setDnaBaseName(baseName) + + #Also assign the baseNames for the PAM atoms on the complementary + #('mate') strand. + strandAtomMate = atom.get_strand_atom_mate() + complementBaseName = getComplementSequence(str(baseName)) + if strandAtomMate is not None: + strandAtomMate.setDnaBaseName(str(complementBaseName)) + return + + def _editProperties_DnaStrandChunk(self): + """ + Private helper for Chunk.edit; + does the dna-related part. + """ + # probably by Ninad; split out of Chunk.edit by Bruce 090115 + commandSequencer = self.assy.w.commandSequencer + commandSequencer.userEnterCommand('DNA_STRAND') + assert commandSequencer.currentCommand.commandName == 'DNA_STRAND' + commandSequencer.currentCommand.editStructure(self) + return + + + def isStrandChunk(self): # Ninad circa 080117, revised by Bruce 080117 + """ + Returns True if *all atoms* in this chunk are PAM 'strand' atoms + or 'unpaired-base' atoms (or bondpoints), and at least one is a + 'strand' atom. + + Also resets self.iconPath (based on self.hidden) if it returns True. + + This method is overridden in dna-specific subclasses of Chunk. + It is likely that this implementation on Chunk itself could now + be redefined to just return False, but this has not been analyzed closely. + + @see: BuildDna_PropertyManager.updateStrandListWidget where this is used + to filter out strand chunks to put those into the strandList + widget. + """ + # This is a temporary method that can be removed once dna_model is fully + # functional. [That is true now; REVIEW whether it can really be removed, + # or more precisely, redefined to return False on this class. bruce 090106 addendum] + found_strand_atom = False + for atom in self.atoms.itervalues(): + if atom.element.role == 'strand': + found_strand_atom = True + # side effect: use strand icon [mark 080203] + if self.hidden: + self.iconPath = "ui/modeltree/Strand-hide.png" + else: + self.iconPath = "ui/modeltree/Strand.png" + elif atom.is_singlet() or atom.element.role == 'unpaired-base': + pass + else: + # other kinds of atoms are not allowed + return False + continue + + return found_strand_atom + + def get_strand_atoms_in_bond_direction(self): # ninad 080205; bruce 080205 revised docstring + """ + Return a list of atoms in a fixed direction -- from 5' to 3' + + @note: this is a stub and we can modify it so that + it can accept other direction i.e. 3' to 5' , as an argument. + + BUG: ? : This also includes the bondpoints (X) .. I think this is + from the atomlist returned by bond_chains.grow_directional_bond_chain. + The caller -- self.getStrandSequence uses atom.getDnaBaseName to + retrieve the DnaBase name info out of atom. So this bug introduces + no harm (as dnaBaseNames are not assigned for bondpoints). + + [I think at most one atom at each end can be a bondpoint, + so we could revise this code to remove them before returning. + bruce 080205] + + @warning: for a ring, this uses an arbitrary start atom in self + (so it is not yet useful in that case). ### VERIFY + + @warning: this only works for PAM3 chunks (not PAM5). + + @note: this would return all atoms from an entire strand (chain or ring) + even if it spanned multiple chunks. + """ + startAtom = None + atomList = [] + + #Choose startAtom randomly (make sure that it's a PAM3 Sugar atom + # and not a bondpoint) + for atom in self.atoms.itervalues(): + if atom.element.symbol == 'Ss3': + startAtom = atom + break + + if startAtom is None: + print_compact_stack("bug: no PAM3 Sugar atom (Ss3) found: " ) + return [] + + #Build one list in each direction, detecting a ring too + + #ringQ decides whether the first returned list forms a ring. + #This needs a better name in bond_chains.grow_directional_bond_chain + ringQ = False + atomList_direction_1 = [] + atomList_direction_2 = [] + + b = None + bond_direction = 0 + for bnd in startAtom.directional_bonds(): + if not bnd.is_open_bond(): # (this assumes strand length > 1) + #Determine the bond_direction from the 'startAtom' + direction = bnd.bond_direction_from(startAtom) + if direction in (1, -1): + b = bnd + bond_direction = direction + break + + if b is None or bond_direction == 0: + return [] + + #Find out the list of new atoms and bonds in the direction + #from bond b towards 'startAtom' . This can either be 3' to 5' direction + #(i.e. bond_direction = -1 OR the reverse direction + # Later, we will check the bond direction and do appropriate things. + #(things that will decide which list (atomList_direction_1 or + #atomList_direction_2) should be prepended in atomList so that it has + #atoms ordered from 5' to 3' end. + + # 'atomList_direction_1' does NOT include 'startAtom'. + # See a detailed explanation below on how atomList_direction_a will be + # used, based on bond_direction + ringQ, listb, atomList_direction_1 = grow_directional_bond_chain(b, startAtom) + + del listb # don't need list of bonds + + if ringQ: + # The 'ringQ' returns True So its it's a 'ring'. + #First add 'startAtom' (as its not included in atomList_direction_1) + atomList.append(startAtom) + #extend atomList with remaining atoms + atomList.extend(atomList_direction_1) + else: + #Its not a ring. Now we need to make sure to include atoms in the + #direction_2 (if any) from the 'startAtom' . i.e. we need to grow + #the directional bond chain in the opposite direction. + + other_atom = b.other(startAtom) + if not other_atom.is_singlet(): + ringQ, listb, atomList_direction_2 = grow_directional_bond_chain(b, other_atom) + assert not ringQ #bruce 080205 + del listb + #See a detailed explanation below on how + #atomList_direction_2 will be used based on 'bond_direction' + atomList_direction_2.insert(0, other_atom) + + atomList = [] # not needed but just to be on a safer side. + + if bond_direction == 1: + # 'bond_direction' is the direction *away from* startAtom and + # along the bond 'b' declared above. . + + # This can be represented by the following sketch -- + # (3'end) <--1 <-- 2 <-- 3 <-- 4 <-- (5' end) + + # Let startAtom be '2' and bond 'b' be directional bond between + # 1 and 2. In this case, the direction of bond *away* from + # '2' and along 2 = bond direction of bond 'b' and thus + # atoms traversed along bond_direction = 1 lead us to 3' end. + + # Now, 'atomList_direction_1' is computed by 'growing' (expanding) + # a bond chain in the direction that goes from bond b + # *towards* startAtom. That is, in this case it is the opposite + # direction of one specified by 'bond_direction'. The last atom + # in atomList_direction_1 is the (5' end) atom. + # Note that atomList_direction_1 doesn't include 'startAtom' + # Therefore, to get atomList ordered from 5'to 3' end we must + #reverse atomList_direction_1 , then append startAtom to the + #atomList (as its not included in atomList_direction_1) and then + #extend atoms from atomList_direction_2. + + #What is atomList_direction_2 ? It is the list of atoms + #obtained by growing bond chain from bond b, in the direction of + #atom 1 (atom 1 is the 'other atom' of the bond) . In this case + #these are the atoms in the direction same as 'bond_direction' + #starting from atom 1. Thus the atoms in the list are already + #arranged from 5' to 3' end. (also note that after computing + #the atomList_direction_2, we also prepend 'atom 1' as the + #first atom in that list. See the code above that does that. + atomList_direction_1.reverse() + atomList.extend(atomList_direction_1) + atomList.append(startAtom) + atomList.extend(atomList_direction_2) + + else: + #See a detailed explanation above. + #Here, bond_direction == -1. + + # This can be represented by the following sketch -- + # (5'end) --> 1 --> 2 --> 3 --> 4 --> (3' end) + + #bond b is the bond betweern atoms 1 and 2. + #startAtom remains the same ..i.e. atom 2. + + #As you can notice from the sketch, the bond_direction is + #direction *away* from 2, along bond b and it leads us to + # 5' end. + + #based on how atomList_direction_2 (explained earlier), it now + #includes atoms begining at 1 and ending at 5' end. So + #we must reverse atomList_direction_2 now to arrange them + #from 5' to 3' end. + atomList_direction_2.reverse() + atomList.extend(atomList_direction_2) + atomList.append(startAtom) + atomList.extend(atomList_direction_1) + + #TODO: could zap first and/or last element if they are bondpoints + #[bruce 080205 comment] + return atomList + + #END of Dna-Strand chunk specific code ================================== + + + #START of Dna-Axis chunk specific code ================================== + + def isAxisChunk(self): + """ + Returns True if *all atoms* in this chunk are PAM 'axis' atoms + or bondpoints, and at least one is an 'axis' atom. + + Overridden in some subclasses. + + @see: isStrandChunk + """ + found_axis_atom = False + for atom in self.atoms.itervalues(): + if atom.element.role == 'axis': + found_axis_atom = True + elif atom.is_singlet(): + pass + else: + # other kinds of atoms are not allowed + return False + continue + + return found_axis_atom + + #END of Dna-Axis chunk specific code ==================================== + + + #START of Dna-Strand-or-Axis chunk specific code ======================== + + def isDnaChunk(self): + """ + @return: whether self is a DNA chunk (strand or axis chunk) + @rtype: boolean + """ + return self.isAxisChunk() or self.isStrandChunk() + + + def getDnaGroup(self): # ninad 080205 + """ + Return the DnaGroup of this chunk if it has one. + """ + return self.parent_node_of_class(self.assy.DnaGroup) + + def getDnaStrand(self): + """ + Returns the DnaStrand(group) node to which this chunk belongs to. + + Returns None if there isn't a parent DnaStrand group. + + @see: Atom.getDnaStrand() + """ + if self.isNullChunk(): + return None + + dnaStrand = self.parent_node_of_class(self.assy.DnaStrand) + + return dnaStrand + + def getDnaSegment(self): + """ + Returns the DnaStrand(group) node to which this chunk belongs to. + + Returns None if there isn't a parent DnaStrand group. + + @see: Atom.getDnaStrand() + """ + if self.isNullChunk(): + return None + + dnaSegment = self.parent_node_of_class(self.assy.DnaSegment) + + return dnaSegment + + #END of Dna-Strand-or-Axis chunk specific code ======================== + + + def _readmmp_info_chunk_setitem_Dna( self, key, val, interp ): + """ + Private helper for Chunk.readmmp_info_chunk_setitem. + + @return: whether we handled this info record (depends only on key, + not on success vs error) + @rtype: boolean + """ + if key == ['display_as_pam']: + # val should be one of the strings "", MODEL_PAM3, MODEL_PAM5; + # if not recognized, use "" + if val not in ("", MODEL_PAM3, MODEL_PAM5): + # 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 + # eliminates all ways of setting this attribute, thus fixing + # bug 2842 well enough for v1.1. (The same change is not needed + # for save_as_pam below, since it never gets set, or ever did, + # except when using non-default values of debug_prefs. This means + # someone setting those prefs could save a file which causes a bug + # only seen by whoever loads it, but I'll live with that for now.) + ## self.display_as_pam = val + pass + elif key == ['save_as_pam']: + # val should be one of the strings "", MODEL_PAM3, MODEL_PAM5; + # if not recognized, use "" + if val not in ("", MODEL_PAM3, MODEL_PAM5): + # 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: + return False + return True + + def _writemmp_info_chunk_before_atoms_Dna(self, mapping): + """ + Private helper for Chunk.writemmp_info_chunk_before_atoms. + + @return: None + """ + if self.display_as_pam: + # not normally set on most chunks, even when PAM3+5 is in use. + # future optim (unimportant since not normally set): + # we needn't write this is self contains no PAM atoms. + # and if we failed to write it when dna updater was off, that would be ok. + # so we could assume we don't need it for ordinary chunks + # (even though that means dna updater errors on atoms would discard it). + mapping.write("info chunk display_as_pam = %s\n" % self.display_as_pam) + if self.save_as_pam: + # not normally set, even when PAM3+5 is in use + mapping.write("info chunk save_as_pam = %s\n" % self.save_as_pam) + return + + + def _getToolTipInfo_Dna(self): + """ + Return the dna-related part of the tooltip string for this chunk + """ + # probably by Mark or Ninad + # Note: As of 2008-11-09, this is only implemented for a DnaStrand. + #bruce 090115 split this out of Chunk.getToolTipInfo + strand = self.getDnaStrand() + if strand: + return strand.getDefaultToolTipInfo() + + segment = self.getDnaSegment() + if segment: + return segment.getDefaultToolTipInfo() + + return "" + + + def _getAxis_of_self_or_eligible_parent_node_Dna(self, atomAtVectorOrigin = None): + """ + Private helper for Chunk.getAxis_of_self_or_eligible_parent_node; + does the dna-related part. + """ + # by Ninad + #bruce 090115 split this out of Chunk.getAxis_of_self_or_eligible_parent_node + dnaSegment = self.parent_node_of_class(self.assy.DnaSegment) + if dnaSegment and self.isAxisChunk(): + axisVector = dnaSegment.getAxisVector(atomAtVectorOrigin = atomAtVectorOrigin) + if axisVector is not None: + return axisVector, dnaSegment + + dnaStrand = self.parent_node_of_class(self.assy.DnaStrand) + if dnaStrand and self.isStrandChunk(): + arbitraryAtom = self.atlist[0] + dnaSegment = dnaStrand.get_DnaSegment_with_content_atom( + arbitraryAtom) + if dnaSegment: + axisVector = dnaSegment.getAxisVector(atomAtVectorOrigin = atomAtVectorOrigin) + if axisVector is not None: + return axisVector, dnaSegment + + return None, None + + pass # end of class Chunk_Dna_methods + +# end diff --git a/cad/src/model/chunk.py b/cad/src/model/chunk.py index 6f3829c36..13c1b42d7 100755 --- a/cad/src/model/chunk.py +++ b/cad/src/model/chunk.py @@ -56,7 +56,6 @@ bruce 080305 changed superclass from Node to NodeWithAtomContents _DRAW_BONDS = True # Debug/test switch. Similar constant in CS_workers.py. import math # only used for pi, everything else is from Numeric [as of before 071113] -import re import Numeric # for sqrt from Numeric import array from Numeric import add @@ -72,7 +71,6 @@ from OpenGL.GL import glPushMatrix from OpenGL.GL import glTranslatef from OpenGL.GL import glRotatef from OpenGL.GL import glPopMatrix -from OpenGL.GL import glCallList from OpenGL.GL import glPopName from OpenGL.GL import glPushName @@ -87,7 +85,7 @@ from foundation.NodeWithAtomContents import NodeWithAtomContents from utilities.Log import graymsg from utilities.debug import print_compact_stack -from utilities.debug import compact_stack +## from utilities.debug import compact_stack from utilities.debug import print_compact_traceback from utilities.debug import safe_repr @@ -128,8 +126,6 @@ from utilities.prefs_constants import selectionColor_prefs_key from utilities.constants import gensym, genKey -from utilities.constants import default_display_mode - from utilities.constants import diDEFAULT from utilities.constants import diINVISIBLE from utilities.constants import diBALL @@ -145,8 +141,6 @@ from utilities.constants import BBOX_MIN_RADIUS from utilities.constants import ATOM_CONTENT_FOR_DISPLAY_STYLE from utilities.constants import noop -from utilities.constants import MODEL_PAM3, MODEL_PAM5 - from utilities.GlobalPreferences import use_frustum_culling from utilities.GlobalPreferences import pref_show_node_color_in_MT @@ -154,10 +148,6 @@ from model.elements import PeriodicTable from commands.ChunkProperties.ChunkProp import ChunkProp -from dna.model.Dna_Constants import getComplementSequence - -from operations.bond_chains import grow_directional_bond_chain - import graphics.drawing.drawing_globals as drawing_globals from graphics.drawing.gl_lighting import apply_material @@ -167,6 +157,8 @@ from graphics.drawing.special_drawing import SPECIAL_DRAWING_STRAND_END from graphics.drawing.special_drawing import SpecialDrawing_ExtraChunkDisplayList from graphics.drawing.special_drawing import Chunk_SpecialDrawingHandler +from model.Chunk_Dna_methods import Chunk_Dna_methods + # == _inval_all_bonds_counter = 1 # private global counter [bruce 050516] @@ -198,9 +190,11 @@ _inval_all_bonds_counter = 1 # private global counter [bruce 050516] _superclass = NodeWithAtomContents #bruce 080305 revised this -class Chunk(NodeWithAtomContents, InvalMixin, +class Chunk(Chunk_Dna_methods, + NodeWithAtomContents, + InvalMixin, SelfUsageTrackingMixin, SubUsageTrackingMixin, - Selobj_API): + Selobj_API ): """ A set of atoms treated as a unit. """ @@ -258,39 +252,10 @@ class Chunk(NodeWithAtomContents, InvalMixin, ## user_specified_center = None # never changed for now, so not in copyable_attrs - # PAM3+5 attributes (these only affect PAM atoms in self, if any): - # - # self.display_as_pam can be MODEL_PAM3 or MODEL_PAM5 to force conversion on input - # to the specified PAM model for display and editing of self, or can be - # "" to use global preference settings. (There is no value which always - # causes no conversion, but there may be preference settings which disable - # ever doing conversion. But in practice, a PAM chunk will be all PAM3 or - # all PAM5, so this can be set to the model the chunk uses to prevent - # conversion for that chunk.) - # - # The value MODEL_PAM3 implies preservation of PAM5 data when present - # (aka "pam3+5" or "pam3plus5"). The allowed values are "", MODEL_PAM3, MODEL_PAM5. - # - # self.save_as_pam can be MODEL_PAM3 or MODEL_PAM5 to force conversion on save - # to the specified PAM model. When not set, global settings or save - # 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 - - 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) - - copyable_attrs = _superclass.copyable_attrs + ('display', 'color', - 'display_as_pam', 'save_as_pam', - 'protein') - # this extends the tuple from Node + copyable_attrs = _superclass.copyable_attrs + \ + ('display', 'color', 'protein') + \ + Chunk_Dna_methods._dna_copyable_attrs + # this extends the copyable_attrs tuple from Node # (could add _colorfunc, but better to handle it separately in case this # gets used for mmp writing someday. as of 051003 _colorfunc would # anyway not be permitted since state_utils.copy_val doesn't know @@ -502,52 +467,36 @@ class Chunk(NodeWithAtomContents, InvalMixin, """ return False - def invalidate_ladder(self): #bruce 071203 + def make_glpane_cmenu_items(self, contextMenuList, command): # by Ninad """ - Subclasses which have a .ladder attribute - should call its ladder_invalidate_if_not_disabled method. + Make glpane context menu items for this chunk (and append them to + contextMenuList), some of which may be specific to the given command + (presumably the current command) based on its having a commandName + for which we have special-case code. """ - return - - def invalidate_ladder_and_assert_permitted(self): #bruce 080413 - """ - Subclasses which have a .ladder attribute - should call its ladder_invalidate_and_assert_permitted method. - """ - return - - def in_a_valid_ladder(self): #bruce 071203 - """ - Is this chunk a rail of a valid DnaLadder? - [subclasses that might be should override] - """ - return False - - def make_glpane_context_menu_items(self, contextMenuList, command = None): - """ - """ - # TODO: See make_selobj_cmenu_items in other classes. This method is very + # Note: See make_selobj_cmenu_items in other classes. This method is very # similar to that method. But it's not named the same because the chunk # may not be a glpane.selobj (as it may get highlighted in SelectChunks # mode even when, for example, the cursor is over one of its atoms - # (i.e. selobj = an Atom). So ideally, that old method should be renamed - # to this one. [Ninad] - if command is None: - return + # (i.e. selobj = an Atom). So ideally, that method and this one should be + # unified somehow. This method exists only in class Chunk and is called + # only by certain commands. [comment originally by Ninad, revised by Bruce] + + assert command is not None #Start Standard context menu items rename and delete [by Ninad] - - ### TODO: refactor to not hardcode these classes, - # but to have a uniform way to find the innermost node - # visible in the MT, to be renamed. - ### Also REVIEW whether this is always the same as the unit - # of hover highlighting, and if not, whether it should be, - # and if so, whether the same code can be used to determine - # the highlighted object and the object to rename or delete. - # [bruce 081210 comments] - + parent_node_classes = (self.assy.DnaStrandOrSegment, self.assy.NanotubeSegment) + ### TODO: refactor to not hardcode these classes, + # but to have a uniform way to find the innermost node + # visible in the MT, which is the node to be renamed. + + ### Also REVIEW whether what this finds (node_to_rename) is always + # the same as the unit of hover highlighting, and if not, whether + # it should be, and if so, whether the same code can be used to + # determine the highlighted object and the object to rename or + # delete. [bruce 081210 comments] parent_node = None @@ -557,6 +506,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, break node_to_rename = parent_node or self + del parent_node + name = node_to_rename.name item = (("Rename %s..." % name), @@ -569,28 +520,22 @@ class Chunk(NodeWithAtomContents, InvalMixin, node_to_rename.kill_with_contents() return + del node_to_rename + item = (("Delete %s" % name), delnode_cmd ) contextMenuList.append(item) #End Standard context menu items rename and delete - def addDnaGroupMenuItems(dnaGroup): - if dnaGroup is None: - return - item = (("DnaGroup: [%s]" % dnaGroup.name), noop, 'disabled') - contextMenuList.append(item) - item = (("Edit DnaGroup Properties..."), - dnaGroup.edit) - contextMenuList.append(item) - return - - #Urmi 20080730: edit properties for protein for context menu in gl pane + # Protein-related items + #Urmi 20080730: edit properties for protein for context menu in glpane if command.commandName in ('SELECTMOLS', 'BUILD_PROTEIN'): if self.isProteinChunk(): try: protein = self.protein except: print_compact_traceback("exception in protein class") - return + return + ### REVIEW: is this early return appropriate? [bruce 090115 comment] if protein is not None: item = (("%s" % (self.name)), noop, 'disabled') @@ -600,7 +545,11 @@ class Chunk(NodeWithAtomContents, InvalMixin, protein.edit(_arg)) ) contextMenuList.append(item) - + pass + pass + pass + + # Nanotube-related items if command.commandName in ('SELECTMOLS', 'BUILD_NANOTUBE', 'EDIT_NANOTUBE'): if self.isNanotubeChunk(): try: @@ -617,6 +566,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, # [bruce 080723 comment and debug print] print_compact_traceback("exception in %r.parent_node_of_class: " % self) return + ### REVIEW: is this early return appropriate? [bruce 090115 comment] if segment is not None: # Self is a member of a Nanotube group, so add this # info to a disabled menu item in the context menu. @@ -627,82 +577,15 @@ class Chunk(NodeWithAtomContents, InvalMixin, item = (("Edit Nanotube Properties..."), segment.edit) contextMenuList.append(item) - + pass + pass + pass + + # Dna-related items if command.commandName in ('SELECTMOLS', 'BUILD_DNA', 'DNA_SEGMENT', 'DNA_STRAND'): - if self.isStrandChunk(): - strandGroup = self.parent_node_of_class(self.assy.DnaStrand) - - if strandGroup is None: - strand = self - else: - #dna_updater case which uses DnaStrand object for - #internal DnaStrandChunks - strand = strandGroup - - dnaGroup = strand.parent_node_of_class(self.assy.DnaGroup) - - if dnaGroup is None: - #This is probably a bug. A strand should always be contained - #within a Dnagroup. Lets assume that this is possible. - item = (("%s" % strand.name), noop, 'disabled') - else: - item = (("%s of [%s]" % (strand.name, dnaGroup.name)), - noop, - 'disabled') - contextMenuList.append(None) # adds a separator in the contextmenu - contextMenuList.append(item) - item = (("Edit DnaStrand Properties..."), - strand.edit) - contextMenuList.append(item) - contextMenuList.append(None) # separator - - addDnaGroupMenuItems(dnaGroup) - - # add menu commands from our DnaLadder [bruce 080407] - if self.ladder: - menu_spec = self.ladder.dnaladder_menu_spec(self) - # note: this is empty when self (the arg) is a Chunk. - # [bruce 080723 refactoring a recent Mark change] - if menu_spec: - # append separator?? ## contextMenuList.append(None) - contextMenuList.extend(menu_spec) - - elif self.isAxisChunk(): - segment = self.parent_node_of_class(self.assy.DnaSegment) - dnaGroup = segment.parent_node_of_class(self.assy.DnaGroup) - if segment is not None: - contextMenuList.append(None) # separator - if dnaGroup is not None: - item = (("%s of [%s]" % (segment.name, dnaGroup.name)), - noop, - 'disabled') - else: - item = (("%s " % segment.name), - noop, - 'disabled') - - contextMenuList.append(item) - item = (("Edit DnaSegment Properties..."), - segment.edit) - contextMenuList.append(item) - contextMenuList.append(None) # separator - # add menu commands from our DnaLadder [bruce 080407] - if segment.picked: - selectedDnaSegments = self.assy.getSelectedDnaSegments() - if len(selectedDnaSegments) > 0: - item = (("Resize Selected DnaSegments "\ - "(%d)..."%len(selectedDnaSegments)), - self.assy.win.resizeSelectedDnaSegments) - contextMenuList.append(item) - contextMenuList.append(None) - if self.ladder: - menu_spec = self.ladder.dnaladder_menu_spec(self) - if menu_spec: - contextMenuList.extend(menu_spec) - - addDnaGroupMenuItems(dnaGroup) - - return # from make_glpane_context_menu_items + self._make_glpane_cmenu_items_Dna(contextMenuList) + + return # from make_glpane_cmenu_items def nodes_containing_selobj(self): #bruce 080508 bugfix """ @@ -766,375 +649,33 @@ class Chunk(NodeWithAtomContents, InvalMixin, def _f_remove_ExternalBondSet(self, ebset): otherchunk = ebset.other_chunk(self) del self._bonded_chunks[otherchunk] - - # START of Dna-Strand-or-Axis chunk specific code ========================== - - # Note: all these methods will be removed from class Chunk once the - # dna data model is always active. [bruce 080205 comment] - - # Assign a strand sequence (or get that information from a chunk) - # MEANT ONLY FOR THE DNA CHUNK. THESE METHODS NEED TO BE MOVED TO AN - # APPROPRIATE FILE IN The dna_model PACKAGE -- Ninad 2008-01-11 - # [And revised to use DnaMarkers for sequence alignment as Ninad suggests below. - # The sequence methods will end up as methods of DnaStrand with - # possible helper methods on objects it owns, like DnaStrandChunk - # (whose bases are in a known order) or DnaMarker or internal objects - # they refer to. -- Bruce 080117/080205 comment] - - def getStrandSequence(self): - """ - Returns the strand sequence for this chunk (strandChunk) - @return: strand Sequence string - @rtype: str - """ - sequenceString = "" - for atom in self.get_strand_atoms_in_bond_direction(): - baseName = str(atom.getDnaBaseName()) - if baseName: - sequenceString = sequenceString + baseName - - return sequenceString - - - def setStrandSequence(self, sequenceString): - """ - Set the strand sequence i.e.assign the baseNames for the PAM atoms in - this strand AND the complementary baseNames to the PAM atoms of the - complementary strand ('mate strand') - @param sequenceString: The sequence to be assigned to this strand chunk - @type sequenceString: str - """ - sequenceString = str(sequenceString) - #Remove whitespaces and tabs from the sequence string - sequenceString = re.sub(r'\s', '', sequenceString) - - #May be we set this beginning with an atom marked by the - #Dna Atom Marker in dna data model? -- Ninad 2008-01-11 - # [yes, see my longer reply comment above -- Bruce 080117] - atomList = [] - for atom in self.get_strand_atoms_in_bond_direction(): - if not atom.is_singlet(): - atomList.append(atom) - - for atom in atomList: - atomIndex = atomList.index(atom) - if atomIndex > (len(sequenceString) - 1): - #In this case, set an unassigned base ('X') for the remaining - #atoms - baseName = 'X' - else: - baseName = sequenceString[atomIndex] - - atom.setDnaBaseName(baseName) - - #Also assign the baseNames for the PAM atoms on the complementary - #('mate') strand. - strandAtomMate = atom.get_strand_atom_mate() - complementBaseName = getComplementSequence(str(baseName)) - if strandAtomMate is not None: - strandAtomMate.setDnaBaseName(str(complementBaseName)) - return - - def edit(self): # probably by Ninad - # This method could be revised (moved to appropriate class or subclass) - # post dna_model implementation. + + def edit(self): ### REVIEW: model tree has a special case for isProteinChunk; # should we pull that in here too? Guess yes. # (Note, there are several other uses of isProteinChunk # that might also be worth refactoring.) [bruce 090106 comment] if self.isStrandChunk(): - commandSequencer = self.assy.w.commandSequencer - commandSequencer.userEnterCommand('DNA_STRAND') - assert commandSequencer.currentCommand.commandName == 'DNA_STRAND' - commandSequencer.currentCommand.editStructure(self) + self._editProperties_DnaStrandChunk() else: cntl = ChunkProp(self) cntl.exec_() self.assy.mt.mt_update() ### REVIEW [bruce 041109]: don't we want to repaint the glpane, too? - def getProps(self): + def getProps(self): # probably by Ninad """ - To be revised post dna data model. Used in EditConmmand class and its + To be revised post dna data model. Used in EditCommand class and its subclasses. """ return () - def setProps(self, params): + def setProps(self, params): # probably by Ninad """ - To be revised post dna data model + To be revised post dna data model. """ del params - def isStrandChunk(self): # Ninad circa 080117, revised by Bruce 080117 - """ - Returns True if *all atoms* in this chunk are PAM 'strand' atoms - or 'unpaired-base' atoms (or bondpoints), and at least one is a - 'strand' atom. - - Also resets self.iconPath (based on self.hidden) if it returns True. - - This method is overridden in dna-specific subclasses of Chunk. - It is likely that this implementation on Chunk itself could now - be redefined to just return False, but this has not been analyzed closely. - - @see: BuildDna_PropertyManager.updateStrandListWidget where this is used - to filter out strand chunks to put those into the strandList - widget. - """ - # This is a temporary method that can be removed once dna_model is fully - # functional. [That is true now; REVIEW whether it can really be removed, - # or more precisely, redefined to return False on this class. bruce 090106 addendum] - found_strand_atom = False - for atom in self.atoms.itervalues(): - if atom.element.role == 'strand': - found_strand_atom = True - # side effect: use strand icon [mark 080203] - if self.hidden: - self.iconPath = "ui/modeltree/Strand-hide.png" - else: - self.iconPath = "ui/modeltree/Strand.png" - elif atom.is_singlet() or atom.element.role == 'unpaired-base': - pass - else: - # other kinds of atoms are not allowed - return False - continue - - return found_strand_atom - - def get_strand_atoms_in_bond_direction(self): # ninad 080205; bruce 080205 revised docstring - """ - Return a list of atoms in a fixed direction -- from 5' to 3' - - @note: this is a stub and we can modify it so that - it can accept other direction i.e. 3' to 5' , as an argument. - - BUG: ? : This also includes the bondpoints (X) .. I think this is - from the atomlist returned by bond_chains.grow_directional_bond_chain. - The caller -- self.getStrandSequence uses atom.getDnaBaseName to - retrieve the DnaBase name info out of atom. So this bug introduces - no harm (as dnaBaseNames are not assigned for bondpoints). - - [I think at most one atom at each end can be a bondpoint, - so we could revise this code to remove them before returning. - bruce 080205] - - @warning: for a ring, this uses an arbitrary start atom in self - (so it is not yet useful in that case). ### VERIFY - - @warning: this only works for PAM3 chunks (not PAM5). - - @note: this would return all atoms from an entire strand (chain or ring) - even if it spanned multiple chunks. - """ - startAtom = None - atomList = [] - - #Choose startAtom randomly (make sure that it's a PAM3 Sugar atom - # and not a bondpoint) - for atom in self.atoms.itervalues(): - if atom.element.symbol == 'Ss3': - startAtom = atom - break - - if startAtom is None: - print_compact_stack("bug: no PAM3 Sugar atom (Ss3) found: " ) - return [] - - #Build one list in each direction, detecting a ring too - - #ringQ decides whether the first returned list forms a ring. - #This needs a better name in bond_chains.grow_directional_bond_chain - ringQ = False - atomList_direction_1 = [] - atomList_direction_2 = [] - - b = None - bond_direction = 0 - for bnd in startAtom.directional_bonds(): - if not bnd.is_open_bond(): # (this assumes strand length > 1) - #Determine the bond_direction from the 'startAtom' - direction = bnd.bond_direction_from(startAtom) - if direction in (1, -1): - b = bnd - bond_direction = direction - break - - if b is None or bond_direction == 0: - return [] - - #Find out the list of new atoms and bonds in the direction - #from bond b towards 'startAtom' . This can either be 3' to 5' direction - #(i.e. bond_direction = -1 OR the reverse direction - # Later, we will check the bond direction and do appropriate things. - #(things that will decide which list (atomList_direction_1 or - #atomList_direction_2) should be prepended in atomList so that it has - #atoms ordered from 5' to 3' end. - - # 'atomList_direction_1' does NOT include 'startAtom'. - # See a detailed explanation below on how atomList_direction_a will be - # used, based on bond_direction - ringQ, listb, atomList_direction_1 = grow_directional_bond_chain(b, startAtom) - - del listb # don't need list of bonds - - if ringQ: - # The 'ringQ' returns True So its it's a 'ring'. - #First add 'startAtom' (as its not included in atomList_direction_1) - atomList.append(startAtom) - #extend atomList with remaining atoms - atomList.extend(atomList_direction_1) - else: - #Its not a ring. Now we need to make sure to include atoms in the - #direction_2 (if any) from the 'startAtom' . i.e. we need to grow - #the directional bond chain in the opposite direction. - - other_atom = b.other(startAtom) - if not other_atom.is_singlet(): - ringQ, listb, atomList_direction_2 = grow_directional_bond_chain(b, other_atom) - assert not ringQ #bruce 080205 - del listb - #See a detailed explanation below on how - #atomList_direction_2 will be used based on 'bond_direction' - atomList_direction_2.insert(0, other_atom) - - atomList = [] # not needed but just to be on a safer side. - - if bond_direction == 1: - # 'bond_direction' is the direction *away from* startAtom and - # along the bond 'b' declared above. . - - # This can be represented by the following sketch -- - # (3'end) <--1 <-- 2 <-- 3 <-- 4 <-- (5' end) - - # Let startAtom be '2' and bond 'b' be directional bond between - # 1 and 2. In this case, the direction of bond *away* from - # '2' and along 2 = bond direction of bond 'b' and thus - # atoms traversed along bond_direction = 1 lead us to 3' end. - - # Now, 'atomList_direction_1' is computed by 'growing' (expanding) - # a bond chain in the direction that goes from bond b - # *towards* startAtom. That is, in this case it is the opposite - # direction of one specified by 'bond_direction'. The last atom - # in atomList_direction_1 is the (5' end) atom. - # Note that atomList_direction_1 doesn't include 'startAtom' - # Therefore, to get atomList ordered from 5'to 3' end we must - #reverse atomList_direction_1 , then append startAtom to the - #atomList (as its not included in atomList_direction_1) and then - #extend atoms from atomList_direction_2. - - #What is atomList_direction_2 ? It is the list of atoms - #obtained by growing bond chain from bond b, in the direction of - #atom 1 (atom 1 is the 'other atom' of the bond) . In this case - #these are the atoms in the direction same as 'bond_direction' - #starting from atom 1. Thus the atoms in the list are already - #arranged from 5' to 3' end. (also note that after computing - #the atomList_direction_2, we also prepend 'atom 1' as the - #first atom in that list. See the code above that does that. - atomList_direction_1.reverse() - atomList.extend(atomList_direction_1) - atomList.append(startAtom) - atomList.extend(atomList_direction_2) - - else: - #See a detailed explanation above. - #Here, bond_direction == -1. - - # This can be represented by the following sketch -- - # (5'end) --> 1 --> 2 --> 3 --> 4 --> (3' end) - - #bond b is the bond betweern atoms 1 and 2. - #startAtom remains the same ..i.e. atom 2. - - #As you can notice from the sketch, the bond_direction is - #direction *away* from 2, along bond b and it leads us to - # 5' end. - - #based on how atomList_direction_2 (explained earlier), it now - #includes atoms begining at 1 and ending at 5' end. So - #we must reverse atomList_direction_2 now to arrange them - #from 5' to 3' end. - atomList_direction_2.reverse() - atomList.extend(atomList_direction_2) - atomList.append(startAtom) - atomList.extend(atomList_direction_1) - - #TODO: could zap first and/or last element if they are bondpoints - #[bruce 080205 comment] - return atomList - - #END of Dna-Strand chunk specific code ================================== - - - #START of Dna-Axis chunk specific code ================================== - - def isAxisChunk(self): - """ - Returns True if *all atoms* in this chunk are PAM 'axis' atoms - or bondpoints, and at least one is an 'axis' atom. - - Overridden in some subclasses. - - @see: isStrandChunk - """ - found_axis_atom = False - for atom in self.atoms.itervalues(): - if atom.element.role == 'axis': - found_axis_atom = True - elif atom.is_singlet(): - pass - else: - # other kinds of atoms are not allowed - return False - continue - - return found_axis_atom - - #END of Dna-Axis chunk specific code ==================================== - - - #START of Dna-Strand-or-Axis chunk specific code ======================== - - def getDnaGroup(self): # ninad 080205 - """ - Return the DnaGroup of this chunk if it has one. - """ - return self.parent_node_of_class(self.assy.DnaGroup) - - def getDnaStrand(self): - """ - Returns the DnaStrand(group) node to which this chunk belongs to. - - Returns None if there isn't a parent DnaStrand group. - - @see: Atom.getDnaStrand() - """ - if self.isNullChunk(): - return None - - dnaStrand = self.parent_node_of_class(self.assy.DnaStrand) - - return dnaStrand - - def getDnaSegment(self): - """ - Returns the DnaStrand(group) node to which this chunk belongs to. - - Returns None if there isn't a parent DnaStrand group. - - @see: Atom.getDnaStrand() - """ - if self.isNullChunk(): - return None - - dnaSegment = self.parent_node_of_class(self.assy.DnaSegment) - - return dnaSegment - - #END of Dna-Strand-or-Axis chunk specific code ======================== - - #START of Nanotube chunk specific code ======================== def isNanotubeChunk(self): # probably by Mark @@ -2018,6 +1559,8 @@ class Chunk(NodeWithAtomContents, InvalMixin, """ @return: the display style we will use to draw self """ + # TODO: refactor so each type of chunk has its own drawing code + # [bruce 090115 comment] if self.display != diDEFAULT: disp = self.display else: @@ -2464,10 +2007,10 @@ class Chunk(NodeWithAtomContents, InvalMixin, # (possible optim: decide once per redraw, cache in glpane) # piotr 080320: if this debug_pref is set, the external bonds - # are hidden whenever the mouse is dragged. this speeds up interactive + # are hidden whenever the mouse is dragged. This speeds up interactive # manipulation of DNA segments by a factor of 3-4x in tubes # or ball-and-sticks display styles. - # this extends the previous condition to suppress the external + # This extends the previous condition to suppress the external # bonds during animation. suppress_external_bonds = ( (getattr(glpane, 'in_drag', False) # glpane.in_drag undefined in ThumbView @@ -2707,17 +2250,17 @@ class Chunk(NodeWithAtomContents, InvalMixin, This is used to return a highlight color for the chunk highlighting. See a comment in this method below """ - #NOTE: before 2008-03-13, the chunk highlighting was achieved by - #using the atoms and bonds within the chunk. The Atom and Bond classes - #have their own glselect name, so the code was able to recognize them - #as highlightable objects and then depending upon the graphics mode - #the user was in, it used to highlight the whole chunk by accessing the - #chunk using, for instance, atom.molecule. although this is still - #implemented, for certain display styles such as DnaCylinderChunks, the - #atoms and bonds are never drawn. So there is no way to access the - #chunk! To fix this, we need to make chunk a highlightable object. - #This is done by making sure that the chunk gets a glselect name and - #by defining this API method - Ninad 2008-03-13 + #NOTE: before 2008-03-13, the chunk highlighting was achieved by using + #the atoms and bonds within the chunk. The Atom and Bond classes have + #their own glselect name, so the code was able to recognize them as + #highlightable objects and then depending upon the graphics mode the + #user was in, it used to highlight the whole chunk by accessing the + #chunk using, for instance, atom.molecule. although this is still + #implemented, for certain display styles such as DnaCylinderChunks, + #the atoms and bonds are never drawn. So there is no way to access the + #chunk! To fix this, we need to make chunk a highlightable object. + #This is done by making sure that the chunk gets a glselect name + #(glname) and by defining this API method - Ninad 2008-03-13 return env.prefs[hoverHighlightingColor_prefs_key] @@ -2725,7 +2268,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, """ Draws this chunk as highlighted with the specified color. - In future 'draw_in_abs_coords' defined on some node classes + In future, 'draw_in_abs_coords' defined on some node classes could be merged into this method (for highlighting various objects). @param glpane: the GLPane @@ -3013,7 +2556,10 @@ class Chunk(NodeWithAtomContents, InvalMixin, containing unrecognized info records or keys, but not too verbosely (at most once per file per type of info).) """ - if key == ['hotspot']: + didit = self._readmmp_info_chunk_setitem_Dna( key, val, interp) + if didit: + pass # e.g. for display_as_pam, save_as_pam + elif key == ['hotspot']: # val should be a string containing an atom number referring to # the hotspot to be set for this chunk (which is being read from an mmp file) (hs_num,) = val.split() @@ -3026,31 +2572,6 @@ class Chunk(NodeWithAtomContents, InvalMixin, r,g,b = map(int, val.split()) color = r/255.0, g/255.0, b/255.0 self.setcolor(color, repaint_in_MT = False) - elif key == ['display_as_pam']: - # val should be one of the strings "", MODEL_PAM3, MODEL_PAM5; - # if not recognized, use "" - if val not in ("", MODEL_PAM3, MODEL_PAM5): - # 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 - # eliminates all ways of setting this attribute, thus fixing - # bug 2842 well enough for v1.1. (The same change is not needed - # for save_as_pam below, since it never gets set, or ever did, - # except when using non-default values of debug_prefs. This means - # someone setting those prefs could save a file which causes a bug - # only seen by whoever loads it, but I'll live with that for now.) - ## self.display_as_pam = val - pass - elif key == ['save_as_pam']: - # val should be one of the strings "", MODEL_PAM3, MODEL_PAM5; - # if not recognized, use "" - if val not in ("", MODEL_PAM3, MODEL_PAM5): - # 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: if debug_flags.atom_debug: print "atom_debug: fyi: info chunk with unrecognized key %r" % (key,) @@ -3137,19 +2658,9 @@ class Chunk(NodeWithAtomContents, InvalMixin, (since their value, during mmp read, might be needed when reading the atoms or their bonds). - [subclasses should override this as needed] + [subclasses should extend this as needed] """ - if self.display_as_pam: - # not normally set on most chunks, even when PAM3+5 is in use. - # future optim (unimportant since not normally set): - # we needn't write this is self contains no PAM atoms. - # and if we failed to write it when dna updater was off, that would be ok. - # so we could assume we don't need it for ordinary chunks - # (even though that means dna updater errors on atoms would discard it). - mapping.write("info chunk display_as_pam = %s\n" % self.display_as_pam) - if self.save_as_pam: - # not normally set, even when PAM3+5 is in use - mapping.write("info chunk save_as_pam = %s\n" % self.save_as_pam) + self._writemmp_info_chunk_before_atoms_Dna( mapping) return def write_bonds_compactly_for_these_atoms(self, mapping): #bruce 080328 @@ -3642,18 +3153,10 @@ class Chunk(NodeWithAtomContents, InvalMixin, """ Return the tooltip string for this chunk """ - # As of 2008-11-09, this is only implemented for a DnaStrand. - strand = self.getDnaStrand() - toolTipInfoString = '' - if strand: - toolTipInfoString = strand.getDefaultToolTipInfo() - return toolTipInfoString - - segment = self.getDnaSegment() - if segment: - toolTipInfoString = segment.getDefaultToolTipInfo() - - return toolTipInfoString + info = self._getToolTipInfo_Dna() + if info: + return info # in future, we might combine it with other info + return "" def getinfo(self): # mark 2004-10-14 """ @@ -3677,7 +3180,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, einfo = "" for item in ele2Num.iteritems(): - if item[0] == "X": # It is a Singlet + if item[0] == "X": # Singlet nsinglets = int(item[1]) continue else: @@ -3780,32 +3283,29 @@ class Chunk(NodeWithAtomContents, InvalMixin, def getAxis_of_self_or_eligible_parent_node(self, atomAtVectorOrigin = None): """ Return the axis of a parent node such as a DnaSegment or a Nanotube - segment or a dna segment of a DnaStrand. If one doesn't exist, - return the self's axis. + Segment or the dna segment of a DnaStrand. If one doesn't exist, + return self's axis. Also return the node from which the returned + axis was found. + @param atomAtVectorOrigin: If the atom at vector origin is specified, the method will try to return the axis vector with the vector - start point at the this atom's center. + start point at this atom's center. [REVIEW: What does this mean??] @type atomAtVectorOrigin: B{Atom} - @see: + + @return: (axis, node used to get that axis) """ - #@TODO: refactor this. MEthod written just before FNANO08 for a critical + #@TODO: refactor this. Method written just before FNANO08 for a critical #NFR. (this code is not a part of Rattlesnake rc2) #- Ninad 2008-04-17 - dnaSegment = self.parent_node_of_class(self.assy.DnaSegment) - if dnaSegment and self.isAxisChunk(): - axisVector = dnaSegment.getAxisVector(atomAtVectorOrigin = atomAtVectorOrigin) - if axisVector is not None: - return axisVector, dnaSegment - - dnaStrand = self.parent_node_of_class(self.assy.DnaStrand) - if dnaStrand and self.isStrandChunk(): - arbitraryAtom = self.atlist[0] - dnaSegment = dnaStrand.get_DnaSegment_with_content_atom( - arbitraryAtom) - if dnaSegment: - axisVector = dnaSegment.getAxisVector(atomAtVectorOrigin = atomAtVectorOrigin) - if axisVector is not None: - return axisVector, dnaSegment + + #bruce 090115 partly refactored it, but more would be better. + # REVIEW: I don't understand any meaning in what the docstring says about + # atomAtVectorOrigin. What does it actually do? [bruce 090115 comment] + + axis, node = self._getAxis_of_self_or_eligible_parent_node_Dna( + atomAtVectorOrigin = atomAtVectorOrigin ) + if axis is not None: + return axis, node nanotube = self.parent_node_of_class(self.assy.NanotubeSegment) if nanotube: @@ -3813,8 +3313,7 @@ class Chunk(NodeWithAtomContents, InvalMixin, if axisVector is not None: return axisVector, nanotube - #If no eligible parent node with an axis is found, return self's - #axis. + #If no eligible parent node with an axis is found, return self's axis. return self.getaxis(), self @@ -4634,13 +4133,6 @@ class Chunk(NodeWithAtomContents, InvalMixin, else: return True - def isDnaChunk(self): - """ - Returns True is the chunk is a DNA object (either strand or axis). - """ - return self.isAxisChunk() or \ - self.isStrandChunk() - def isProteinChunk(self): """ Returns True if the chunk is a protein object. diff --git a/cad/src/ne1_ui/MWsemantics.py b/cad/src/ne1_ui/MWsemantics.py index e30e1a20f..511c1f52d 100755 --- a/cad/src/ne1_ui/MWsemantics.py +++ b/cad/src/ne1_ui/MWsemantics.py @@ -1,9 +1,9 @@ -# Copyright 2004-2008 Nanorex, Inc. See LICENSE file for details. +# Copyright 2004-2009 Nanorex, Inc. See LICENSE file for details. """ MWsemantics.py provides the main window class, MWsemantics. @version: $Id$ -@copyright: 2004-2008 Nanorex, Inc. See LICENSE file for details. +@copyright: 2004-2009 Nanorex, Inc. See LICENSE file for details. History: too much to mention, except for breakups of the file. @@ -883,8 +883,8 @@ class MWsemantics(QMainWindow, """ Invokes the MultipleDnaSegmentResize_EditCommand to resize the selected segments. - @see: chunk.make_glpane_context_menu_items (a context menu that calls - this method) + @see: chunk.make_glpane_cmenu_items (which makes a context menu + which has an item which can call this method) """ #TODO: need more ui options to invoke this command. selectedSegments = self.assy.getSelectedDnaSegments() @@ -897,7 +897,7 @@ class MWsemantics(QMainWindow, def editAddSuffix(self): """ - Adds a suffix to the name(s) the selected objects. + Adds a suffix to the name(s) of the selected objects. """ # Don't allow renaming while animating (b/w views). if self.glpane.is_animating: @@ -971,6 +971,12 @@ class MWsemantics(QMainWindow, # IIRC, the numbering is not guaranteed to be in any specific order, # but testing has shown that leaf nodes are numbered in the order # they appear in the model tree. --Mark 2008-11-12 + + # REVIEW: I would guess that getSelectedRenameables is guaranteed + # to return nodes in model tree order. If analysis of its code + # confirms that, then its docstring should be made to say that. + # [bruce 090115 comment] + if new_name[-1] == "#": _renumber = True new_name = new_name[:-1] |