コード例 #1
0
    def modifyHydrogenate(self):
        """
        Add hydrogen atoms to bondpoints on selected chunks/atoms.
        """
        cmd = greenmsg("Hydrogenate: ")
        
        fixmols = {} # helps count modified mols for statusbar
        if self.selmols:
            counta = countm = 0
            for m in self.selmols:
                changed = m.Hydrogenate()
                if changed:
                    counta += changed
                    countm += 1
                    fixmols[id(m)] = m
            if counta:
                didwhat = "Added %d atom(s) to %d chunk(s)" \
                          % (counta, countm)
                if len(self.selmols) > countm:
                    didwhat += \
                        " (%d selected chunk(s) had no bondpoints)" \
                        % (len(self.selmols) - countm)
                didwhat = fix_plurals(didwhat)
            else:
                didwhat = "Selected chunks contain no bondpoints"    

        elif self.selatoms:
            count = 0
            for a in self.selatoms.values():
                ma = a.molecule
                for atm in a.neighbors():
                    matm = atm.molecule
                    changed = atm.Hydrogenate()
                    if changed:
                        count += 1
                        fixmols[id(ma)] = ma
                        fixmols[id(matm)] = matm
            if fixmols:
                didwhat = \
                    "Added %d atom(s) to %d chunk(s)" \
                    % (count, len(fixmols))
                didwhat = fix_plurals(didwhat)
                # Technically, we *should* say ", affected" instead of "from"
                # since the count includes mols of neighbors of
                # atoms we removed, not always only mols of atoms we removed.
                # Since that's rare, we word this assuming it didn't happen.
                # [#e needs low-pri fix to be accurate in that rare case;
                #  might as well deliver that as a warning, since that case is
                #  also "dangerous" in some sense.]
            else:
                didwhat = "No bondpoints on selected atoms"
        else:
            didwhat = redmsg("Nothing selected")

        if fixmols:
            self.changed()
            self.w.win_update()
        env.history.message(cmd + didwhat)
        return
コード例 #2
0
 def unselectConnected(self, atomlist=None):
     """
     Unselect any atom that can be reached from any currently
     selected atom through a sequence of bonds.
     If <atomlist> is supplied, use it instead of the currently selected atoms.
     """
     cmd = greenmsg("Unselect Connected: ")
     
     if atomlist is None and not self.selatoms:
         msg = redmsg("No atoms selected")
         env.history.message(cmd + msg)
         return
     
     if atomlist is None: # test for None since atomlist can be an empty list.
         atomlist = self.selatoms.values()
         
     catoms = self.getConnectedAtoms(atomlist)
     if not len(catoms): return
     
     natoms = 0
     for atom in catoms[:]:
         if atom.picked:
             atom.unpick()
             if not atom.picked:
                 # Just in case a selection filter was applied to this atom.
                 natoms += 1 
     
     from platform_dependent.PlatformDependent import fix_plurals
     info = fix_plurals( "%d atom(s) unselected." % natoms)
     env.history.message( cmd + info)
     self.o.gl_update()
コード例 #3
0
 def selectDoubly(self):
     """
     Select any atom that can be reached from any currently
     selected atom through two or more non-overlapping sequences of
     bonds. Also select atoms that are connected to this group by
     one bond and have no other bonds.
     """
     ###@@@ same comment about interspace bonds as in selectConnected
     
     cmd = greenmsg("Select Doubly: ")
     
     if not self.selatoms:
         msg = redmsg("No atoms selected")
         env.history.message(cmd + msg)
         return
     
     alreadySelected = len(self.selatoms.values())
     from operations.op_select_doubly import select_doubly # new code, bruce 050520
     #e could also reload it now to speed devel!
     select_doubly(self.selatoms.values()) #e optim
     totalSelected = len(self.selatoms.values())
     
     from platform_dependent.PlatformDependent import fix_plurals
     info = fix_plurals("%d new atom(s) selected (besides the %d initially selected)." % \
                            (totalSelected - alreadySelected, alreadySelected) )
     env.history.message( cmd + info)
             
     if totalSelected > alreadySelected:
         ## otherwise, means nothing new selected. Am I right? ---Huaicai, not analyze the markdouble() algorithm yet 
         #self.w.win_update()
         self.o.gl_update()
     return
コード例 #4
0
    def selectDoubly(self):
        """
        Select any atom that can be reached from any currently
        selected atom through two or more non-overlapping sequences of
        bonds. Also select atoms that are connected to this group by
        one bond and have no other bonds.
        """
        ###@@@ same comment about interspace bonds as in selectConnected

        cmd = greenmsg("Select Doubly: ")

        if not self.selatoms:
            msg = redmsg("No atoms selected")
            env.history.message(cmd + msg)
            return

        alreadySelected = len(self.selatoms.values())
        from operations.op_select_doubly import select_doubly # new code, bruce 050520
        #e could also reload it now to speed devel!
        select_doubly(self.selatoms.values()) #e optim
        totalSelected = len(self.selatoms.values())

        from platform_dependent.PlatformDependent import fix_plurals
        info = fix_plurals("%d new atom(s) selected (besides the %d initially selected)." % \
                               (totalSelected - alreadySelected, alreadySelected) )
        env.history.message( cmd + info)

        if totalSelected > alreadySelected:
            ## otherwise, means nothing new selected. Am I right? ---Huaicai, not analyze the markdouble() algorithm yet
            #self.w.win_update()
            self.o.gl_update()
        return
コード例 #5
0
    def unselectConnected(self, atomlist=None):
        """
        Unselect any atom that can be reached from any currently
        selected atom through a sequence of bonds.
        If <atomlist> is supplied, use it instead of the currently selected atoms.
        """
        cmd = greenmsg("Unselect Connected: ")

        if atomlist is None and not self.selatoms:
            msg = redmsg("No atoms selected")
            env.history.message(cmd + msg)
            return

        if atomlist is None: # test for None since atomlist can be an empty list.
            atomlist = self.selatoms.values()

        catoms = self.getConnectedAtoms(atomlist)
        if not len(catoms): return

        natoms = 0
        for atom in catoms[:]:
            if atom.picked:
                atom.unpick()
                if not atom.picked:
                    # Just in case a selection filter was applied to this atom.
                    natoms += 1

        from platform_dependent.PlatformDependent import fix_plurals
        info = fix_plurals( "%d atom(s) unselected." % natoms)
        env.history.message( cmd + info)
        self.o.gl_update()
コード例 #6
0
 def _getToolTipInfo(self): #ninad060825
     "Return a string for display in Dynamic Tool tip "
     from platform_dependent.PlatformDependent import fix_plurals
     attachedAtomCount = fix_plurals("Attached to %d atom(s)"%(len(self.atoms)))
     return str(self.name) + "<br>" +  "<font color=\"#0000FF\"> Jig Type:</font>Linear Motor"\
            +  "<br>" + "<font color=\"#0000FF\">Force: </font>" + str(self.force) +  " pN " \
            +  "<br>" + "<font color=\"#0000FF\">Stiffness:</font> " + str(self.stiffness) + " N/m" \
            + "<br>"  + str(attachedAtomCount)
コード例 #7
0
 def _getToolTipInfo(self): #ninad060825
     "Return a string for display in Dynamic Tool tip "
     from platform_dependent.PlatformDependent import fix_plurals
     attachedAtomCount = fix_plurals("Attached to %d atom(s)"%(len(self.atoms)))
     return str(self.name) + "<br>" +  "<font color=\"#0000FF\"> Jig Type:</font>Rotary Motor"\
            +  "<br>" + "<font color=\"#0000FF\">Torque: </font>" + str(self.torque) +  " nN-nm " \
            +  "<br>" + "<font color=\"#0000FF\">Speed:</font> " + str(self.speed) + " GHz" \
            + "<br>"  + str(attachedAtomCount)
コード例 #8
0
ファイル: TreeModel.py プロジェクト: vcsrc/nanoengineer
 def cm_select_jigs_atoms(self):  #bruce 050504
     nodeset = self.topmost_selected_nodes()
     otherpart = {}  #bruce 050505 to fix bug 589
     did_these = {}
     nprior = len(self.assy.selatoms)
     for jig in nodeset:
         assert isinstance(jig, Jig)  # caller guarantees they are all jigs
         # If we didn't want to desel the jig, I'd have to say:
         # note: this does not deselect the jig (good); and permit_pick_atoms would deselect it (bad);
         # so to keep things straight (not sure this is actually needed except to avoid a debug message),
         # just set SELWHAT_ATOMS here; this is legal because no chunks are selected. Actually, bugs might occur
         # in which that's not true... I forget whether I fixed those recently or only analyzed them (due to delays
         # in update event posting vs processing)... but even if they can occur, it's not high-priority to fix them,
         # esp since selection rules might get revised soon.
         ## self.assy.set_selwhat(SELWHAT_ATOMS)
         # but (I forgot when I wrote that) we *do* desel the jig,
         # so instead I can just say:
         self.assy.part.permit_pick_atoms(
         )  # changes selwhat and deselects all chunks, jigs, and groups
         # [bruce 050519 8pm]
         for atm in jig.atoms:
             if atm.molecule.part == jig.part:
                 atm.pick()
                 did_these[atm.key] = atm
             else:
                 otherpart[atm.key] = atm
         ## jig.unpick() # not done by picking atoms [no longer needed since done by permit_pick_atoms]
     msg = fix_plurals("Selected %d atom(s)" %
                       len(did_these))  # might be 0, that's ok
     if nprior:  #bruce 050519
         #e msg should distinguish between atoms already selected and also selected again just now,
         # vs already and not now; for now, instead, we just try to be ambiguous about that
         msg += fix_plurals(" (%d atom(s) remain selected from before)" %
                            nprior)
     if otherpart:
         msg += fix_plurals(
             " (skipped %d atom(s) which were not in this Part)" %
             len(otherpart))
         msg = orangemsg(msg)  # the whole thing, I guess
     env.history.message(msg)
     self.win.win_update()
     # note: caller (which puts up context menu) does
     # self.win.update_select_mode(); we depend on that. [### still true??]
     return
コード例 #9
0
def add_basepair_handles_to_selected_atoms(glpane): #bruce 080515
    assy = glpane.assy
    goodcount, badcount = add_basepair_handles_to_atoms(assy.selatoms.values())
    msg = "adding handles to %d duplex Gv5 atom(s)" % (goodcount,)
    if badcount:
        msg += " (but not %d other selected atom(s))" % (badcount,)
    msg = fix_plurals(msg)
    env.history.message(greenmsg( "Add basepair handles:") + " " + msg)
    assy.w.win_update()
    return
コード例 #10
0
def add_basepair_handles_to_selected_atoms(glpane):  #bruce 080515
    assy = glpane.assy
    goodcount, badcount = add_basepair_handles_to_atoms(assy.selatoms.values())
    msg = "adding handles to %d duplex Gv5 atom(s)" % (goodcount, )
    if badcount:
        msg += " (but not %d other selected atom(s))" % (badcount, )
    msg = fix_plurals(msg)
    env.history.message(greenmsg("Add basepair handles:") + " " + msg)
    assy.w.win_update()
    return
コード例 #11
0
def mark_selected_atoms_command(glpane): # untested
    """
    current part only...
    """
    assy = glpane.win.assy
    atoms = assy.selatoms.values()
    mark_atoms(atoms)
    msg = "marked %d selected atom(s)" % len(atoms) #e could use part of this string in jig name too
    msg = fix_plurals(msg)
    env.history.message(quote_html(msg))    
    return
コード例 #12
0
def mark_selected_atoms_command(glpane): # untested
    """
    current part only...
    """
    assy = glpane.win.assy
    atoms = assy.selatoms.values()
    mark_atoms(atoms)
    msg = "marked %d selected atom(s)" % len(atoms) #e could use part of this string in jig name too
    msg = fix_plurals(msg)
    env.history.message(quote_html(msg))
    return
コード例 #13
0
    def makeChunkFromSelectedAtoms(self):
        """
        Create a new chunk from the selected atoms.
        """

        # ninad 070411 moved the original method out of 'merge' method to
        # facilitate implementation of 'Create New Chunk
        # from selected atoms' feature

        cmd = greenmsg("Create New Chunk: ")
        if not self.selatoms:
            msg1 = "Create New Chunk: "
            msg2 = redmsg("Select some atoms first to create a new chunk")
            env.history.message(msg1 + msg2)
            return

        # ninad070411 : Following checks if the selected molecules
        # belong to more than one chunk. If they don't (i.e. if they are a part of
        # a sinle chunk, it returns from the method with proper histry msg

        molList = []
        for atm in self.selatoms.values():
            if not len(molList) > 1:
                mol = atm.molecule
                if mol not in molList:
                    molList.append(mol)

        if len(molList) < 2:
            msg1 = "Create New Chunk: "
            msg2 = redmsg(
                "Not created as the selected atoms are part of the \
            same chunk."
            )
            env.history.message(msg1 + msg2)
            return

        # bruce 060329 new feature: work on atoms too (put all selected atoms into a new chunk)
        self.ensure_toplevel_group()  # avoid bug for part containing just one chunk, all atoms selected
        numol = Chunk(self.assy, gensym("Chunk", self.assy))
        natoms = len(self.selatoms)
        for a in self.selatoms.values():
            # leave the moved atoms picked, so still visible
            a.hopmol(numol)
        self.addmol(numol)
        # e should we add it in the same groups (and just after the chunks) which these atoms used to belong to?
        # could use similar scheme to placing jigs...
        msg = fix_plurals(
            "made chunk from %d atom(s)" % natoms
        )  # len(numol.atoms) would count bondpoints, this doesn't
        msg = msg.replace("chunk", numol.name)
        env.history.message(cmd + msg)
        self.w.win_update()
コード例 #14
0
 def _emit_one_summary_message(self, format, count):
     msg = format.replace("[N]", "%s" % count)
     # note: not an error if format doesn't contain "[N]"
     # assume msg contains '(s)' -- could check this instead to avoid debug print
     msg = fix_plurals(msg, between=3)
     # kluge: if a large between option is generally safe,
     # it ought to be made the default in fix_plurals
     #
     # todo: review for safety in case msg contains HTML (e.g. colors)
     # todo: ideally format would be a class instance which knew warning status, etc,
     # rather than containing html
     self.message(msg)
     return
コード例 #15
0
 def cm_select_jigs_atoms(self): #bruce 050504
     nodeset = self.topmost_selected_nodes()
     otherpart = {} #bruce 050505 to fix bug 589
     did_these = {}
     nprior = len(self.assy.selatoms)
     for jig in nodeset:
         assert isinstance( jig, Jig) # caller guarantees they are all jigs
         # If we didn't want to desel the jig, I'd have to say:
             # note: this does not deselect the jig (good); and permit_pick_atoms would deselect it (bad);
             # so to keep things straight (not sure this is actually needed except to avoid a debug message),
             # just set SELWHAT_ATOMS here; this is legal because no chunks are selected. Actually, bugs might occur
             # in which that's not true... I forget whether I fixed those recently or only analyzed them (due to delays
             # in update event posting vs processing)... but even if they can occur, it's not high-priority to fix them,
             # esp since selection rules might get revised soon.
             ## self.assy.set_selwhat(SELWHAT_ATOMS)
         # but (I forgot when I wrote that) we *do* desel the jig,
         # so instead I can just say:
         self.assy.part.permit_pick_atoms() # changes selwhat and deselects all chunks, jigs, and groups
         # [bruce 050519 8pm]
         for atm in jig.atoms:
             if atm.molecule.part == jig.part:
                 atm.pick()
                 did_these[atm.key] = atm
             else:
                 otherpart[atm.key] = atm
         ## jig.unpick() # not done by picking atoms [no longer needed since done by permit_pick_atoms]
     msg = fix_plurals("Selected %d atom(s)" % len(did_these)) # might be 0, that's ok
     if nprior: #bruce 050519
         #e msg should distinguish between atoms already selected and also selected again just now,
         # vs already and not now; for now, instead, we just try to be ambiguous about that
         msg += fix_plurals(" (%d atom(s) remain selected from before)" % nprior)
     if otherpart:
         msg += fix_plurals(" (skipped %d atom(s) which were not in this Part)" % len(otherpart))
         msg = orangemsg(msg) # the whole thing, I guess
     env.history.message(msg)
     self.win.win_update()
     # note: caller (which puts up context menu) does
     # self.win.update_select_mode(); we depend on that. [### still true??]
     return
コード例 #16
0
 def _emit_one_summary_message(self, format, count):
     msg = format.replace("[N]", "%s" % count)
         # note: not an error if format doesn't contain "[N]"
     # assume msg contains '(s)' -- could check this instead to avoid debug print
     msg = fix_plurals(msg, between = 3)
         # kluge: if a large between option is generally safe,
         # it ought to be made the default in fix_plurals
         #
         # todo: review for safety in case msg contains HTML (e.g. colors)
         # todo: ideally format would be a class instance which knew warning status, etc,
         # rather than containing html
     self.message( msg)
     return
コード例 #17
0
    def getAngleHighlightedAtomAndSelAtoms(self, ppa2, ppa3, selectedAtomList,
                                           bendAngPrecision):
        """
        Returns the angle between the last two selected atoms and the current highlighted atom. 
        If the highlighed atom is also one of the selected atoms and there are only 2 selected atoms other than 
        the highlighted one then it returns None.(then the function calling this routine needs to handle that case.) 
        """
        glpane = self.glpane
        lastSelAtom = None
        secondLastSelAtom = None

        ppa3Exists = self.lastTwoPickedInSelAtomList(
            ppa2, ppa3,
            selectedAtomList)  #checks if *both* ppa2 and ppa3 exist

        if len(selectedAtomList) == 3 and glpane.selobj in selectedAtomList:
            if ppa3Exists and not (glpane.selobj is ppa2
                                   or glpane.selobj is ppa3):
                lastSelAtom = ppa2
                secondLastSelAtom = ppa3
            else:
                #ninad060825 revised the following. Earlier code in v1.8 was correct but this one is simpler. Suggested by Bruce. I have tested it and is safe.
                tempAtomList = list(selectedAtomList)
                tempAtomList.remove(glpane.selobj)
                lastSelAtom = tempAtomList[0]
                secondLastSelAtom = tempAtomList[1]

        if len(
                selectedAtomList
        ) == 2:  #here I (ninad) don't care about whether itselected atom is also highlighted. It is handled below.
            if ppa3Exists:
                lastSelAtom = ppa2
                secondLastSelAtom = ppa3
            else:
                lastSelAtom = selectedAtomList[0]
                secondLastSelAtom = selectedAtomList[1]
            #ninad060821 No need to display angle info if highlighed object and lastpicked or secondlast picked
            #  object are identical
            if glpane.selobj in selectedAtomList:
                return False

        if lastSelAtom and secondLastSelAtom:
            angle = atom_angle_radians(glpane.selobj, lastSelAtom,
                                       secondLastSelAtom) * 180 / math.pi
            roundedAngle = str(round(angle, bendAngPrecision))
            angleStr = fix_plurals(
                "<font color=\"#0000FF\">Angle %s-%s-%s:</font> %s degree(s)" %
                (glpane.selobj, lastSelAtom, secondLastSelAtom, roundedAngle))
            return angleStr
        else:
            return False
コード例 #18
0
    def makeChunkFromSelectedAtoms(self):
        """
        Create a new chunk from the selected atoms.
        """

        #ninad 070411 moved the original method out of 'merge' method to
        #facilitate implementation of 'Create New Chunk
        #from selected atoms' feature

        cmd = greenmsg("Create New Chunk: ")
        if not self.selatoms:
            msg1 = "Create New Chunk: "
            msg2 = redmsg('Select some atoms first to create a new chunk')
            env.history.message(msg1 + msg2)
            return

        #ninad070411 : Following checks if the selected molecules
        #belong to more than one chunk. If they don't (i.e. if they are a part of
        # a sinle chunk, it returns from the method with proper histry msg

        molList = []
        for atm in self.selatoms.values():
            if not len(molList) > 1:
                mol = atm.molecule
                if mol not in molList:
                    molList.append(mol)

        if len(molList) < 2:
            msg1 = "Create New Chunk: "
            msg2 = redmsg('Not created as the selected atoms are part of the \
            same chunk.')
            env.history.message(msg1 + msg2)
            return

        #bruce 060329 new feature: work on atoms too (put all selected atoms into a new chunk)
        self.ensure_toplevel_group(
        )  # avoid bug for part containing just one chunk, all atoms selected
        numol = Chunk(self.assy, gensym("Chunk", self.assy))
        natoms = len(self.selatoms)
        for a in self.selatoms.values():
            # leave the moved atoms picked, so still visible
            a.hopmol(numol)
        self.addmol(numol)
        #e should we add it in the same groups (and just after the chunks) which these atoms used to belong to?
        # could use similar scheme to placing jigs...
        msg = fix_plurals(
            "made chunk from %d atom(s)" %
            natoms)  # len(numol.atoms) would count bondpoints, this doesn't
        msg = msg.replace('chunk', numol.name)
        env.history.message(cmd + msg)
        self.w.win_update()
コード例 #19
0
    def deleteConnected(self, atomlist=None):  # by mark
        """
        Delete any atom that can be reached from any currently
        selected atom through a sequence of bonds, and that is acceptable to the current selection filter.
        If <atomlist> is supplied, use it instead of the currently selected atoms.
        """

        cmd = greenmsg("Delete Connected: ")

        if atomlist is None and not self.selatoms:
            msg = redmsg("No atoms selected")
            env.history.message(cmd + msg)
            return

        if atomlist is None:  # test for None since atomlist can be an empty list.
            atomlist = self.selatoms.values()

        catoms = self.getConnectedAtoms(atomlist)
        if not len(catoms): return

        natoms = 0
        for atom in catoms[:]:
            if atom.killed():
                continue
                #bruce 060331 precaution, to avoid counting bondpoints twice
                # (once when atom is them, once when they die when we kill their base atom)
                # if they can be in the passed-in list or the getConnectedAtoms retval
                # (I don't know if they can be)
            if atom.is_singlet():
                continue  #bruce 060331 precaution, related to above but different (could conceivably have valence errors w/o it)
            if atom.filtered():
                continue  #bruce 060331 fix a bug (don't know if reported) by doing 'continue' rather than 'return'.
                # Note, the motivation for 'return' might have been (I speculate) to not traverse bonds through filtered atoms
                # (so as to only delete a connected set of atoms), but the old code's 'return' was not a correct
                # implementation of that, in general; it might even have deleted a nondeterministic set of atoms,
                # depending on python dict item order and/or their order of deposition or their order in the mmp file.
            natoms += 1
            atom.kill()

        from platform_dependent.PlatformDependent import fix_plurals
        info = fix_plurals("%d connected atom(s) deleted." % natoms)
        #bruce 060331 comment: this message is sometimes wrong, since caller has deleted some atoms on click 1 of
        # a double click, and then calls us on click 2 to delete the atoms connected to the neighbors of those.
        # To fix this, the caller ought to pass us the number of atoms it deleted, for us to add to our number,
        # or (better) we ought to return the number we delete so the caller can print the history message itself.
        env.history.message(cmd + info)
        ## self.o.gl_update()
        self.w.win_update(
        )  #bruce 060331 possible bugfix (bug is unconfirmed) -- update MT too, in case some chunk is gone now
        return
コード例 #20
0
    def _make_bonds_4(self):
        msg = fix_plurals( "%d bond(s) made" % self.total_bonds_made)
        env.history.message(msg)

        # Update the slider tolerance label.  This fixed bug 502-14. Mark 050407
        self.reset_tolerance_label()

        if self.bondable_pairs_atoms:
            # This must be done before gl_update, or it will try to draw the
            # bondable singlets again, which generates errors.
            self.bondable_pairs = []
            self.ways_of_bonding = {}

        self.w.win_update()
コード例 #21
0
    def _make_bonds_4(self):
        msg = fix_plurals("%d bond(s) made" % self.total_bonds_made)
        env.history.message(msg)

        # Update the slider tolerance label.  This fixed bug 502-14. Mark 050407
        self.reset_tolerance_label()

        if self.bondable_pairs_atoms:
            # This must be done before gl_update, or it will try to draw the
            # bondable singlets again, which generates errors.
            self.bondable_pairs = []
            self.ways_of_bonding = {}

        self.w.win_update()
コード例 #22
0
    def deleteConnected(self, atomlist=None): # by mark
        """
        Delete any atom that can be reached from any currently
        selected atom through a sequence of bonds, and that is acceptable to the current selection filter.
        If <atomlist> is supplied, use it instead of the currently selected atoms.
        """

        cmd = greenmsg("Delete Connected: ")

        if atomlist is None and not self.selatoms:
            msg = redmsg("No atoms selected")
            env.history.message(cmd + msg)
            return

        if atomlist is None: # test for None since atomlist can be an empty list.
            atomlist = self.selatoms.values()

        catoms = self.getConnectedAtoms(atomlist)
        if not len(catoms): return

        natoms = 0
        for atom in catoms[:]:
            if atom.killed():
                continue
                    #bruce 060331 precaution, to avoid counting bondpoints twice
                    # (once when atom is them, once when they die when we kill their base atom)
                    # if they can be in the passed-in list or the getConnectedAtoms retval
                    # (I don't know if they can be)
            if atom.is_singlet():
                continue #bruce 060331 precaution, related to above but different (could conceivably have valence errors w/o it)
            if atom.filtered():
                continue #bruce 060331 fix a bug (don't know if reported) by doing 'continue' rather than 'return'.
                # Note, the motivation for 'return' might have been (I speculate) to not traverse bonds through filtered atoms
                # (so as to only delete a connected set of atoms), but the old code's 'return' was not a correct
                # implementation of that, in general; it might even have deleted a nondeterministic set of atoms,
                # depending on python dict item order and/or their order of deposition or their order in the mmp file.
            natoms += 1
            atom.kill()

        from platform_dependent.PlatformDependent import fix_plurals
        info = fix_plurals( "%d connected atom(s) deleted." % natoms)
            #bruce 060331 comment: this message is sometimes wrong, since caller has deleted some atoms on click 1 of
            # a double click, and then calls us on click 2 to delete the atoms connected to the neighbors of those.
            # To fix this, the caller ought to pass us the number of atoms it deleted, for us to add to our number,
            # or (better) we ought to return the number we delete so the caller can print the history message itself.
        env.history.message( cmd + info)
        ## self.o.gl_update()
        self.w.win_update() #bruce 060331 possible bugfix (bug is unconfirmed) -- update MT too, in case some chunk is gone now
        return
コード例 #23
0
    def fuse_atoms(self):
        """
        Deletes overlapping atoms found with the selected chunk(s).
        Only the overlapping atoms from the unselected chunk(s) are deleted.
        If the "Merge Chunks" checkbox
        is checked, then find_bondable_pairs() and make_bonds() is called,
        resulting in the merging of chunks.
        """
        total_atoms_fused = 0  # The total number of atoms fused.

        # fused_chunks stores the list of chunks that contain overlapping atoms
        # (but no selected chunks, though)
        fused_chunks = []

        # Delete overlapping atoms.
        for a1, a2 in self.overlapping_atoms:
            if a2.molecule not in fused_chunks:
                fused_chunks.append(a2.molecule)
            a2.kill()

#        print "Fused chunks list:", fused_chunks

# Merge the chunks if the "merge chunks" checkbox is checked
        if self.propMgr.mergeChunksCheckBox.isChecked(
        ) and self.overlapping_atoms:
            # This will bond and merge the selected chunks only with
            # chunks that had overlapping atoms.
            #& This has bugs when the bonds don't line up nicely between
            # overlapping atoms in the selected chunk
            #& and the bondpoints of the deleted atoms' neighbors.
            # Needs a bug report. mark 060406.
            self.find_bondable_pairs(fused_chunks)
            self.make_bonds()

        # Print history msgs to inform the user what happened.
        total_atoms_fused = len(self.overlapping_atoms)
        msg = fix_plurals("%d atom(s) fused with %d chunk(s)" %
                          (total_atoms_fused, len(fused_chunks)))
        env.history.message(msg)
        #"%s => %s overlapping atoms" % (tol_str, natoms_str)

        # Update the slider tolerance label.
        self.reset_tolerance_label()

        self.overlapping_atoms = []
        # This must be done before win_update(), or it will try to draw the
        # overlapping atoms again, which generates errors.

        self.w.win_update()
コード例 #24
0
 def cm_remove_empty_groups(self): #bruce 080207
     self.deselect_partly_picked_whole_nodes()
     nodeset = self.topmost_selected_nodes()
     empties = []
     def func(group):
         if not group.members and group.permits_ungrouping():
             empties.append(group)
     for node in nodeset:
         node.apply_to_groups(func)
     for group in empties:
         group.kill()
     msg = fix_plurals("removed %d empty Group(s)" % len(empties))
     env.history.message( msg)
     self.mt_update()
     return
コード例 #25
0
ファイル: TreeModel.py プロジェクト: vcsrc/nanoengineer
 def cm_ungroup(self):
     self.deselect_partly_picked_whole_nodes()
     nodeset = self.topmost_selected_nodes()
     assert len(nodeset) == 1  # caller guarantees this
     node = nodeset[0]
     assert node.permits_ungrouping()  # ditto
     need_update_parts = []
     pickme = None
     if node.is_top_of_selection_group():
         # this case is harder, since dissolving this node causes its members to become
         # new selection groups. Whether there's one or more members, Part structure needs fixing;
         # if more than one, interpart bonds need breaking (or in future might keep some subsets of
         # members together; more likely we'd have a different command for that).
         # simplest fix -- just make sure to update the part structure when you're done.
         # [bruce 050316]
         need_update_parts.append(node.assy)
         #bruce 050419 comment: if exactly one child, might as well retain the same Part... does this matter?
         # Want to retain its name (if group name was automade)? think about this a bit before doing it...
         # maybe fixing bugs for >1 child case will also cover this case. ###e
         #bruce 050420 addendum: I did some things in Part.__init__ which might handle all this well enough. We'll see. ###@@@ #k
         #bruce 050528 addendum: it's not handled well enough, so try this: hmm, it's not enough! try adding pickme too... ###@@@
         if len(node.members) == 1 and node.part.topnode is node:
             node.part.topnode = pickme = node.members[0]
     if node.is_top_of_selection_group() and len(node.members) > 1:
         msg = "splitting %r into %d new clipboard items" % (
             node.name, len(node.members))
     else:
         msg = fix_plurals("ungrouping %d item(s) from " %
                           len(node.members)) + "%s" % node.name
     env.history.message(msg)
     node.ungroup()
     # this also unpicks the nodes... is that good? Not really, it'd be nice to see who they were,
     # and to be consistent with Group command, and to avoid a glpane redraw.
     # But it's some work to make it pick them now, so for now I'll leave it like that.
     # BTW, if this group is a clipboard item and has >1 member, we couldn't pick all the members anyway!
     #bruce 050528 addendum: we can do it in this case, temporarily, just to get selgroup changed:
     if pickme is not None:
         pickme.pick(
         )  # just to change selgroup (too lazy to look up the official way to only do that)
         pickme.unpick(
         )  # then make it look the same as for all other "ungroup" ops
     #e history.message?
     for assy in need_update_parts:
         assy.update_parts()  # this should break new inter-part bonds
     self.win.glpane.gl_update(
     )  #k needed? (e.g. for selection change? not sure. Needed if inter-part bonds break!)
     self.mt_update()
     return
コード例 #26
0
def select_atoms_with_errors_command(glpane):
    """
    current part only...
    """
    count = 0
    assy = glpane.win.assy
    for mol in assy.molecules: # current part only
        for atom in mol.atoms.itervalues():
            if atom._dna_updater__error:
                count += 1 # whether or not already selected
                atom.pick() # should be safe inside itervalues
                    ### REVIEW: selection filter effect not considered
    msg = "found %d pseudoatom(s) with dna updater errors in %r" % (count, assy.part)
    msg = fix_plurals(msg)
    env.history.message(quote_html(msg))
    return
コード例 #27
0
def select_atoms_with_errors_command(glpane):
    """
    current part only...
    """
    count = 0
    assy = glpane.win.assy
    for mol in assy.molecules: # current part only
        for atom in mol.atoms.itervalues():
            if atom._dna_updater__error:
                count += 1 # whether or not already selected
                atom.pick() # should be safe inside itervalues
                    ### REVIEW: selection filter effect not considered
    msg = "found %d pseudoatom(s) with dna updater errors in %r" % (count, assy.part)
    msg = fix_plurals(msg)
    env.history.message(quote_html(msg))
    return
コード例 #28
0
    def fuse_atoms(self):
        """
        Deletes overlapping atoms found with the selected chunk(s).
        Only the overlapping atoms from the unselected chunk(s) are deleted.
        If the "Merge Chunks" checkbox
        is checked, then find_bondable_pairs() and make_bonds() is called,
        resulting in the merging of chunks.
        """
        total_atoms_fused = 0 # The total number of atoms fused.

        # fused_chunks stores the list of chunks that contain overlapping atoms
        # (but no selected chunks, though)
        fused_chunks = []

        # Delete overlapping atoms.
        for a1, a2 in self.overlapping_atoms:
            if a2.molecule not in fused_chunks:
                fused_chunks.append(a2.molecule)
            a2.kill()

#        print "Fused chunks list:", fused_chunks

        # Merge the chunks if the "merge chunks" checkbox is checked
        if self.propMgr.mergeChunksCheckBox.isChecked() and self.overlapping_atoms:
            # This will bond and merge the selected chunks only with
            # chunks that had overlapping atoms.
            #& This has bugs when the bonds don't line up nicely between
            # overlapping atoms in the selected chunk
            #& and the bondpoints of the deleted atoms' neighbors.
            # Needs a bug report. mark 060406.
            self.find_bondable_pairs(fused_chunks)
            self.make_bonds()

        # Print history msgs to inform the user what happened.
        total_atoms_fused = len(self.overlapping_atoms)
        msg = fix_plurals( "%d atom(s) fused with %d chunk(s)" % (total_atoms_fused, len(fused_chunks)))
        env.history.message(msg)
        #"%s => %s overlapping atoms" % (tol_str, natoms_str)

        # Update the slider tolerance label.
        self.reset_tolerance_label()

        self.overlapping_atoms = []
            # This must be done before win_update(), or it will try to draw the
            # overlapping atoms again, which generates errors.

        self.w.win_update()
コード例 #29
0
ファイル: TreeModel.py プロジェクト: vcsrc/nanoengineer
    def cm_remove_empty_groups(self):  #bruce 080207
        self.deselect_partly_picked_whole_nodes()
        nodeset = self.topmost_selected_nodes()
        empties = []

        def func(group):
            if not group.members and group.permits_ungrouping():
                empties.append(group)

        for node in nodeset:
            node.apply_to_groups(func)
        for group in empties:
            group.kill()
        msg = fix_plurals("removed %d empty Group(s)" % len(empties))
        env.history.message(msg)
        self.mt_update()
        return
コード例 #30
0
    def getAngleHighlightedAtomAndSelAtoms(self, ppa2, ppa3, selectedAtomList, bendAngPrecision):
        """
        Returns the angle between the last two selected atoms and the current highlighted atom.
        If the highlighed atom is also one of the selected atoms and there are only 2 selected atoms other than
        the highlighted one then it returns None.(then the function calling this routine needs to handle that case.)
        """
        glpane = self.glpane
        lastSelAtom = None
        secondLastSelAtom = None

        ppa3Exists = self.lastTwoPickedInSelAtomList(ppa2, ppa3, selectedAtomList) #checks if *both* ppa2 and ppa3 exist

        if  len(selectedAtomList) ==3 and glpane.selobj in selectedAtomList:
            if ppa3Exists and not (glpane.selobj is ppa2 or glpane.selobj is ppa3):
                lastSelAtom = ppa2
                secondLastSelAtom = ppa3
            else:
                #ninad060825 revised the following. Earlier code in v1.8 was correct but this one is simpler. Suggested by Bruce. I have tested it and is safe.
                tempAtomList =list(selectedAtomList)
                tempAtomList.remove(glpane.selobj)
                lastSelAtom = tempAtomList[0]
                secondLastSelAtom = tempAtomList[1]

        if len(selectedAtomList) == 2: #here I (ninad) don't care about whether itselected atom is also highlighted. It is handled below.
            if ppa3Exists:
                lastSelAtom = ppa2
                secondLastSelAtom = ppa3
            else:
                 lastSelAtom = selectedAtomList[0]
                 secondLastSelAtom = selectedAtomList[1]
            #ninad060821 No need to display angle info if highlighed object and lastpicked or secondlast picked
            #  object are identical
            if glpane.selobj in selectedAtomList:
                return False

        if lastSelAtom and secondLastSelAtom:
            angle = atom_angle_radians( glpane.selobj, lastSelAtom,secondLastSelAtom ) * 180 / math.pi
            roundedAngle = str(round(angle, bendAngPrecision))
            angleStr = fix_plurals("<font color=\"#0000FF\">Angle %s-%s-%s:</font> %s degree(s)"
            %(glpane.selobj, lastSelAtom, secondLastSelAtom, roundedAngle))
            return angleStr
        else:
            return False
コード例 #31
0
 def cm_ungroup(self):
     self.deselect_partly_picked_whole_nodes()
     nodeset = self.topmost_selected_nodes()
     assert len(nodeset) == 1 # caller guarantees this
     node = nodeset[0]
     assert node.permits_ungrouping() # ditto
     need_update_parts = []
     pickme = None
     if node.is_top_of_selection_group():
         # this case is harder, since dissolving this node causes its members to become
         # new selection groups. Whether there's one or more members, Part structure needs fixing;
         # if more than one, interpart bonds need breaking (or in future might keep some subsets of
         # members together; more likely we'd have a different command for that).
         # simplest fix -- just make sure to update the part structure when you're done.
         # [bruce 050316]
         need_update_parts.append( node.assy)
         #bruce 050419 comment: if exactly one child, might as well retain the same Part... does this matter?
         # Want to retain its name (if group name was automade)? think about this a bit before doing it...
         # maybe fixing bugs for >1 child case will also cover this case. ###e
         #bruce 050420 addendum: I did some things in Part.__init__ which might handle all this well enough. We'll see. ###@@@ #k
         #bruce 050528 addendum: it's not handled well enough, so try this: hmm, it's not enough! try adding pickme too... ###@@@
         if len(node.members) == 1 and node.part.topnode is node:
             node.part.topnode = pickme = node.members[0]
     if node.is_top_of_selection_group() and len(node.members) > 1:
         msg = "splitting %r into %d new clipboard items" % (node.name, len(node.members))
     else:
         msg = fix_plurals("ungrouping %d item(s) from " % len(node.members)) + "%s" % node.name
     env.history.message( msg)
     node.ungroup()
     # this also unpicks the nodes... is that good? Not really, it'd be nice to see who they were,
     # and to be consistent with Group command, and to avoid a glpane redraw.
     # But it's some work to make it pick them now, so for now I'll leave it like that.
     # BTW, if this group is a clipboard item and has >1 member, we couldn't pick all the members anyway!
     #bruce 050528 addendum: we can do it in this case, temporarily, just to get selgroup changed:
     if pickme is not None:
         pickme.pick() # just to change selgroup (too lazy to look up the official way to only do that)
         pickme.unpick() # then make it look the same as for all other "ungroup" ops
     #e history.message?
     for assy in need_update_parts:
         assy.update_parts() # this should break new inter-part bonds
     self.win.glpane.gl_update() #k needed? (e.g. for selection change? not sure. Needed if inter-part bonds break!)
     self.mt_update()
     return
コード例 #32
0
 def Invert(self):
     """
     Invert the atoms of the selected chunk(s) around the chunk centers
     """
     mc = env.begin_op("Invert")
     cmd = greenmsg("Invert: ")
     
     if not self.selmols:
         msg = redmsg("No selected chunks to invert")
         env.history.message(cmd + msg)
         return
     self.changed()
     for m in self.selmols:
         m.stretch(-1.0)
     self.o.gl_update()
     
     info = fix_plurals( "Inverted %d chunk(s)" % len(self.selmols))
     env.history.message( cmd + info)
     env.end_op(mc) #e try/finally?
コード例 #33
0
    def align_NEW(self):
        """
        Align the axes of the selected movables to the axis of the movable
        that is placed at the highest order in the Model Tree
        """
        #@@This is not called yet.
        #method *always* uses the MT order to align chunks or jigs
        #This supports jigs (including reference planes) but it has following
        #bug -- It always uses the selected movable that is placed highest
        #in the Model Tree, as the reference axis for alignment. (Instead
        #it should align to the 'first selected movable'
        #(this doesn't happen (or very rarely happens) in old align method where
        #'selmols' is used.)

        cmd = greenmsg("Align to Common Axis: ")

        movables = self.assy.getSelectedMovables()
        for m in movables:
            print "movable =", m.name
        numMovables = len(movables)
        if len(movables) < 2:
            msg = redmsg("Need two or more selected chunks to align")
            env.history.message(cmd + msg)
            return
        self.changed()
        try:
            firstAxis = movables[0].getaxis()
            for m in movables[1:]:
                m.rot(Q(m.getaxis(), firstAxis))
            self.o.gl_update()
        except:
            print_compact_traceback(
                "bug: selected movable object doesn't have an \
            axis")
            msg = redmsg("bug: selected movable object doesn't have an axis")
            env.history.message(cmd + msg)
            return
        self.o.gl_update()

        info = fix_plurals( "Aligned %d item(s)" % (len(movables) - 1) ) \
            + " to axis of %s" % movables[0].name
        env.history.message(cmd + info)
        return
コード例 #34
0
    def Invert(self):
        """
        Invert the atoms of the selected chunk(s) around the chunk centers
        """
        mc = env.begin_op("Invert")
        cmd = greenmsg("Invert: ")

        if not self.selmols:
            msg = redmsg("No selected chunks to invert")
            env.history.message(cmd + msg)
            return
        self.changed()
        for m in self.selmols:
            m.stretch(-1.0)
        self.o.gl_update()

        info = fix_plurals( "Inverted %d chunk(s)" % len(self.selmols))
        env.history.message( cmd + info)
        env.end_op(mc) #e try/finally?
コード例 #35
0
    def align_NEW(self):
        """
        Align the axes of the selected movables to the axis of the movable
        that is placed at the highest order in the Model Tree
        """
        #@@This is not called yet.
        #method *always* uses the MT order to align chunks or jigs
        #This supports jigs (including reference planes) but it has following
        #bug -- It always uses the selected movable that is placed highest
        #in the Model Tree, as the reference axis for alignment. (Instead
        #it should align to the 'first selected movable'
        #(this doesn't happen (or very rarely happens) in old align method where
        #'selmols' is used.)

        cmd = greenmsg("Align to Common Axis: ")

        movables = self.assy.getSelectedMovables()
        for m in movables:
            print "movable =", m.name
        numMovables = len(movables)
        if len(movables) < 2:
            msg = redmsg("Need two or more selected chunks to align")
            env.history.message(cmd + msg)
            return
        self.changed()
        try:
            firstAxis = movables[0].getaxis()
            for m in movables[1:]:
                m.rot(Q(m.getaxis(),firstAxis))
            self.o.gl_update()
        except:
            print_compact_traceback ("bug: selected movable object doesn't have an \
            axis")
            msg = redmsg("bug: selected movable object doesn't have an axis")
            env.history.message(cmd + msg)
            return
        self.o.gl_update()

        info = fix_plurals( "Aligned %d item(s)" % (len(movables) - 1) ) \
            + " to axis of %s" % movables[0].name
        env.history.message( cmd + info)
        return
コード例 #36
0
ファイル: ops_atoms.py プロジェクト: elfion/nanoengineer
    def modifyDeleteBonds(self):
        """
        Delete all bonds between selected and unselected atoms or chunks
        """
        cmd = greenmsg("Delete Bonds: ")

        if not self.selatoms and not self.selmols:  # optimization, and different status msg
            msg = redmsg("Nothing selected")
            env.history.message(cmd + msg)
            return

        cutbonds = 0

        # Delete bonds between selected atoms and their neighboring atoms that are not selected.
        for a in self.selatoms.values():
            for b in a.bonds[:]:
                neighbor = b.other(a)
                if neighbor.element != Singlet:
                    if not neighbor.picked:
                        b.bust()
                        a.pick()  # Probably not needed, but just in case...
                        cutbonds += 1

        # Delete bonds between selected chunks and chunks that are not selected.
        for mol in self.selmols[:]:
            # "externs" contains a list of bonds between this chunk and a different chunk
            for b in mol.externs[:]:
                # atom1 and atom2 are the connect atoms in the bond
                if int(b.atom1.molecule.picked) + int(
                        b.atom2.molecule.picked) == 1:
                    b.bust()
                    cutbonds += 1

        msg = fix_plurals("%d bond(s) deleted" % cutbonds)
        env.history.message(cmd + msg)

        if self.selatoms and cutbonds:
            self.modifySeparate(
            )  # Separate the selected atoms into a new chunk
        else:
            self.w.win_update()  #e do this in callers instead?
        return
コード例 #37
0
 def selectConnected(self, atomlist = None):
     """
     Selects any atom that can be reached from any currently
     selected atom through a sequence of bonds.
     
     @param atomlist: If supplied, use this list of atoms to select connected
                      atoms instead of the currently selected atoms.
     @type  atomlist: List of atoms.
     
     @attention: Only correctly reports the number newly selected atoms.
     """
     ###@@@ should make sure we don't traverse interspace bonds, until all bugs creating them are fixed
     
     cmd = greenmsg("Select Connected: ")
     
     if atomlist is None and not self.selatoms:
         msg = redmsg("No atoms selected")
         env.history.message(cmd + msg)
         return
     
     if atomlist is None: # test for None since atomlist can be an empty list.
         atomlist = self.selatoms.values()
         
     catoms = self.getConnectedAtoms(atomlist)
     if not len(catoms): 
         return
     
     natoms = 0
     for atom in catoms[:]:
         if not atom.picked:
             atom.pick()
             if atom.picked:
                 # Just in case a selection filter was applied to this atom.
                 natoms += 1
         else:
             natoms += 1 # Counts atom that is already picked.
     
     from platform_dependent.PlatformDependent import fix_plurals
     info = fix_plurals( "%d new atom(s) selected." % natoms)
     env.history.message( cmd + info)
     self.o.gl_update()
コード例 #38
0
    def selectConnected(self, atomlist = None):
        """
        Selects any atom that can be reached from any currently
        selected atom through a sequence of bonds.

        @param atomlist: If supplied, use this list of atoms to select connected
                         atoms instead of the currently selected atoms.
        @type  atomlist: List of atoms.

        @attention: Only correctly reports the number newly selected atoms.
        """
        ###@@@ should make sure we don't traverse interspace bonds, until all bugs creating them are fixed

        cmd = greenmsg("Select Connected: ")

        if atomlist is None and not self.selatoms:
            msg = redmsg("No atoms selected")
            env.history.message(cmd + msg)
            return

        if atomlist is None: # test for None since atomlist can be an empty list.
            atomlist = self.selatoms.values()

        catoms = self.getConnectedAtoms(atomlist)
        if not len(catoms):
            return

        natoms = 0
        for atom in catoms[:]:
            if not atom.picked:
                atom.pick()
                if atom.picked:
                    # Just in case a selection filter was applied to this atom.
                    natoms += 1
            else:
                natoms += 1 # Counts atom that is already picked.

        from platform_dependent.PlatformDependent import fix_plurals
        info = fix_plurals( "%d new atom(s) selected." % natoms)
        env.history.message( cmd + info)
        self.o.gl_update()
コード例 #39
0
ファイル: ops_atoms.py プロジェクト: ematvey/NanoEngineer-1
    def modifyDeleteBonds(self):
        """
        Delete all bonds between selected and unselected atoms or chunks
        """
        cmd = greenmsg("Delete Bonds: ")
        
        if not self.selatoms and not self.selmols: # optimization, and different status msg
            msg = redmsg("Nothing selected")
            env.history.message(cmd + msg)
            return
        
        cutbonds = 0
        
        # Delete bonds between selected atoms and their neighboring atoms that are not selected.
        for a in self.selatoms.values():
            for b in a.bonds[:]:
                neighbor = b.other(a)
                if neighbor.element != Singlet:
                    if not neighbor.picked:
                        b.bust()
                        a.pick() # Probably not needed, but just in case...
                        cutbonds += 1

        # Delete bonds between selected chunks and chunks that are not selected.
        for mol in self.selmols[:]:
            # "externs" contains a list of bonds between this chunk and a different chunk
            for b in mol.externs[:]:
                # atom1 and atom2 are the connect atoms in the bond
                if int(b.atom1.molecule.picked) + int(b.atom2.molecule.picked) == 1: 
                    b.bust()
                    cutbonds += 1
                    
        msg = fix_plurals("%d bond(s) deleted" % cutbonds)
        env.history.message(cmd + msg)
        
        if self.selatoms and cutbonds:
            self.modifySeparate() # Separate the selected atoms into a new chunk
        else:
            self.w.win_update() #e do this in callers instead?
        return
コード例 #40
0
 def Stretch(self):
     """
     stretch a Chunk
     """
     mc = env.begin_op("Stretch")
     try:
         cmd = greenmsg("Stretch: ")
         
         if not self.selmols:
             msg =  redmsg("No selected chunks to stretch")
             env.history.message(cmd + msg)
         else:
             self.changed()
             for m in self.selmols:
                 m.stretch(1.1)
             self.o.gl_update()
             
             # Added history message.  Mark 050413.
             info = fix_plurals( "Stretched %d chunk(s)" % len(self.selmols))
             env.history.message( cmd + info)
     finally:
         env.end_op(mc)
     return
コード例 #41
0
    def Stretch(self):
        """
        stretch a Chunk
        """
        mc = env.begin_op("Stretch")
        try:
            cmd = greenmsg("Stretch: ")

            if not self.selmols:
                msg =  redmsg("No selected chunks to stretch")
                env.history.message(cmd + msg)
            else:
                self.changed()
                for m in self.selmols:
                    m.stretch(1.1)
                self.o.gl_update()

                # Added history message.  Mark 050413.
                info = fix_plurals( "Stretched %d chunk(s)" % len(self.selmols))
                env.history.message( cmd + info)
        finally:
            env.end_op(mc)
        return
コード例 #42
0
    def align(self):
        """

        """
        cmd = greenmsg("Align to Common Axis: ")

        if len(self.selmols) < 2:
            msg = redmsg("Need two or more selected chunks to align")
            env.history.message(cmd + msg)
            return
        self.changed() #bruce 050131 bugfix or precaution
        #ax = V(0,0,0)
        #for m in self.selmols:
        #    ax += m.getaxis()
        #ax = norm(ax)
        ax = self.selmols[0].getaxis() # Axis of first selected chunk
        for m in self.selmols[1:]:
            m.rot(Q(m.getaxis(),ax))
        self.o.gl_update()

        info = fix_plurals( "Aligned %d chunk(s)" % (len(self.selmols) - 1) ) \
            + " to chunk %s" % self.selmols[0].name
        env.history.message( cmd + info)
コード例 #43
0
    def align(self):
        """
        
        """
        cmd = greenmsg("Align to Common Axis: ")     
 
        if len(self.selmols) < 2:
            msg = redmsg("Need two or more selected chunks to align")
            env.history.message(cmd + msg)
            return
        self.changed() #bruce 050131 bugfix or precaution
        #ax = V(0,0,0)
        #for m in self.selmols:
        #    ax += m.getaxis()
        #ax = norm(ax)
        ax = self.selmols[0].getaxis() # Axis of first selected chunk
        for m in self.selmols[1:]:
            m.rot(Q(m.getaxis(),ax))
        self.o.gl_update()
        
        info = fix_plurals( "Aligned %d chunk(s)" % (len(self.selmols) - 1) ) \
            + " to chunk %s" % self.selmols[0].name
        env.history.message( cmd + info)
コード例 #44
0
    def alignmove(self):

        cmd = greenmsg("Move to Axis: ")

        if len(self.selmols) < 2:
            msg = redmsg("Need two or more selected chunks to align")
            env.history.message(cmd + msg)
            return
        self.changed()
        #ax = V(0,0,0)
        #for m in self.selmols:
        #    ax += m.getaxis()
        #ax = norm(ax)
        ax = self.selmols[0].getaxis() # Axis of first selected chunk
        ctr = self.selmols[0].center # Center of first selected chunk
        for m in self.selmols[1:]:
            m.rot(Q(m.getaxis(),ax))
            m.move(ctr-m.center) # offset

        self.o.gl_update()

        info = fix_plurals( "Aligned %d chunk(s)" % (len(self.selmols) - 1) ) \
            + " to chunk %s" % self.selmols[0].name
        env.history.message( cmd + info)
コード例 #45
0
 def alignmove(self):
     
     cmd = greenmsg("Move to Axis: ")
     
     if len(self.selmols) < 2:
         msg = redmsg("Need two or more selected chunks to align")
         env.history.message(cmd + msg)
         return
     self.changed()
     #ax = V(0,0,0)
     #for m in self.selmols:
     #    ax += m.getaxis()
     #ax = norm(ax)
     ax = self.selmols[0].getaxis() # Axis of first selected chunk
     ctr = self.selmols[0].center # Center of first selected chunk
     for m in self.selmols[1:]:
         m.rot(Q(m.getaxis(),ax))
         m.move(ctr-m.center) # offset
     
     self.o.gl_update()
     
     info = fix_plurals( "Aligned %d chunk(s)" % (len(self.selmols) - 1) ) \
         + " to chunk %s" % self.selmols[0].name
     env.history.message( cmd + info)
コード例 #46
0
    def modifySeparate(self, new_old_callback = None):
        """
        For each Chunk (named N) containing any selected atoms,
        move the selected atoms out of N (but without breaking any bonds)
        into a new Chunk which we name N-frag. If N is now empty, remove it.

        @param new_old_callback: If provided, then each time we create a new
            (and nonempty) fragment N-frag, call new_old_callback with the
            2 args N-frag and N (that is, with the new and old molecules).
        @type  new_old_callback: function

        @warning: we pass the old mol N to that callback, even if it has no
                  atoms and we deleted it from this assembly.
        """
        # bruce 040929 wrote or revised docstring, added new_old_callback feature
        # for use from Extrude.
        # Note that this is called both from a tool button and for internal uses.
        # bruce 041222 removed side effect on selection mode, after discussion
        # with Mark and Josh. Also added some status messages.
        # Questions: is it good to refrain from merging all moved atoms into one
        # new mol? If not, then if N becomes empty, should we rename N-frag to N?

        cmd = greenmsg("Separate: ")

        if not self.selatoms: # optimization, and different status msg
            msg =  redmsg("No atoms selected")
            env.history.message(cmd + msg)
            return
        if 1:
            #bruce 060313 mitigate bug 1627, or "fix it by doing something we'd rather not always have to do" --
            # create (if necessary) a new toplevel group right now (before addmol does), avoiding a traceback
            # when all atoms in a clipboard item part consisting of a single chunk are selected for this op,
            # and the old part.topnode (that chunk) disappears from loss of atoms before we add the newly made chunk
            # containing those same atoms.
            # The only things wrong with this fix are:
            # - It's inefficient (so is the main algorithm, and it'd be easy to rewrite it to be faster, as explained below).
            # - The user ends up with a new Group even if one would theoretically not have been needed.
            #   But that's better than a traceback and disabled session, so for A7 this fix is fine.
            # - The same problem might arise in other situations (though I don't know of any), so ideally we'd
            #   have a more general fix.
            # - It's nonmodular for this function to have to know anything about Parts.
            ##e btw, a simpler way to do part of the following is "part = self". should revise this when time to test it. [bruce 060329]
            someatom = self.selatoms.values()[0] # if atoms in multiple parts could be selected, we'd need this for all their mols
            part = someatom.molecule.part
            part.ensure_toplevel_group()
            # this is all a kluge; a better way would be to rewrite the main algorithm to find the mols
            # with selected atoms, only make numol for those, and add it (addmol) before transferring all the atoms to it.
            pass
        numolist=[]
        for mol in self.molecules[:]: # new mols are added during the loop!
            numol = Chunk(self.assy, gensym(mol.name + "-frag", self.assy)) # (in modifySeparate)
            for a in mol.atoms.values():
                if a.picked:
                    # leave the moved atoms picked, so still visible
                    a.hopmol(numol)
            if numol.atoms:
                numol.setDisplayStyle(mol.display) # Fixed bug 391.  Mark 050710
                numol.setcolor(mol.color, repaint_in_MT = False)
                    #bruce 070425, fix Extrude bug 2331 (also good for Separate in general), "nice to have" for A9
                self.addmol(numol) ###e move it to just after the one it was made from? or, end of same group??
                numolist+=[numol]
                if new_old_callback:
                    new_old_callback(numol, mol) # new feature 040929
        msg = fix_plurals("Created %d new chunk(s)" % len(numolist))
        env.history.message(cmd + msg)
        self.w.win_update() #e do this in callers instead?
コード例 #47
0
def writepdb(part, 
             filename, 
             mode = 'w', 
             excludeFlags = EXCLUDE_BONDPOINTS | EXCLUDE_HIDDEN_ATOMS
             ):
    """
    Write a PDB file of the I{part}.
    
    @param part: The part.
    @type  part: assembly
    
    @param filename: The fullpath of the PDB file to write. 
                     We don't care if it has the .pdb extension or not.
    @type  filename: string
                 
    @param mode: 'w' for writing (the default)
                 'a' for appending
    @type  mode: string
    
    @param excludeFlags: used to exclude certain atoms from being written, 
        where:
        WRITE_ALL_ATOMS = 0 (even writes hidden and invisble atoms)
        EXCLUDE_BONDPOINTS = 1 (excludes bondpoints)
        EXCLUDE_HIDDEN_ATOMS = 2 (excludes invisible atoms)
        EXCLUDE_DNA_ATOMS = 4 (excludes PAM3 and PAM5 pseudo atoms)
        EXCLUDE_DNA_AXIS_ATOMS = 8 (excludes PAM3 axis atoms)
        EXCLUDE_DNA_AXIS_BONDS = 16 (supresses PAM3 axis bonds)
    @type  excludeFlags: integer
    
    @note: Atoms and bonds of hidden chunks are never written.
    
    @see: U{B{PDB File Format}<http://www.wwpdb.org/documentation/format23/v2.3.html>}
    """

    if mode != 'a': # Precaution. Mark 2007-06-25
        mode = 'w'
    
    f = open(filename, mode) 
    # doesn't yet detect errors in opening file [bruce 050927 comment]
    
    # Atom object's key is the key, the atomSerialNumber is the value  
    atomsTable = {}
    # Each element of connectLists is a list of atoms to be connected with the
    # 1st atom in the list, i.e. the atoms to write into a CONECT record
    connectLists = []
    
    atomSerialNumber = 1

    from protein.model.Protein import enableProteins
    
    def exclude(atm): #bruce 050318
        """
        Exclude this atom (and bonds to it) from the file under the following
        conditions (as selected by excludeFlags):
            - if it is a singlet
            - if it is not visible
            - if it is a member of a hidden chunk
            - some dna-related conditions (see code for details)
        """
        # Added not visible and hidden member of chunk. This effectively deletes
        # these atoms, which might be considered a bug.
        # Suggested solutions:
        # - if the current file is a PDB and has hidden atoms/chunks, warn user
        #   before quitting NE1 (suggest saving as MMP).
        # - do not support native PDB. Open PDBs as MMPs; only allow export of
        #   PDB.
        # Fixes bug 2329. Mark 070423
        
        if excludeFlags & EXCLUDE_BONDPOINTS:
            if atm.element == Singlet: 
                return True # Exclude
        if excludeFlags & EXCLUDE_HIDDEN_ATOMS:
            if not atm.visible():
                return True # Exclude
        if excludeFlags & EXCLUDE_DNA_AXIS_ATOMS:
##            if atm.element.symbol in ('Ax3', 'Ae3'):
            #bruce 080320 bugfix: revise to cover new elements and PAM5.
            if atm.element.role == 'axis':
                return True # Exclude
        if excludeFlags & EXCLUDE_DNA_ATOMS:
            # PAM5 atoms begin at 200.
            #
            # REVIEW: better to check atom.element.pam?
            # What about "carbon nanotube pseudoatoms"?
            # [bruce 080320 question]
            if atm.element.eltnum >= 200:
                return True # Exclude
        # Always exclude singlets connected to DNA p-atoms.
        if atm.element == Singlet: 
            for a in atm.neighbors():
                if a.element.eltnum >= 200:
                    # REVIEW: see above comment about atom.element.pam vs >= 200
                    return True
        return False # Don't exclude.

    excluded = 0
    molnum   = 1
    chainIdChar  = 65 # ASCII "A"
    
    if mode == 'w':
        writePDB_Header(f)
        
    for mol in part.molecules:
        if mol.hidden:
            # Atoms and bonds of hidden chunks are never written.
            continue
        for a in mol.atoms.itervalues():
            if exclude(a):
                excluded += 1
                continue
            atomConnectList = []
            
            atomsTable[a.key] = atomSerialNumber
            if enableProteins:
                # piotr 080709 : Use more robust ATOM output code for Proteins.
                resId = 1
                resName = "UNK"
                atomName = a.element.symbol
                if mol.protein:
                    res = mol.protein.get_residuum(a)
                    if res:
                        resId = res.get_id()
                        resName = res.get_three_letter_code()
                        atomName = res.get_atom_name(a)
                writepdb_atom(a, 
                              f, 
                              atomSerialNumber, 
                              atomName, 
                              chr(chainIdChar), 
                              resId, 
                              resName)
            else:
                a.writepdb(f, atomSerialNumber, chr(chainIdChar))
            atomConnectList.append(a)
    
            for b in a.bonds:
                a2 = b.other(a)
                # The following removes bonds b/w PAM3 axis atoms.
                if excludeFlags & EXCLUDE_DNA_AXIS_BONDS:
##                    if a.element.symbol in ('Ax3', 'Ae3'):
##                        if a2.element.symbol in ('Ax3', 'Ae3'):
##                            continue
                    #bruce 080320 bugfix: revise to cover new elements and PAM5.
                    if a.element.role == 'axis' and a2.element.role == 'axis':
                            continue
                        
                if a2.key in atomsTable:
                    assert not exclude(a2) # see comment below
                    atomConnectList.append(a2)
                #bruce 050318 comment: the old code wrote every bond twice
                # (once from each end). I doubt we want that, so now I only
                # write them from the 2nd-seen end. (This also serves to
                # not write bonds to excluded atoms, without needing to check
                # that directly. The assert verifies this claim.)
            
            atomSerialNumber += 1
            if len(atomConnectList) > 1:
                connectLists.append(atomConnectList)
                # bruce 050318 comment: shouldn't we leave it out if 
                # len(atomConnectList) == 1?
                # I think so, so I'm doing that (unlike the previous code).

        # Write the chain TER-minator record
        #
        # COLUMNS     DATA TYPE         FIELD           DEFINITION
        # ------------------------------------------------------
        #  1 - 6      Record name       "TER     "
        #  7 - 11     Integer           serial          Serial number.
        # 18 - 20     Residue name      resName         Residue name.
        # 22          Character         chainID         Chain identifier.
        # 23 - 26     Integer           resSeq          Residue sequence number.
        # 27          AChar             iCode           Insertion code.
        f.write("TER   %5d          %1s\n" % (molnum, chr(chainIdChar)))

        molnum += 1
        chainIdChar += 1
        if chainIdChar > 126: # ASCII "~", end of PDB-acceptable chain chars
            chainIdChar = 32 # Rollover to ASCII " "
            
    for atomConnectList in connectLists:
        # Begin CONECT record ----------------------------------
        f.write("CONECT")
        for a in atomConnectList:
            index = atomsTable[a.key]
            f.write("%5d" % index)
        f.write("\n")
        # End CONECT record ----------------------------------
        connectLists = []
            
    f.write("END\n")
    
    f.close()
    
    if excluded:
        msg  = "Warning: excluded %d open bond(s) from saved PDB file; " \
             % excluded
        msg += "consider Hydrogenating and resaving." 
        msg  = fix_plurals(msg)
        env.history.message( orangemsg(msg))
    return # from writepdb
コード例 #48
0
    def run(self):
        """
        Minimize (or Adjust) the Selection or the current Part
        """
        #bruce 050324 made this method from the body of MWsemantics.modifyMinimize
        # and cleaned it up a bit in terms of how it finds the movie to use.

        #bruce 050412 added 'Sel' vs 'All' now that we have two different Minimize buttons.
        # In future the following code might become subclass-specific (and cleaner):

        ## fyi: this old code was incorrect, I guess since 'in' works by 'is' rather than '==' [not verified]:
        ## assert self.args in [['All'], ['Sel']], "%r" % (self.args,)

        #bruce 051129 revising this to clarify it, though command-specific subclasses would be better
        assert len(self.args) >= 1
        cmd_subclass_code = self.args[0]
        cmd_type = self.kws.get('type', 'Minimize')
            # one of 'Minimize' or 'Adjust' or 'Adjust Atoms'; determines conv criteria, name [bruce 060705]
        self.cmd_type = cmd_type # kluge, see comment where used

        engine = self.kws.get('engine', MINIMIZE_ENGINE_UNSPECIFIED)
        if (engine == MINIMIZE_ENGINE_UNSPECIFIED):
            engine = env.prefs[Adjust_minimizationEngine_prefs_key]

        if (engine == MINIMIZE_ENGINE_GROMACS_FOREGROUND):
            self.useGromacs = True
            self.background = False
        elif (engine == MINIMIZE_ENGINE_GROMACS_BACKGROUND):
            self.useGromacs = True
            self.background = True
        else:
            self.useGromacs = False
            self.background = False

        assert cmd_subclass_code in ['All', 'Sel', 'Atoms'] #e and len(args) matches that?

        # These words and phrases are used in history messages and other UI text;
        # they should be changed by specific commands as needed.
        # See also some computed words and phrases, e.g. self.word_Minimize,
        # below the per-command if stamements. [bruce 060705]
        # Also set flags for other behavior which differs between these commands.
        if cmd_type.startswith('Adjust'):

            self.word_minimize = "adjust"
            self.word_minimization = "adjustment"
            self.word_minimizing = "adjusting"

            anchor_all_nonmoving_atoms = False
            pass

        else:

            assert cmd_type.startswith('Minimize')
            self.word_minimize = "minimize"
            self.word_minimization = "minimization"
            self.word_minimizing = "minimizing"

            anchor_all_nonmoving_atoms = True
                #bruce 080513 revision to implement nfr bug 2848 item 2
                # (note: we might decide to add a checkbox for this into the UI,
                #  and just change its default value for Minimize vs Adjust)
            pass

        self.word_Minimize = _capitalize_first_word( self.word_minimize)
        self.word_Minimizing = _capitalize_first_word( self.word_minimizing)

        if cmd_subclass_code == 'All':
            cmdtype = _MIN_ALL
            cmdname = "%s All" % self.word_Minimize

        elif cmd_subclass_code == 'Sel':
            cmdtype = _MIN_SEL
            cmdname = "%s Selection" % self.word_Minimize

        elif cmd_subclass_code == 'Atoms':
            #bruce 051129 added this case for Local Minimize (extending a kluge -- needs rewrite to use command-specific subclass)
            cmdtype = _LOCAL_MIN
            cmdname = "%s Atoms"  % self.word_Minimize #bruce 060705; some code may assume this is always Adjust Atoms, as it is
            # self.args is parsed later

        else:
            assert 0, "unknown cmd_subclass_code %r" % (cmd_subclass_code,)
        self.cmdname = cmdname #e in principle this should come from a subclass for the specific command [bruce 051129 comment]
        startmsg = cmdname + ": ..."
        del cmd_subclass_code

        # remove model objects inserted only for feedback from prior runs
        # (both because it's a good feature, and to avoid letting them
        #  mess up this command) [bruce 080520]
        from simulation.runSim import part_contains_pam_atoms
            # kluge to use this function for this purpose
            # (it's called later for other reasons)
        hasPAM_junk = part_contains_pam_atoms( self.part,
                            kill_leftover_sim_feedback_atoms = True )
        self.part.assy.update_parts() ###k is this always safe or good?

        # Make sure some chunks are in the part.
        # (Valid for all cmdtypes -- Minimize only moves atoms, even if affected by jigs.)
        if not self.part.molecules: # Nothing in the part to minimize.
            env.history.message(greenmsg(cmdname + ": ") + redmsg("Nothing to %s." % self.word_minimize))
            return

        if cmdtype == _MIN_SEL:
            selection = self.part.selection_from_glpane() # compact rep of the currently selected subset of the Part's stuff
            if not selection.nonempty():
                msg = greenmsg(cmdname + ": ") + redmsg("Nothing selected.") + \
                    " (Use %s All to %s the entire Part.)" % (self.word_Minimize, self.word_minimize)
                        #e might need further changes for Minimize Energy, if it's confusing that Sel/All is a dialog setting then
                env.history.message( msg)
                return
        elif cmdtype == _LOCAL_MIN:
            from operations.ops_select import selection_from_atomlist
            junk, atomlist, ntimes_expand = self.args
            selection = selection_from_atomlist( self.part, atomlist) #e in cleaned up code, selection object might come from outside
            selection.expand_atomset(ntimes = ntimes_expand) # ok if ntimes == 0

            # Rationale for adding monovalent atoms to the selection before
            # instantiating the sim_aspect
            #
            # (Refer to comments for sim_aspect.__init__.) Why is it safe to add
            # monovalent atoms to a selection? Let's look at what happens during a
            # local minimization.
            #
            # While minimizing, we want to simulate as if the entire rest of the
            # part is grounded, and only our selection of atoms is free to move. The
            # most obvious approach would be to minimize all the atoms in the part
            # while applying anchors to the atoms that aren't in the selection. But
            # minimizing all the atoms, especially if the selection is small, is very
            # wasteful. Applying the simulator to atoms is expensive and we want to
            # minimize as few atoms as possible.
            #
            # [revision, bruce 080513: this discussion applies for Adjust,
            #  but the policy for Minimize is being changed to always include
            #  all atoms, even if most of them are anchored,
            #  re nfr bug 2848 item 2.]
            #
            # A more economical approach is to anchor the atoms for two layers going
            # out from the selection. The reason for going out two layers, and not just
            # one layer, is that we need bond angle terms to simulate accurately. When
            # we get torsion angles we will probably want to bump this up to three
            # layers. [Now we're doing three layers -- bruce 080507]
            #
            # Imagine labeling all the atoms in the selection with zero. Then take the
            # set of unlabeled atoms that are bonded to a zero-labeled atom, and label
            # all the atoms in that set with one. Next, take the set of yet-unlabeled
            # atoms that are bonded to a one-labeled atom, and label the atoms in that
            # set with two. The atoms labeled one and two become our first and second
            # layers, and we anchor them during the minimization.
            #
            # In sim_aspect.__init__, the labels for zero, one and two correspond
            # respectively to membership in the dictionaries self._moving_atoms,
            # self._boundary1_atoms, and self._boundary2_atoms.
            #
            # If an atom in the selection is anchored, we don't need to go two layers
            # out from that atom, only one layer. So we can label it with one, even
            # though it's a member of the selection and would normally be labeled with
            # zero. The purpose in doing this is to give the simulator a few less atoms
            # to worry about.
            #
            # If a jig includes one of the selected atoms, but additionally includes
            # atoms outside the selection, then it may not be obvious how to simulate
            # that jig. For the present, the only jig that counts in a local
            # minimization is an anchor, because all the other jigs are too complicated
            # to simulate.
            #
            # The proposed fix here has the effect that monovalent atoms bonded to
            # zero-labeled atoms are also labeled zero, rather than being labeled one,
            # so they are allowed to move. Why is this OK to do?
            #
            # (1) Have we violated the assumption that the rest of the part is locked
            # down? Yes, as it applies to those monovalent atoms, but they are
            # presumably acceptable violations, since bug 1240 is regarded as a bug.
            #
            # (2) Have we unlocked any bond lengths or bond angles that should remain
            # locked? Again, only those which involve (and necessarily end at) the
            # monovalent atoms in question. The same will be true when we introduce
            # torsion terms.
            #
            # (3) Have we lost any ground on the jig front? If a jig includes one or
            # more of the monovalent atoms, possibly - but the only jigs we are
            # simulating in this case is anchors, and those will be handled correctly.
            # Remember that anchored atoms are only extended one layer, not two, but
            # with a monovalent atom bonded to a selected atom, no extension is
            # possible at all.
            #
            # One can debate about whether bug 1240 should be regarded as a bug. But
            # having accepted it as a bug, one cannot object to adding these monovalents
            # to the original selection.
            #
            # wware 060410 bug 1240
            atoms = selection.selatoms
            for atom in atoms.values():
                # enumerate the monovalents bonded to atom
                for atom2 in filter(lambda atom: not atom.is_singlet(), atom.baggageNeighbors()):
                    atoms[atom2.key] = atom2

        else:
            assert cmdtype == _MIN_ALL
            selection = self.part.selection_for_all()
                # like .selection_from_glpane() but for all atoms presently in the part [bruce 050419]
            # no need to check emptiness, this was done above

        self.selection = selection #e might become a feature of all CommandRuns, at some point

        # At this point, the conditions are met to try to do the command.
        env.history.message(greenmsg( startmsg)) #bruce 050412 doing this earlier

        # Disable some QActions (menu items/toolbar buttons) during minimize.
        self.win.disable_QActions_for_sim(True)
        try:
            simaspect = sim_aspect( self.part,
                                    selection.atomslist(),
                                    cmdname_for_messages = cmdname,
                                    anchor_all_nonmoving_atoms = anchor_all_nonmoving_atoms
                                   )
                #bruce 051129 passing cmdname
                # note: atomslist gets atoms from selected chunks, not only selected atoms
                # (i.e. it gets atoms whether you're in Select Atoms or Select Chunks mode)
            # history message about singlets written as H (if any);
            #bruce 051115 updated comment: this is used for both Minimize All and Minimize Selection as of long before 051115;
            # for Run Sim this code is not used (so this history message doesn't go out for it, though it ought to)
            # but the bug254 X->H fix is done (though different code sets the mapping flag that makes it happen).
            nsinglets_H = simaspect.nsinglets_H()
            if nsinglets_H: #bruce 051209 this message code is approximately duplicated elsewhere in this file
                info = fix_plurals( "(Treating %d bondpoint(s) as Hydrogens, during %s)" % (nsinglets_H, self.word_minimization) )
                env.history.message( info)
            nsinglets_leftout = simaspect.nsinglets_leftout()
            assert nsinglets_leftout == 0 # for now
            # history message about how much we're working on; these atomcounts include singlets since they're written as H
            nmoving = simaspect.natoms_moving()
            nfixed  = simaspect.natoms_fixed()
            info = fix_plurals( "(%s %d atom(s)" % (self.word_Minimizing, nmoving))
            if nfixed:
                them_or_it = (nmoving == 1) and "it" or "them"
                if anchor_all_nonmoving_atoms:
                    msg2 = "holding remaining %d atom(s) fixed" % nfixed
                else:
                    msg2 = "holding %d atom(s) fixed around %s" % (nfixed, them_or_it)
                info += ", " + fix_plurals(msg2 )
            info += ")"
            env.history.message( info)
            self.doMinimize(mtype = 1, simaspect = simaspect)
                # mtype = 1 means single-frame XYZ file.
                # [this also sticks results back into the part]
            #self.doMinimize(mtype = 2) # 2 = multi-frame DPB file.
        finally:
            self.win.disable_QActions_for_sim(False)
        simrun = self._movie._simrun #bruce 050415 klugetower
        if not simrun.said_we_are_done:
            env.history.message("Done.")
        return
コード例 #49
0
ファイル: TreeModel.py プロジェクト: vcsrc/nanoengineer
    def make_cmenuspec_for_set(self, nodeset, nodeset_whole, optflag):
        """
        #doc... see superclass docstring, and the term "menu_spec"
        """
        # Note: we use nodeset_whole (a subset of nodeset, or equal to it)
        # for operations which might be unsafe on partly-selected "whole nodes"
        # (i.e. unopenable nodes such as DnaStrand).
        # (Doing this on the Group operation is part of fixing bug 2948.)
        # These menu commands need corresponding changes in their cm methods,
        # namely, calling deselect_partly_picked_whole_nodes at the start.
        # All cm_duplicate methods (on individual nodes) also may need to do
        # this if they care about the selection.
        # [bruce 081218]

        #e some advice [bruce]: put "display changes" (eg Hide) before "structural changes" (such as Group/Ungroup)...
        #e a context-menu command "duplicate" which produces
        ##a copy of them, with related names and a "sibling" position.
        ##Whereas the menu command called "copy" produces a copy of the selected
        ##things in the system-wide "clipboard" shared by all apps.)

        # I think we might as well remake this every time, for most kinds of menus,
        # so it's easy for it to depend on current state.
        # I really doubt this will be too slow. [bruce 050113]

        if not nodeset:
            #e later we'll add useful menu commands for no nodes,
            # i.e. for a "context menu of the background".
            # In fact, we'll probably remove this special case
            # and instead let each menu command decide whether it applies
            # in this case.
            res = [('Model Tree (nothing selected)', noop, 'disabled')]
            #bruce 050505 adding some commands here (cm_delete_clipboard is a just-reported NFR from Mark)
            res.append(('Create new empty clipboard item',
                        self.cm_new_clipboard_item))
            lenshelf = len(
                self.assy.shelf.MT_kids())  #bruce 081217 use MT_kids
            if lenshelf:
                if lenshelf > 2:
                    text = 'Delete all %d clipboard items' % lenshelf
                else:
                    text = 'Delete all clipboard items'
                res.append((text, self.cm_delete_clipboard))
            return res

        res = []

        if len(nodeset_whole) < len(nodeset):
            # alert user to presence of partly-selected items [bruce 081218]
            # (which are not marked as selected on nodes seeable in MT)
            # (review: should we mark them in some other way?)
            #
            # (note about older text,
            #  "deselect %d partly-selected item(s)":
            #  the count is wrong if one partly-selected leaflike group
            #  contains more than one selected node, and nothing explains
            #  the situation well to the user)
            #
            # (about this text: it might be ambiguous whether they're too deep
            #  because of groups being closed, or being leaflike; mitigated
            #  by saying "shown" rather than "visible")
            text = "Deselect %d node(s) too deep to be shown" % \
                   (len(nodeset) - len(nodeset_whole))
            text = fix_plurals(text)
            res.append((text, self.cm_deselect_partly_selected_items))
            res.append(None)
            pass

        # old comment, not recently reviewed/updated as of 081217:
        # first put in a Hide item, checked or unchecked. But what if the hidden-state is mixed?
        # then there is a need for two menu commands! Or, use the command twice, fully hide then fully unhide -- not so good.
        # Hmm... let's put in Hide (with checkmark meaning "all hidden"), then iff that's not enough, Unhide.
        # So how do we know if a node is hidden -- this is only defined for leaf nodes now!
        # I guess we figure it out... I guess we might as well classify nodeset and its kids.
        # [update, bruce 080108/080306: does "and its kids" refer to members, or MT_kids?
        #  It might be some of each -- we would want to include members present but not shown
        #  in the MT (like the members of DnaGroup or DnaStrand), which are in members but not in
        #  MT_kids, but we might also want to cover "shared members", like DnaStrandChunks,
        #  which *might* be included in both strands and segments for this purpose (in the future;
        #  shared members are NIM now).]

        allstats = all_attrs_act_as_counters()

        for node in nodeset:
            node_stats = all_attrs_act_as_counters()
            node.apply2all(
                lambda node1: mt_accumulate_stats(node1, node_stats))
            allstats += node_stats  # totals to allstats

        # Hide command (and sometimes Unhide)

        # now can we figure out how much is/could be hidden, etc
        #e (later, modularize this, make assertfails only affect certain menu commands, etc)
        nleafs = allstats.n - allstats.ngroups
        assert nleafs >= 0
        nhidden = allstats.nhidden
        nunhidden = nleafs - nhidden  # since only leafs can be hidden
        assert nunhidden >= 0

        # We'll always define a Hide item. Checked means all is hidden (and the command will be unhide);
        # unchecked means not all is hidden (and the command will be hide).
        # First handle degenerate case where there are no leafs selected.
        if nleafs == 0:
            res.append(
                ('Hide', noop, 'disabled'))  # nothing that can be hidden
        elif nunhidden == 0:
            # all is hidden -- show that, and offer to unhide it all
            ## res.append(( 'Hidden', self.cm_unhide, 'checked'))
            res.append(('Unhide', self.cm_unhide))  # will this be better?
            ##e do we want special cases saying "Unhide All", here and below,
            # when all hidden items would be unhidden, or vice versa?
            # (on PartGroup, or in other cases, so detect by comparing counts for sel and tree_node.)
        elif nhidden > 0:
            # some is not hidden, some is hidden -- make this clear & offer both extremes
            ## res.append(( 'Hide (' + fix_plurals('%d item(s)' % nunhidden) + ')', self.cm_hide )) #e fix_plurals bug, worked around
            res.append(
                (fix_plurals('Unhide %d item(s)' % nhidden), self.cm_unhide))
            res.append(
                (fix_plurals('Hide %d item(s)' % nunhidden), self.cm_hide))
        else:
            # all is unhidden -- just offer to hide it
            res.append(('Hide', self.cm_hide))

        try:
            njigs = allstats.njigs
            if njigs == 1 and allstats.n == 1:
                # exactly one jig selected. Show its disabled state, with option to change this if permitted.
                # warning: depends on details of Jig.is_disabled() implem. Ideally we should ask Jig to contribute
                # this part of the menu-spec itself #e. [bruce 050421]
                jig = nodeset[0]
                if not isinstance(
                        jig, RectGadget
                ):  # remove this menu item for RectGadget [Huaicai 10/11/05]
                    disabled_must = jig.disabled_by_atoms(
                    )  # (by its atoms being in the wrong part)
                    disabled_choice = jig.disabled_by_user_choice
                    disabled_menu_item = disabled_must  # menu item is disabled iff jig disabled state can't be changed, ie is "stuck on"
                    checked = disabled_must or disabled_choice  # menu item is checked if it's disabled for whatever reason (also affects text)
                    if checked:
                        command = self.cm_enable
                        if disabled_must:
                            text = "Disabled (atoms in other Part)"
                        else:
                            text = "Disabled"
                    else:
                        command = self.cm_disable
                        text = "Disable"
                    res.append(
                        (text, command, checked and 'checked'
                         or None, disabled_menu_item and 'disabled' or None))
        except:
            print "bug in MT njigs == 1, ignored"
            ## raise # just during devel
            pass

        if nodeset_whole:

            res.append(None)  # separator
            # (from here on, only add these at start of optional items
            #  or sets of items)

            # Group command -- only offered for 2 or more subtrees of any Part,
            # or for exactly one clipboard item topnode itself if it's not already a Group.
            # [rules loosened by bruce 050419-050421]

            if optflag or len(nodeset_whole) >= 2:
                # note that these nodes are always in the same Part and can't include its topnode
                ok = True
            else:
                # exactly one node - ok iff it's a clipboard item and not a group
                node = nodeset_whole[0]
                ok = (node.dad is self.shelf_node and not node.is_group())
            if not ok:
                res.append(('Group', noop, 'disabled'))
            else:
                res.append(('Group', self.cm_group))

            # Ungroup command -- only when exactly one picked Group is what we have, of a suitable kind.
            # (As for Group, later this can become more general, tho in this case it might be general
            #  enough already -- it's more "self-contained" than the Group command can be.)

            offered_ungroup = False  # modified below; used by other menu items farther below

            if len(nodeset_whole) == 1 and nodeset_whole[0].permits_ungrouping(
            ):
                # (this implies it's a group, or enough like one)
                node = nodeset_whole[0]
                if not node.members:  #bruce 080207
                    #REVIEW: use MT_kids? [same issue in many places in this file, as of 080306]
                    #reply, bruce 081217: not yet; really we need a new Node or Group API method
                    # "offer to remove as empty Group"; meanwhile, be conservative by using .members
                    text = "Remove empty Group"
                elif node.dad == self.shelf_node and len(node.members) > 1:
                    # todo: "Ungroup into %d separate clipboard item(s)"
                    text = "Ungroup into separate clipboard items"  #bruce 050419 new feature (distinct text in this case)
                else:
                    # todo: "Ungroup %d item(s)"
                    text = "Ungroup"
                res.append((text, self.cm_ungroup))
                offered_ungroup = True
            else:
                # review: is this clear enough for nodes that are internally Groups
                # but for which permits_ungrouping is false, or would some other
                # text be better, or would leaving this item out be better?
                # An old suggestion of "Ungroup (unsupported)" seems bad now,
                # since it might sound like "a desired feature that's nim".
                # [bruce 081212 comment]
                res.append(("Ungroup", noop, 'disabled'))

            # Remove all %d empty Groups (which permit ungrouping) [bruce 080207]
            count_holder = [0]

            def func(group, count_holder=count_holder):
                if not group.members and group.permits_ungrouping():
                    count_holder[
                        0] += 1  # UnboundLocalError when this was count += 1

            for node in nodeset_whole:
                node.apply_to_groups(
                    func
                )  # note: this descends into groups that don't permit ungrouping, e.g. DnaStrand
            count = count_holder[0]
            if count == 1 and len(
                    nodeset_whole) == 1 and not nodeset_whole[0].members:
                # this is about the single top selected node,
                # so it's redundant with the Ungroup command above
                # (and if that was not offered, this should not be either)
                pass
            elif count:
                res.append(('Remove all %d empty Groups' % count,
                            self.cm_remove_empty_groups))
                # lack of fix_plurals seems best here; review when seen
            else:
                pass

            pass

        # Edit Properties command -- only provide this when there's exactly one thing to apply it to,
        # and it says it can handle it.
        ###e Command name should depend on what the thing is, e.g. "Part Properties", "Chunk Properties".
        # Need to add methods to return that "user-visible class name".
        res.append(None)  # separator

        if debug_flags.atom_debug:
            if len(nodeset) == 1:
                res.append(("debug._node =", self.cm_set_node))
            else:
                res.append(("debug._nodeset =", self.cm_set_node))

        if len(nodeset) == 1 and nodeset[0].editProperties_enabled():
            res.append(('Edit Properties...', self.cm_properties))
        else:
            res.append(('Edit Properties...', noop,
                        'disabled'))  # nim for multiple items

        #ninad 070320 - context menu option to edit color of multiple chunks
        if allstats.nchunks:
            res.append(("Edit Chunk Color...", self.cmEditChunkColor))
        if allstats.canShowOverlayText:
            res.append(("Show Overlay Text", self.cmShowOverlayText))
        if allstats.canHideOverlayText:
            res.append(("Hide Overlay Text", self.cmHideOverlayText))

        #bruce 070531 - rename node -- temporary workaround for inability to do this in MT, or, maybe we'll like it to stay
        if len(nodeset) == 1:
            node = nodeset[0]
            if node.rename_enabled():
                res.append(
                    ("Rename node...", self.cmRenameNode)
                )  ##k should it be called node or item in this menu text?

        # subsection of menu (not a submenu unless they specify one)
        # for node-class-specific menu items, when exactly one node
        # (old way, based on methodnames that start with __CM;
        #  and new better way, using Node method ModelTree_context_menu_section)
        if len(nodeset) == 1:
            node = nodeset[0]
            submenu = []
            attrs = filter(lambda attr: "__CM_" in attr, dir(
                node.__class__))  #e should do in order of superclasses
            attrs.sort()  # ok if empty list
            #bruce 050708 -- provide a way for these custom menu items to specify a list of menu_spec options (e.g. 'disabled') --
            # they should define a method with the same name + "__options" and have it return a list of options, e.g. ['disabled'],
            # or [] if it doesn't want to provide any options. It will be called again every time the context menu is shown.
            # If it wants to remove the menu item entirely, it can return the special value (not a list) 'remove'.
            opts = {}
            for attr in attrs:  # pass 1 - record menu options for certain commands
                if attr.endswith("__options"):
                    boundmethod = getattr(node, attr)
                    try:
                        lis = boundmethod()
                        assert type(lis) == type([]) or lis == 'remove'
                        opts[attr] = lis  # for use in pass 2
                    except:
                        print_compact_traceback(
                            "exception ignored in %r.%s(): " % (node, attr))
                        pass
            for attr in attrs:  # pass 2
                if attr.endswith("__options"):
                    continue
                classname, menutext = attr.split("__CM_", 1)
                boundmethod = getattr(node, attr)
                if callable(boundmethod):
                    lis = opts.get(attr + "__options") or []
                    if lis != 'remove':
                        mitem = tuple(
                            [menutext.replace('_', ' '), boundmethod] + lis)
                        submenu.append(mitem)
                elif boundmethod is None:
                    # kluge: None means remove any existing menu items (before the submenu) with this menutext!
                    res = filter(
                        lambda text_cmd: text_cmd and text_cmd[0] != menutext,
                        res)  # text_cmd might be None
                    while res and res[0] == None:
                        res = res[1:]
                    #e should also remove adjacent Nones inside res
                else:
                    assert 0, "not a callable or None: %r" % boundmethod
            if submenu:
                ## res.append(( 'other', submenu )) #e improve submenu name, ordering, location
                res.extend(
                    submenu
                )  # changed append to extend -- Mark and Bruce at Retreat 050621

            # new system, used in addition to __CM above (preferred in new code):
            # [bruce 080225]
            try:
                submenu = node.ModelTree_context_menu_section()
                assert submenu is not None  # catch a likely possible error specifically
                assert type(submenu) is type(
                    [])  # it should be a menu_spec list
            except:
                print_compact_traceback("exception ignored in %r.%s() " \
                                        "or in checking its result: " % \
                                        (node, 'ModelTree_context_menu_section'))
                submenu = []
            if submenu:
                res.extend(submenu)
            pass

        if nodeset_whole:
            # copy, cut, delete, maybe duplicate...
            # bruce 050704 revisions:
            # - these are probably ok for clipboard items; I'll enable them there and let them be tested there.
            # - I'll remove Copy when the selection only contains jigs that won't copy themselves
            #   unless some of their atoms are copied (which for now is true of all jigs).
            #   More generally (in principle -- the implem is not general), Copy should be removed
            #   when the selection contains nothing which makes sense to copy on its own,
            #   only things which make sense to copy only in conjunction with other things.
            #   I think this is equivalent to whether all the selected things would fail to get copied,
            #   when the copy command was run.
            # - I'll add Duplicate for single selected jigs which provide an appropriate method,
            #   and show it dimmed for those that don't.

            res.append(None)  # separator

            # figure out whether Copy would actually copy anything.
            part = nodeset_whole[
                0].part  # the same for all nodes in nodeset_whole
            sel = selection_from_part(
                part, use_selatoms=False
            )  #k should this be the first code to use selection_from_MT() instead?
            doit = False
            for node in nodeset_whole:
                if node.will_copy_if_selected(sel, False):
                    #wware 060329 added realCopy arg, False here (this is not a real copy, so do not issue a warning).
                    #bruce 060329 points out about realCopy being False vs True that at this point in the code we don't
                    # yet know whether the real copy will be made, and when we do, will_copy_if_selected
                    # might like to be re-called with True, but that's presently nim. ###@@@
                    #
                    # if this test is too slow, could inline it by knowing about Jigs here; but better to speed it up instead!
                    doit = True
                    break
            if doit:
                res.append(('Copy', self.cm_copy))
            # For single items, add a Duplicate command and enable it if they support the method. [bruce 050704 new feature]
            # For now, hardly anything offers this command, so I'm changing the plan, and removing it (not disabling it)
            # when not available. This should be reconsidered if more things offer it.
            if len(nodeset_whole) == 1:
                node = nodeset_whole[0]
                try:
                    method = node.cm_duplicate
                    # Warning 1: different API than self.cm_xxx methods (arg differs)
                    # or __CM_ methods (disabled rather than missing, if not defined).
                    # Warning 2: if a class provides it, no way for a subclass to stop
                    # providing it. This aspect of the API is bad, should be revised.
                    # Warning 3: consider whether each implem of this needs to call
                    # self.deselect_partly_picked_whole_nodes().
                    assert callable(method)
                except:
                    dupok = False
                else:
                    dupok = True
                if dupok:
                    res.append(('Duplicate', method))
                else:
                    pass  ## res.append(( 'Duplicate', noop, 'disabled' ))
            # Cut (unlike Copy), and Delete, should always be ok.
            res.append(('Cut', self.cm_cut))
            res.append(('Delete', self.cm_delete))

        #ninad060816 added option to select all atoms of the selected chunks.
        #I don't know how to handle a case when a whole group is selected.
        #So putting a condition allstats.nchunks == allstats.n.
        #Perhaps, I should unpick the groups while picking atoms?
        if allstats.nchunks == allstats.n and allstats.nchunks:
            res.append(
                (fix_plurals("Select all atoms of %d chunk(s)" %
                             allstats.nchunks), self.cmSelectAllAtomsInChunk))

        # add basic info on what's selected at the end
        # (later might turn into commands related to subclasses of nodes)

        if allstats.nchunks + allstats.njigs:
            # otherwise, nothing we can yet print stats on... (e.g. clipboard)

            res.append(None)  # separator

            res.append(("selection:", noop, 'disabled'))

            if allstats.nchunks:
                res.append((fix_plurals("%d chunk(s)" % allstats.nchunks),
                            noop, 'disabled'))

            if allstats.njigs:
                res.append((fix_plurals("%d jig(s)" % allstats.njigs), noop,
                            'disabled'))

            if allstats.nhidden:
                res.append(("(%d of these are hidden)" % allstats.nhidden,
                            noop, 'disabled'))

            if allstats.njigs == allstats.n and allstats.njigs:
                # only jigs are selected -- offer to select their atoms [bruce 050504]
                # (text searches for this code might like to find "Select this jig's" or "Select these jigs'")
                want_select_item = True  #bruce 051208
                if allstats.njigs == 1:
                    jig = nodeset[0]
                    if isinstance(
                            jig, RectGadget
                    ):  # remove menu item for RectGadget [Huaicai 10/11/05]
                        ## return res  -- this 'return' was causing bug 1189 by skipping the rest of the menu, not just this item.
                        # Try to do something less drastic. [bruce 051208]
                        want_select_item = False
                    else:
                        natoms = len(nodeset[0].atoms)
                        myatoms = fix_plurals("this jig's %d atom(s)" % natoms)
                else:
                    myatoms = "these jigs' atoms"
                if want_select_item:
                    res.append(
                        ('Select ' + myatoms, self.cm_select_jigs_atoms))

##        ##e following msg is not true, since nodeset doesn't include selection under selected groups!
##        # need to replace it with a better breakdown of what's selected,
##        # incl how much under selected groups is selected. Maybe we'll add a list of major types
##        # of selected things, as submenus, lower down (with commands like "select only these", "deselect these").
##
##        res.append(( fix_plurals("(%d selected item(s))" % len(nodeset)), noop, 'disabled' ))

# for single items that have a featurename, add wiki-help command [bruce 051201]
        if len(nodeset) == 1:
            node = nodeset[0]
            ms = wiki_help_menuspec_for_object(
                node
            )  # will be [] if this node should have no wiki help menu items
            #review: will this func ever need to know which widget is asking?
            if ms:
                res.append(None)  # separator
                res.extend(ms)

        return res  # from make_cmenuspec_for_set
コード例 #50
0
ファイル: ops_pam.py プロジェクト: elfion/nanoengineer
    def _convert_selection_to_pam_model(self,
                                        which_pam,
                                        commandname = "",
                                        make_ghost_bases = True, # only implemented for PAM3, so far
                                        ## remove_ghost_bases_from_PAM3 = True
                                       ): #bruce 080413
        """
        Convert the selected atoms (including atoms into selected chunks),
        which don't have errors (in the atoms or their dnaladders), into
        the specified pam model (some, none, or all might already be
        in that pam model), along with all atoms in the same basepairs,
        but only for kinds of ladders for which conversion is yet
        implemented. Print summaries to history.

        This is a user operation, so the dna updater has run
        and knows which atoms are errors, knows ladders of atoms, etc.
        We take advantage of that to simplify the implementation.
        """
        if not commandname:
            commandname = "Convert to %s" % which_pam # kluge, doesn't matter yet
                
        # find all selected atoms (including those in selected chunks)
        atoms = dict(self.selatoms)
        for chunk in self.selmols:
            atoms.update(chunk.atoms)

        if not atoms:
            env.history.message( greenmsg(commandname + ": ") + redmsg("Nothing selected.") )
            return
        
        # expand them to cover whole basepairs -- use ladders to help?
        # (the atoms with errors are not in valid ladders, so that
        #  is also an easy way to exclude those)

        num_atoms_with_good_ladders = 0
        ladders = {}
        ghost_bases = {} # initially only holds PAM5 ghost bases
        
        for atom in atoms.itervalues():
            try:
                ladder = atom.molecule.ladder 
            except AttributeError: # for .ladder
                continue # not the right kind of atom, etc
            if not ladder or not ladder.valid:
                continue
            if ladder.error:
                continue
            if not ladder.strand_rails: # bare axis
                continue
            num_atoms_with_good_ladders += 1
            ladders[ladder] = ladder
            # note: if atom is Pl, its Ss neighbors are treated specially
            # lower down in this method
            if atom.ghost and atom.element.pam == MODEL_PAM5:
                ghost_bases[atom.key] = atom
            continue

        orig_len_atoms = len(atoms) # for history messages, in case we add some

        # now iterate on the ladders, scanning their atoms to find the ones
        # in atoms, noting every touched baseindex

        # BUG: this does not notice Pls which are in atoms without either neighbor Ss being in atoms.
        # Future: fix by noticing them above in atom loop; see comment there.

        atoms_to_convert = {}
        ladders_to_inval = {}

        number_of_basepairs_to_convert = 0
        number_of_unpaired_bases_to_convert = 0

        ladders_needing_ghost_bases = {} # maps ladder to (ladder, list of indices) #bruce 080528
        
        for ladder in ladders:
            # TODO: if ladder can't convert (nim for that kind of ladder),
            # say so as a summary message, and skip it. (But do we know how
            # many atoms in our dict it had? if possible, say that too.)

            # TODO: if ladder doesn't need to convert (already in desired model),
            # skip it.
            
            length = len(ladder)
            index_set = {} # base indexes in ladder of basepairs which touch our dict of atoms
            rails = ladder.all_rails()
            if len(ladder.strand_rails) not in (1, 2):
                continue
            for rail in rails:
                for ind in range(length):
                    atom = rail.baseatoms[ind]
                    if atom.key in atoms:
                        # convert this base pair
                        index_set[ind] = ind
                        pass
                    continue
                continue
            # conceivable that for some ladders we never hit them;
            # for now, warn but skip them in that case
            if not index_set:
                print "unexpected: scanned %r but found nothing to convert (only Pls selected??)" % ladder # env.history?
            else:
                if len(ladder.strand_rails) == 2:
                    number_of_basepairs_to_convert += len(index_set)
                else:
                    number_of_unpaired_bases_to_convert += len(index_set)
                        # note: we do this even if the conversion will fail
                        # (as it does initially for single strand domains),
                        # since the summary error message from that is useful.
                    if make_ghost_bases and ladder.can_make_ghost_bases():
                        # initially, this test rules out free floating single strands;
                        # later we might be able to do this for them, which is why
                        # we do the test using that method rather than directly.
                        ladders_needing_ghost_bases[ladder] = (ladder, index_set.values())
                # see related code in _cmd_convert_to_pam method
                # in DnaLadder_pam_conversion.py
                ladders_to_inval[ladder] = ladder
                if 0 in index_set or (length - 1) in index_set:
                    for ladder2 in ladder.strand_neighbor_ladders():
                        # might contain Nones or duplicate entries
                        if ladder2 is not None:
                            ladders_to_inval[ladder2] = ladder2 # overkill if only one ind above was found
                for rail in rails:
                    baseatoms = rail.baseatoms
                    for ind in index_set:
                        atom = baseatoms[ind]
                        atoms_to_convert[atom.key] = atom # note: we also add ghost base atoms, below
                pass
            continue # next ladder

        if not atoms_to_convert:
            assert not number_of_basepairs_to_convert
            assert not number_of_unpaired_bases_to_convert
            assert not ladders_needing_ghost_bases
            if num_atoms_with_good_ladders < orig_len_atoms:
                # warn if we're skipping some atoms [similar code occurs twice in this method]
                msg = "%d atom(s) skipped, since not in valid, error-free DnaLadders"
                env.history.message( greenmsg(commandname + ": ") + orangemsg("Warning: " + fix_plurals(msg)))
            env.history.message( greenmsg(commandname + ": ") + redmsg("Nothing found to convert.") )
            return
        
        # print a message about what we found to convert
        what1 = what2 = ""
        if number_of_basepairs_to_convert:
            what1 = fix_plurals( "%d basepair(s)" % number_of_basepairs_to_convert )
        if number_of_unpaired_bases_to_convert:
            # doesn't distinguish sticky ends from free-floating single strands (fix?)
            what2 = fix_plurals( "%d unpaired base(s)" % number_of_unpaired_bases_to_convert )
        if what1 and what2:
            what = what1 + " and " + what2
        else:
            what = what1 + what2
        
        env.history.message( greenmsg(commandname + ": ") + "Will convert %s ..." % what )

        # warn if we're skipping some atoms [similar code occurs twice in this method]
        if num_atoms_with_good_ladders < orig_len_atoms:
            msg = "%d atom(s) skipped, since not in valid, error-free DnaLadders"
            env.history.message( orangemsg("Warning: " + fix_plurals(msg)))

        print "%s will convert %d atoms, touching %d ladders" % \
              ( commandname, len(atoms_to_convert), len(ladders_to_inval) )

        # make ghost bases as needed for this conversion (if enabled -- not by default since not yet working ####)
        # (this must not delete any baseatoms in atoms, or run the dna updater
        #  or otherwise put atoms into different ladders, but it can make new
        #  atoms in new chunks, as it does)

        if debug_pref_enable_pam_convert_sticky_ends():
            for ladder, index_list in ladders_needing_ghost_bases.itervalues():
                baseatoms = ladder.make_ghost_bases(index_list) # note: index_list is not sorted; that's ok
                    # note: this makes them in a separate chunk, and returns them
                    # as an atom list, but doesn't add the new chunk to the ladder.
                    # the next dna updater run will fix that (making a new ladder
                    # that includes all atoms in baseatoms and the old ladder).
                for ind in index_list:
                    atom = baseatoms[ind]
                    atoms_to_convert[atom.key] = atom
        
        # cause the dna updater (which would normally run after we return,
        #  but is also explicitly run below) to do the rest of the conversion
        # (and report errors for whatever it can't convert)
        
        for ladder in ladders_to_inval:
            ladder._dna_updater_rescan_all_atoms()

        for atom in atoms_to_convert:
            _f_baseatom_wants_pam[atom] = which_pam

        # run the dna updater explicitly
        
        print "about to run dna updater for", commandname
        self.assy.update_parts() # not a part method
            # (note: this catches dna updater exceptions and turns them into redmsgs.)
        print "done with dna updater for", commandname

        if debug_pref_remove_ghost_bases_from_pam3():
            # note: in commented out calling code above, this was a flag
            # option, remove_ghost_bases_from_PAM3;
            # that will be revived if we have a separate command for this.
            #
            # actually we only remove the ones we noticed as PAM5 above,
            # and succeeded in converting to PAM3.
            good = bad = 0
            for atom in ghost_bases.values(): # must not be itervalues
                if atom.element.pam == MODEL_PAM3:
                    good += 1
                    for n in atom.neighbors():
                        if n.is_ghost():
                            ghost_bases[n.key] = n
                else:
                    bad += 1
                    del ghost_bases[atom.key]
                continue
            if good:
                print "removing %d ghost base(s) we converted to PAM3" % good
            if bad:
                print "leaving %d ghost base(s) we didn't convert to PAM3" % bad
            if not bool(good) == bool(ghost_bases): # should never happen
                print "bug: bool(good) != bool(ghost_bases), for", good, ghost_bases
            del good, bad
            if ghost_bases:
                for atom in ghost_bases.itervalues():
                    atom.kill()
                    # todo: probably should use prekill code to avoid
                    # intermediate bondpoint creation, even though there
                    # are not usually a lot of atoms involved at once
                    continue
                print "about to run dna updater 2nd time for", commandname
                self.assy.update_parts()
                print "done with dna updater 2nd time for", commandname
            pass

        env.history.message( greenmsg( commandname + ": " + "Done." ))

        self.assy.w.win_update()

        return
コード例 #51
0
ファイル: TreeModel.py プロジェクト: vcsrc/nanoengineer
    def cm_group(
        self
    ):  # bruce 050126 adding comments and changing behavior; 050420 permitting exactly one subtree
        """
        put the selected subtrees (one or more than one) into a new Group (and update)
        """
        ##e I wonder if option/alt/middleButton should be like a "force" or "power" flag
        # for cmenus; in this case, it would let this work even for a single element,
        # making a 1-item group. That idea can wait. [bruce 050126]
        #bruce 050420 making this work inside clipboard items too
        # TEST if assy.part updated in time ####@@@@ -- no, change to selgroup!
        self.deselect_partly_picked_whole_nodes()
        sg = self.assy.current_selgroup()
        node = sg.hindmost()  # smallest nodetree containing all picked nodes
        if not node:
            env.history.message(
                "nothing selected to Group")  # should never happen
            return
        if node.picked:
            #bruce 050420: permit this case whenever possible (formation of 1-item group);
            # cmenu constructor should disable or leave out the menu command when desired.
            if node != sg:
                assert node.dad  # in fact, it'll be part of the same sg subtree (perhaps equal to sg)
                node = node.dad
                assert not node.picked
                # fall through -- general case below can handle this.
            else:
                # the picked item is the topnode of a selection group.
                # If it's the main part, we could make a new group inside it
                # containing all its children (0 or more). This can't happen yet
                # so I'll be lazy and save it for later.
                assert node != self.assy.tree
                # Otherwise it's a clipboard item. Let the Part take care of it
                # since it needs to patch up its topnode, choose the right name,
                # preserve its view attributes, etc.
                assert node.part.topnode == node
                newtop = node.part.create_new_toplevel_group()
                env.history.message(
                    "made new group %s" % newtop.name
                )  ###k see if this looks ok with autogenerated name
                self.mt_update()
                return
        # (above 'if' might change node and then fall through to here)
        # node is an unpicked Group inside (or equal to) sg;
        # more than one of its children (or exactly one if we fell through from the node.picked case above)
        # are either picked or contain something picked (but maybe none of them are directly picked).
        # We'll make a new Group inside node, just before the first child containing
        # anything picked, and move all picked subtrees into it (preserving their order;
        # but losing their structure in terms of unpicked groups that contain some of them).
        ###e what do we do with the picked state of things we move? worry about the invariant! ####@@@@

        # make a new Group (inside node, same assy)
        ###e future: require all assys the same, or, do this once per topnode or assy-node.
        # for now: this will have bugs when done across topnodes!
        # so the caller doesn't let that happen, for now. [050126]
        new = Group(gensym("Group", node.assy), node.assy,
                    node)  # was self.assy
        assert not new.picked

        # put it where we want it -- before the first node member-tree with anything picked in it
        for m in node.members:
            if m.haspicked():
                assert m != new
                ## node.delmember(new) #e (addsibling ought to do this for us...) [now it does]
                m.addsibling(new, before=True)
                break  # (this always happens, since something was picked under node)
        node.apply2picked(lambda (x): x.moveto(new))
        # this will have skipped new before moving anything picked into it!
        # even so, I'd feel better if it unpicked them before moving them...
        # but I guess it doesn't. for now, just see if it works this way... seems to work.
        # ... later [050316], it evidently does unpick them, or maybe delmember does.
        msg = fix_plurals(
            "grouped %d item(s) into " % len(new.members)) + "%s" % new.name
        env.history.message(msg)

        # now, should we pick the new group so that glpane picked state has not changed?
        # or not, and then make sure to redraw as well? hmm...
        # - possibility 1: try picking the group, then see if anyone complains.
        # Caveat: future changes might cause glpane redraw to occur anyway, defeating the speed-purpose of this...
        # and as a UI feature I'm not sure what's better.
        # - possibility 2: don't pick it, do update glpane. This is consistent with Ungroup (for now)
        # and most other commands, so I'll do it.
        #
        # BTW, the prior code didn't pick the group
        # and orginally didn't unpick the members but now does, so it had a bug (failure to update
        # glpane to show new picked state), whose bug number I forget, which this should fix.
        # [bruce 050316]
        ## new.pick() # this will emit an undesirable history message... fix that?
        self.win.glpane.gl_update(
        )  #k needed? (e.g. for selection change? not sure.)
        self.mt_update()
        return
コード例 #52
0
def select_bad_atoms_cmd(
    widget
):  #bruce 060615 demo of simple "spelling checker" with hardcoded rules
    """Out of the selected atoms or chunks, select the atoms which have "bad spelling"."""
    from utilities.Log import orangemsg, redmsg, greenmsg
    greencmd = greenmsg("%s: " % cmdname)
    orangecmd = orangemsg(
        "%s: " % cmdname
    )  # used when bad atoms are found, even though no error occurred in the command itself
    win = env.mainwindow()
    assy = win.assy
    # 1. compile the patterns to search for. This could be done only once at init time, but it's fast so it doesn't matter.
    bad_patterns_dict, root_eltnums, other_eltnums = compile_patterns()
    # 2. Find the atoms to search from (all selected atoms, or atoms in selected chunks, are potential root atoms)
    checked_in_what = "selected atoms or chunks"
    contained = "contained"
    atoms = {}
    for m in assy.selmols:
        atoms.update(m.atoms)
    atoms.update(assy.selatoms)
    if 0:
        # do this if you don't like the feature of checking the entire model when nothing is selected.
        if not atoms:
            env.history.message(
                redmsg("%s: nothing selected to check." % cmdname))
            return
    else:
        # if nothing is selected, work on the entire model.
        if not atoms:
            checked_in_what = "model"
            contained = "contains"
            for m in assy.molecules:
                atoms.update(m.atoms)
            if not atoms:
                env.history.message(
                    redmsg("%s: model contains no atoms." % cmdname))
                return
        pass
    # 3. Do the search.
    bad_triples = []  # list of bad triples of atoms (perhaps with overlap)
    for a in atoms.itervalues():
        ae = a.element.eltnum
        if ae not in root_eltnums:
            continue
        checkbonds = []
        for b in a.bonds:
            o = b.other(a)
            oe = o.element.eltnum
            if oe in other_eltnums:
                checkbonds.append((o, oe))
        nbonds = len(checkbonds)
        if nbonds > 1:  #e we could easily optimize the following loop for fixed nbonds like 2,3,4... or code it in pyrex.
            for i in xrange(nbonds - 1):
                for j in xrange(i + 1, nbonds):
                    if (checkbonds[i][1], ae,
                            checkbonds[j][1]) in bad_patterns_dict:
                        # gotcha!
                        bad_triples.append(
                            (checkbonds[i][0], a, checkbonds[j][0]))
    if not bad_triples:
        env.history.message(greencmd +
                            "no bad patterns found in %s." % checked_in_what)
        return
    # done - deselect all, then select bad atoms if any. (Should we also deselect if we found no bad atoms, above??)
    win.glpane.gl_update()
    assy.unpickall_in_GLPane()  #bruce 060721; was unpickatoms and unpickparts
    bad_atoms = {}
    for a1, a2, a3 in bad_triples:
        bad_atoms[a1.key] = a1
        bad_atoms[a2.key] = a2
        bad_atoms[a3.key] = a3
    reallypicked = 0
    for a in bad_atoms.itervalues():
        a.pick()
        reallypicked += (not not a.picked)  # check for selection filter effect
    env.history.message(orangecmd + fix_plurals(
                        "%s %s %d bad atom(s), in %d bad pattern(s)." % \
                        (checked_in_what, contained, len(bad_atoms), len(bad_triples)) ))
    if reallypicked < len(bad_atoms):
        env.history.message( orangemsg("Warning: ") + fix_plurals(
                             "%d bad atom(s) were/was not selected due to the selection filter." % \
                             (len(bad_atoms) - reallypicked) ))
    win.mt.update_select_mode()
    return
コード例 #53
0
    def modifySeparate(self, new_old_callback=None):
        """
        For each Chunk (named N) containing any selected atoms,
        move the selected atoms out of N (but without breaking any bonds)
        into a new Chunk which we name N-frag. If N is now empty, remove it.
        
        @param new_old_callback: If provided, then each time we create a new
            (and nonempty) fragment N-frag, call new_old_callback with the
            2 args N-frag and N (that is, with the new and old molecules).
        @type  new_old_callback: function
        
        @warning: we pass the old mol N to that callback, even if it has no 
                  atoms and we deleted it from this assembly.
        """
        # bruce 040929 wrote or revised docstring, added new_old_callback feature
        # for use from Extrude.
        # Note that this is called both from a tool button and for internal uses.
        # bruce 041222 removed side effect on selection mode, after discussion
        # with Mark and Josh. Also added some status messages.
        # Questions: is it good to refrain from merging all moved atoms into one
        # new mol? If not, then if N becomes empty, should we rename N-frag to N?

        cmd = greenmsg("Separate: ")

        if not self.selatoms:  # optimization, and different status msg
            msg = redmsg("No atoms selected")
            env.history.message(cmd + msg)
            return
        if 1:
            #bruce 060313 mitigate bug 1627, or "fix it by doing something we'd rather not always have to do" --
            # create (if necessary) a new toplevel group right now (before addmol does), avoiding a traceback
            # when all atoms in a clipboard item part consisting of a single chunk are selected for this op,
            # and the old part.topnode (that chunk) disappears from loss of atoms before we add the newly made chunk
            # containing those same atoms.
            # The only things wrong with this fix are:
            # - It's inefficient (so is the main algorithm, and it'd be easy to rewrite it to be faster, as explained below).
            # - The user ends up with a new Group even if one would theoretically not have been needed.
            #   But that's better than a traceback and disabled session, so for A7 this fix is fine.
            # - The same problem might arise in other situations (though I don't know of any), so ideally we'd
            #   have a more general fix.
            # - It's nonmodular for this function to have to know anything about Parts.
            ##e btw, a simpler way to do part of the following is "part = self". should revise this when time to test it. [bruce 060329]
            someatom = self.selatoms.values(
            )[0]  # if atoms in multiple parts could be selected, we'd need this for all their mols
            part = someatom.molecule.part
            part.ensure_toplevel_group()
            # this is all a kluge; a better way would be to rewrite the main algorithm to find the mols
            # with selected atoms, only make numol for those, and add it (addmol) before transferring all the atoms to it.
            pass
        numolist = []
        for mol in self.molecules[:]:  # new mols are added during the loop!
            numol = Chunk(self.assy, gensym(mol.name + "-frag",
                                            self.assy))  # (in modifySeparate)
            for a in mol.atoms.values():
                if a.picked:
                    # leave the moved atoms picked, so still visible
                    a.hopmol(numol)
            if numol.atoms:
                numol.setDisplayStyle(
                    mol.display)  # Fixed bug 391.  Mark 050710
                numol.setcolor(mol.color, repaint_in_MT=False)
                #bruce 070425, fix Extrude bug 2331 (also good for Separate in general), "nice to have" for A9
                self.addmol(
                    numol
                )  ###e move it to just after the one it was made from? or, end of same group??
                numolist += [numol]
                if new_old_callback:
                    new_old_callback(numol, mol)  # new feature 040929
        msg = fix_plurals("Created %d new chunk(s)" % len(numolist))
        env.history.message(cmd + msg)
        self.w.win_update()  #e do this in callers instead?
コード例 #54
0
            if _DEBUG_PRINT_BOND_DIRECTION_ERRORS:
                print "bond direction error for %r: %s" % (atom, error_data)
                print
            _atom_set_dna_updater_error( atom, error_data)
            atom.molecule.changeapp(0) #k probably not needed
            new_error_atoms[atom.key] = atom
            _global_direct_error_atoms[atom.key] = atom
        else:
            _atom_clear_dna_updater_error( atom)
            _global_direct_error_atoms.pop(atom.key, None)
        continue

    if new_error_atoms: #e if we print more below, this might be only interesting for debugging, not sure
        # maybe: move later since we will be expanding this set; or report number of base pairs
        msg = "Warning: dna updater noticed %d pseudoatom(s) with bond direction errors" % len(new_error_atoms)
        msg = fix_plurals(msg)
        env.history.orangemsg(msg)

    global _all_error_atoms_after_propogation
    old_all_error_atoms_after_propogation = _all_error_atoms_after_propogation

    new_all_error_atoms_after_propogation = {}

    for atom in _global_direct_error_atoms.itervalues():
        for atom2 in _same_base_pair_atoms(atom):
            new_all_error_atoms_after_propogation[atom2.key] = atom2

    _all_error_atoms_after_propogation = new_all_error_atoms_after_propogation

    for atom in old_all_error_atoms_after_propogation.itervalues():
        if atom.key not in new_all_error_atoms_after_propogation:
コード例 #55
0
ファイル: ops_pam.py プロジェクト: foulowl/nanoengineer
    def _convert_selection_to_pam_model(self,
                                        which_pam,
                                        commandname = "",
                                        make_ghost_bases = True, # only implemented for PAM3, so far
                                        ## remove_ghost_bases_from_PAM3 = True
                                       ): #bruce 080413
        """
        Convert the selected atoms (including atoms into selected chunks),
        which don't have errors (in the atoms or their dnaladders), into
        the specified pam model (some, none, or all might already be
        in that pam model), along with all atoms in the same basepairs,
        but only for kinds of ladders for which conversion is yet
        implemented. Print summaries to history.

        This is a user operation, so the dna updater has run
        and knows which atoms are errors, knows ladders of atoms, etc.
        We take advantage of that to simplify the implementation.
        """
        if not commandname:
            commandname = "Convert to %s" % which_pam # kluge, doesn't matter yet

        # find all selected atoms (including those in selected chunks)
        atoms = dict(self.selatoms)
        for chunk in self.selmols:
            atoms.update(chunk.atoms)

        if not atoms:
            env.history.message( greenmsg(commandname + ": ") + redmsg("Nothing selected.") )
            return

        # expand them to cover whole basepairs -- use ladders to help?
        # (the atoms with errors are not in valid ladders, so that
        #  is also an easy way to exclude those)

        num_atoms_with_good_ladders = 0
        ladders = {}
        ghost_bases = {} # initially only holds PAM5 ghost bases

        for atom in atoms.itervalues():
            try:
                ladder = atom.molecule.ladder
            except AttributeError: # for .ladder
                continue # not the right kind of atom, etc
            if not ladder or not ladder.valid:
                continue
            if ladder.error:
                continue
            if not ladder.strand_rails: # bare axis
                continue
            num_atoms_with_good_ladders += 1
            ladders[ladder] = ladder
            # note: if atom is Pl, its Ss neighbors are treated specially
            # lower down in this method
            if atom.ghost and atom.element.pam == MODEL_PAM5:
                ghost_bases[atom.key] = atom
            continue

        orig_len_atoms = len(atoms) # for history messages, in case we add some

        # now iterate on the ladders, scanning their atoms to find the ones
        # in atoms, noting every touched baseindex

        # BUG: this does not notice Pls which are in atoms without either neighbor Ss being in atoms.
        # Future: fix by noticing them above in atom loop; see comment there.

        atoms_to_convert = {}
        ladders_to_inval = {}

        number_of_basepairs_to_convert = 0
        number_of_unpaired_bases_to_convert = 0

        ladders_needing_ghost_bases = {} # maps ladder to (ladder, list of indices) #bruce 080528

        for ladder in ladders:
            # TODO: if ladder can't convert (nim for that kind of ladder),
            # say so as a summary message, and skip it. (But do we know how
            # many atoms in our dict it had? if possible, say that too.)

            # TODO: if ladder doesn't need to convert (already in desired model),
            # skip it.

            length = len(ladder)
            index_set = {} # base indexes in ladder of basepairs which touch our dict of atoms
            rails = ladder.all_rails()
            if len(ladder.strand_rails) not in (1, 2):
                continue
            for rail in rails:
                for ind in range(length):
                    atom = rail.baseatoms[ind]
                    if atom.key in atoms:
                        # convert this base pair
                        index_set[ind] = ind
                        pass
                    continue
                continue
            # conceivable that for some ladders we never hit them;
            # for now, warn but skip them in that case
            if not index_set:
                print "unexpected: scanned %r but found nothing to convert (only Pls selected??)" % ladder # env.history?
            else:
                if len(ladder.strand_rails) == 2:
                    number_of_basepairs_to_convert += len(index_set)
                else:
                    number_of_unpaired_bases_to_convert += len(index_set)
                        # note: we do this even if the conversion will fail
                        # (as it does initially for single strand domains),
                        # since the summary error message from that is useful.
                    if make_ghost_bases and ladder.can_make_ghost_bases():
                        # initially, this test rules out free floating single strands;
                        # later we might be able to do this for them, which is why
                        # we do the test using that method rather than directly.
                        ladders_needing_ghost_bases[ladder] = (ladder, index_set.values())
                # see related code in _cmd_convert_to_pam method
                # in DnaLadder_pam_conversion.py
                ladders_to_inval[ladder] = ladder
                if 0 in index_set or (length - 1) in index_set:
                    for ladder2 in ladder.strand_neighbor_ladders():
                        # might contain Nones or duplicate entries
                        if ladder2 is not None:
                            ladders_to_inval[ladder2] = ladder2 # overkill if only one ind above was found
                for rail in rails:
                    baseatoms = rail.baseatoms
                    for ind in index_set:
                        atom = baseatoms[ind]
                        atoms_to_convert[atom.key] = atom # note: we also add ghost base atoms, below
                pass
            continue # next ladder

        if not atoms_to_convert:
            assert not number_of_basepairs_to_convert
            assert not number_of_unpaired_bases_to_convert
            assert not ladders_needing_ghost_bases
            if num_atoms_with_good_ladders < orig_len_atoms:
                # warn if we're skipping some atoms [similar code occurs twice in this method]
                msg = "%d atom(s) skipped, since not in valid, error-free DnaLadders"
                env.history.message( greenmsg(commandname + ": ") + orangemsg("Warning: " + fix_plurals(msg)))
            env.history.message( greenmsg(commandname + ": ") + redmsg("Nothing found to convert.") )
            return

        # print a message about what we found to convert
        what1 = what2 = ""
        if number_of_basepairs_to_convert:
            what1 = fix_plurals( "%d basepair(s)" % number_of_basepairs_to_convert )
        if number_of_unpaired_bases_to_convert:
            # doesn't distinguish sticky ends from free-floating single strands (fix?)
            what2 = fix_plurals( "%d unpaired base(s)" % number_of_unpaired_bases_to_convert )
        if what1 and what2:
            what = what1 + " and " + what2
        else:
            what = what1 + what2

        env.history.message( greenmsg(commandname + ": ") + "Will convert %s ..." % what )

        # warn if we're skipping some atoms [similar code occurs twice in this method]
        if num_atoms_with_good_ladders < orig_len_atoms:
            msg = "%d atom(s) skipped, since not in valid, error-free DnaLadders"
            env.history.message( orangemsg("Warning: " + fix_plurals(msg)))

        print "%s will convert %d atoms, touching %d ladders" % \
              ( commandname, len(atoms_to_convert), len(ladders_to_inval) )

        # make ghost bases as needed for this conversion (if enabled -- not by default since not yet working ####)
        # (this must not delete any baseatoms in atoms, or run the dna updater
        #  or otherwise put atoms into different ladders, but it can make new
        #  atoms in new chunks, as it does)

        if debug_pref_enable_pam_convert_sticky_ends():
            for ladder, index_list in ladders_needing_ghost_bases.itervalues():
                baseatoms = ladder.make_ghost_bases(index_list) # note: index_list is not sorted; that's ok
                    # note: this makes them in a separate chunk, and returns them
                    # as an atom list, but doesn't add the new chunk to the ladder.
                    # the next dna updater run will fix that (making a new ladder
                    # that includes all atoms in baseatoms and the old ladder).
                for ind in index_list:
                    atom = baseatoms[ind]
                    atoms_to_convert[atom.key] = atom

        # cause the dna updater (which would normally run after we return,
        #  but is also explicitly run below) to do the rest of the conversion
        # (and report errors for whatever it can't convert)

        for ladder in ladders_to_inval:
            ladder._dna_updater_rescan_all_atoms()

        for atom in atoms_to_convert:
            _f_baseatom_wants_pam[atom] = which_pam

        # run the dna updater explicitly

        print "about to run dna updater for", commandname
        self.assy.update_parts() # not a part method
            # (note: this catches dna updater exceptions and turns them into redmsgs.)
        print "done with dna updater for", commandname

        if debug_pref_remove_ghost_bases_from_pam3():
            # note: in commented out calling code above, this was a flag
            # option, remove_ghost_bases_from_PAM3;
            # that will be revived if we have a separate command for this.
            #
            # actually we only remove the ones we noticed as PAM5 above,
            # and succeeded in converting to PAM3.
            good = bad = 0
            for atom in ghost_bases.values(): # must not be itervalues
                if atom.element.pam == MODEL_PAM3:
                    good += 1
                    for n in atom.neighbors():
                        if n.is_ghost():
                            ghost_bases[n.key] = n
                else:
                    bad += 1
                    del ghost_bases[atom.key]
                continue
            if good:
                print "removing %d ghost base(s) we converted to PAM3" % good
            if bad:
                print "leaving %d ghost base(s) we didn't convert to PAM3" % bad
            if not bool(good) == bool(ghost_bases): # should never happen
                print "bug: bool(good) != bool(ghost_bases), for", good, ghost_bases
            del good, bad
            if ghost_bases:
                for atom in ghost_bases.itervalues():
                    atom.kill()
                    # todo: probably should use prekill code to avoid
                    # intermediate bondpoint creation, even though there
                    # are not usually a lot of atoms involved at once
                    continue
                print "about to run dna updater 2nd time for", commandname
                self.assy.update_parts()
                print "done with dna updater 2nd time for", commandname
            pass

        env.history.message( greenmsg( commandname + ": " + "Done." ))

        self.assy.w.win_update()

        return
コード例 #56
0
ファイル: build_utils.py プロジェクト: ematvey/NanoEngineer-1
    def attach_to( self, singlet, autobond = True, autobond_msg = True): # in AtomTypeDepositionTool
        # [bruce 050831 added autobond option; 050901 added autobond_msg]
        """
        [public method]
        Deposit a new atom of self.atomtype onto the given singlet,
        and (if autobond is true) make other bonds (to other near-enough atoms with singlets)
        as appropriate. (But never more than one bond per other real atom.)

        @return: a 2-tuple consisting of either the new atom and a description
                 of it, or None and the reason we made nothing.
        
        ###@@@ should worry about bond direction! at least as a filter!
           If autobond_msg is true, mention the autobonding done or not done (depending on autobond option),
        in the returned message, if any atoms were near enough for autobonding to be done.
        This option is independent from the autobond option.
        [As of 050901 not all combos of these options have been tested. ###@@@]
        """
        atype = self.atomtype
        if not atype.numbonds:
            whynot = "%s makes no bonds; can't attach one to an open bond" % atype.fullname_for_msg()
            return None, whynot
        if not atype.can_bond_to(singlet.singlet_neighbor(), singlet):
            #bruce 080502 new feature
            whynot = "%s bond to %r is not allowed" % (atype.fullname_for_msg(), singlet.singlet_neighbor())
                # todo: return whynot from same routine
            return None, whynot
        spot = self.findSpot(singlet)
        pl = [(singlet, spot)] # will grow to a list of pairs (s, its spot)
            # bruce change 041215: always include this one in the list
            # (probably no effect, but gives later code less to worry about;
            #  before this there was no guarantee singlet was in the list
            #  (tho it probably always was), or even that the list was nonempty,
            #  without analyzing the subrs in more detail than I'd like!)

        if autobond or autobond_msg: #bruce 050831 added this condition; 050901 added autobond_msg
            # extend pl to make additional bonds, by adding more (singlet, spot) pairs
            rl = [singlet.singlet_neighbor()]
                # list of real neighbors of singlets in pl [for bug 232 fix]
            ## mol = singlet.molecule
            cr = atype.rcovalent
            # bruce 041215: might as well fix the bug about searching for open bonds
            # in other mols too, since it's easy; search in this one first, and stop
            # when you find enough atoms to bond to.
            searchmols = list(singlet.molecule.part.molecules) #bruce 050510 revised this
            searchmols.remove(singlet.molecule)
            searchmols.insert(0, singlet.molecule)
            # max number of real bonds we can make (now this can be more than 4)
            maxpl = atype.numbonds
            
            for mol in searchmols:
              for s in mol.nearSinglets(spot, cr * 1.9):
                  #bruce 041216 changed 1.5 to 1.9 above (it's a heuristic);
                  # see email discussion (ninad, bruce, josh)
                #bruce 041203 quick fix for bug 232:
                # don't include two singlets on the same real atom!
                # (It doesn't matter which one we pick, in terms of which atom we'll
                #  bond to, but it might affect the computation in the bonding
                #  method of where to put the new atom, so ideally we'd do something
                #  more principled than just using the findSpot output from the first
                #  singlet in the list for a given real atom -- e.g. maybe we should
                #  average the spots computed for all singlets of the same real atom.
                #  But this is good enough for now.)
                #bruce 050510 adds: worse, the singlets are in an arb position... really we should just ask if
                # it makes sense to bond to each nearby *atom*, for the ones too near to comfortably *not* be bonded to. ###@@@
                ###@@@ bruce 050221: bug 372: sometimes s is not a singlet. how can this be??
                # guess: mol.singlets is not always invalidated when it should be. But even that theory
                # doesn't seem to fully explain the bug report... so let's find out a bit more, at least:
                try:
                    real = s.singlet_neighbor() 
                except:
                    print_compact_traceback("bug 372 caught red-handed: ")
                    print "bug 372-related data: mol = %r, mol.singlets = %r" % (mol, mol.singlets)
                    continue
                if real not in rl and atype.can_bond_to(real, s, auto = True):
                    # checking can_bond_to is bruce 080502 new feature
                    pl += [(s, self.findSpot(s))]
                    rl += [real]
              # after we're done with each mol (but not in the middle of any mol),
              # stop if we have as many open bonds as we can use
              if len(pl) >= maxpl:
                break
            del mol, s, real
        
        n = min(atype.numbonds, len(pl)) # number of real bonds to make (if this was computed above); always >= 1
        pl = pl[0:n] # discard the extra pairs (old code did this too, implicitly)
        if autobond_msg and not autobond:
            pl = pl[0:1] # don't actually make the bonds we only wanted to tell the user we *might* have made
        # now pl tells which bonds to actually make, and (if autobond_msg) n tells how many we might have made.
        
        # bruce 041215 change: for n > 4, old code gave up now;
        # new code makes all n bonds for any n, tho it won't add singlets
        # for n > 4. (Both old and new code don't know how to add enough
        # singlets for n >= 3 and numbonds > 4. They might add some, tho.)
        # Note: _new_bonded_n uses len(pl) as its n. As of 050901 this might differ from the variable n.
        atm = self._new_bonded_n( pl)
        atm.make_enough_bondpoints() # (tries its best, but doesn't always make enough)
        desc = "%r (in %r)" % (atm, atm.molecule.name)
        #e what if caller renames atm.molecule??
        if n > 1: #e really: if n > (number of singlets clicked on at once)
            if autobond:
                msg = " (%d bond(s) made)" % n
            else:
                msg = " (%d bond(s) NOT made, since autobond is off)" % (n-1) #bruce 050901 new feature
            from platform_dependent.PlatformDependent import fix_plurals
            msg = fix_plurals(msg)
            desc += msg
        return atm, desc