def widget_msg(self, msg, options): #e improved timestamp? #e use html for color etc? [some callers put this directly in the msg, for now] _quote_html = options.pop( 'quote_html', False ) #bruce 060126 new feature, improving on message_no_html interface ##k if _quote_html: msg = quote_html(msg) _color = options.pop('color', None) if _color: #bruce 060126 new feature; for now only permits 4 fixed color name strings; # should someday permit any color name (in any format) or object (of any kind) #e funcs = { 'green': greenmsg, 'orange': orangemsg, 'red': redmsg, 'gray': graymsg } func = funcs[ _color] # any colorname not in this dict is an exception (ok for now) msg = func(msg) _compact_stack = options.pop('compact_stack', "") #bruce 060720 if _compact_stack: msg += graymsg("; history.message() call stack: %s" % quote_html(_compact_stack)) # any unrecognized options are warned about below self._print_msg(msg) if options: msg2 = "fyi: bug: widget_msg got unsupported options: %r" % options print msg2 # too important to only print in the history file -- # could indicate that not enough info is being saved there self._print_msg(msg2) return
def addLine(self, line): columns = line.split() if (len(columns) == 3 and columns[0] == 'Step' and columns[1] == 'Time' and columns[2] == 'Lambda'): self._resetColumns() self.state = 1 return if (self.state == 1 and len(columns) == 3): self.step = columns[0] self.state = 2 return if (self.state == 2 and len(columns) == 0): self.state = 3 return if (self.state == 3 and len(columns) == 2 and columns[0] == 'Energies'): self.state = 4 return if (self.state == 4): if (len(columns) > 0): self.state = 5 self.column_headers = self._extractColumns(line.rstrip()) return else: self._emitColumns() self.state = 0 return if (self.state == 5): if (len(columns) == len(self.column_headers)): self._addColumns(self.column_headers, columns) self.state = 4 return else: self.state = 0 # this never happens return # Stepsize too small, or no change in energy. # Converged to machine precision, # but not to the requested precision Fmax < 0.006022 # # Polak-Ribiere Conjugate Gradients did not converge to Fmax < 0.006022 in 100001 steps. if (line.find("converge") >= 0 and line.find("Fmax") >= 0): env.history.message("Energy (Bond, Strut, Nonbonded): (%f, %f, %f) zJ" % (self.getBondEnergy(), self.getHarmonicEnergy(), self.getNonbondedEnergy())) env.history.message("Total Energy %f zJ" % self.getTotalEnergy()) if (line.find("machine") >= 0 or line.find("did not") >= 0): env.history.message(orangemsg(quote_html(line.rstrip()))) else: env.history.message(quote_html(line.rstrip())) return
def mark_atom_by_name(assy, name): """ If you can find an atom of the given name, mark it visibly. """ atom = find_atom_by_name(assy, name) if atom: env.history.message(quote_html("found atom %r: %r, in part %r" % (name, atom, atom.molecule.part))) mark_one_atom(atom) else: env.history.message(quote_html("can't find atom %r (in part %r)" % (name, assy.part,))) return
def blaberr(): text = str(simProcess.readStderr( )) # str since it's QString (i hope it can't be unicode) print "stderr:", text env.history.message( redmsg("%s stderr: " % self.plugin_name + quote_html(text)))
def debug_make_BorrowerChunk_raw(do_addmol=True): win = env.mainwindow() atomset = win.assy.selatoms if not atomset: env.history.message( redmsg( "Need selected atoms to make a BorrowerChunk (for debugging only)" )) else: atomset = dict( atomset ) # copy it, since we shouldn't really add singlets to assy.selatoms... for atom in atomset.values( ): # not itervalues, we're changing it in the loop! # BTW Python is nicer about this than I expected: # exceptions.RuntimeError: dictionary changed size during iteration for bp in atom.singNeighbors( ): # likely bugs if these are not added into the set! atomset[bp.key] = bp assy = atom.molecule.assy # these are all the same, and we do this at least once chunk = BorrowerChunk(assy, atomset) if do_addmol: win.assy.addmol(chunk) import __main__ __main__._bc = chunk env.history.message( orangemsg("__main__._bc = %s (for debugging only)" % quote_html(safe_repr(chunk)))) win.win_update() #k is this done by caller? return
def accept(self): 'Slot for the OK button' try: self.create_comment() self.done_history_msg() self.comment = None except Exception, e: print_compact_traceback("Bug: exception in CommentProp.accept: ") #bruce Qt4 070502 env.history.message(cmd + redmsg("Bug: " + quote_html(" - ".join(map(str, e.args))))) #bruce Qt4 070502 bugfixes: use quote_html, say it's a bug (could say "internal error" if desired) self.remove_comment()
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
def accept(self): """ Slot for the OK button """ try: self._create_comment() self._done_history_msg() self.comment = None except Exception, e: print_compact_traceback("Bug: exception in CommentProp.accept: ") #bruce Qt4 070502 env.history.message(cmd + redmsg("Bug: " + quote_html(" - ".join(map(str, e.args))))) #bruce Qt4 070502 bugfixes: use quote_html, say it's a bug (could say "internal error" if desired) self._remove_comment()
def _getToolTipInfo(self): # VirtualSiteJig # untested, since some ###BUG prevents this from being shown """ Return a string for display in self's Dynamic Tool tip. (Appears when user highlights this jig's drawing, but unrelated to tooltip on our site_atom itself.) [overridden from class Jig] """ self._update_props() # doesn't yet matter in this method msg = "%s: %s" % (self.sym, quote_html(self.name)) + \ "<br><font color=\"#0000FF\">" \ "%s</font>" % (self._props,) return msg
def widget_msg(self, msg, options): #e improved timestamp? #e use html for color etc? [some callers put this directly in the msg, for now] _quote_html = options.pop('quote_html', False) #bruce 060126 new feature, improving on message_no_html interface ##k if _quote_html: msg = quote_html(msg) _color = options.pop('color', None) if _color: #bruce 060126 new feature; for now only permits 4 fixed color name strings; # should someday permit any color name (in any format) or object (of any kind) #e funcs = {'green':greenmsg, 'orange':orangemsg, 'red':redmsg, 'gray':graymsg} func = funcs[_color] # any colorname not in this dict is an exception (ok for now) msg = func(msg) _compact_stack = options.pop('compact_stack', "") #bruce 060720 if _compact_stack: msg += graymsg("; history.message() call stack: %s" % quote_html(_compact_stack)) # any unrecognized options are warned about below self._print_msg(msg) if options: msg2 = "fyi: bug: widget_msg got unsupported options: %r" % options print msg2 # too important to only print in the history file -- # could indicate that not enough info is being saved there self._print_msg(msg2) return
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
def fatal(self, errortext, errorcode = 1): """ Our submethods call this to report a fatal setup/use error; it prints errortext appropriately and sets self.errorcode and self.errortext. """ if not errorcode: print "bug: fatal errorcode must be a boolean-true value, not %r" % (errorcode,) errorcode = 1 if self.errorcode: print "plugin %r bug: self.errorcode was already set before fatal was called" % (self.plugin_name,) if not self.errorcode or not self.errortext: self.errortext = errortext # permanent record for use by callers self.errorcode = errorcode msg = "plugin %r fatal error: %s" % (self.plugin_name, errortext,) print msg env.history.message(redmsg(quote_html(msg))) # it might be too early for this to be seen return errorcode
def debug_run_command(command, source="user debug input" ): #bruce 040913-16 in GLPane.py; modified 040928 """ Execute a python command, supplied by the user via some sort of debugging interface (named by source), in debug.py's globals. Return 1 for ok (incl empty command), 0 for any error. Caller should not print exception diagnostics -- this function does that (and does not reraise the exception). """ #e someday we might record time, history, etc command = "" + command # i.e. assert it's a string #k what's a better way to do the following? while command and command[0] == '\n': command = command[1:] while command and command[-1] == '\n': command = command[:-1] if not command: print "empty command (from %s), nothing executed" % (source, ) return 1 if '\n' not in command: msg = "will execute (from %s): %s" % (source, command) else: nlines = command.count('\n') + 1 msg = "will execute (from %s; %d lines):\n%s" % (source, nlines, command) print msg try: # include in history file, so one can search old history files for useful things to execute [bruce 060409] from utilities.Log import _graymsg, quote_html env.history.message(_graymsg(quote_html(msg))) except: print_compact_traceback("exception in printing that to history: ") command = command + '\n' #k probably not needed try: ## exec command in globals() legally_exec_command_in_globals(command, globals()) except: print_compact_traceback("exception from that: ") return 0 else: print "did it!" return 1 pass
def _getToolTipInfo(self): # VirtualBondJig """ Return a string for display in self's Dynamic Tool tip. [overridden from class Jig] """ self._update_props() ks = self._ks # N/m r0 = self._r0 # pm length = self._getLength() # pm force = ks * (length - r0) # pN msg = "%s: %s" % (self.sym, quote_html(self.name)) + \ "<br><font color=\"#0000FF\">" \ "ks = %f N/m<br>" \ "r0 = %f pm<br>" \ "len = %f pm<br>" \ "len/r0 = %f<br>" \ "force = %f pN</font>" % (ks, r0, length, length/r0, force) return msg
def debug_run_command(command, source = "user debug input"): #bruce 040913-16 in GLPane.py; modified 040928 """ Execute a python command, supplied by the user via some sort of debugging interface (named by source), in debug.py's globals. Return 1 for ok (incl empty command), 0 for any error. Caller should not print exception diagnostics -- this function does that (and does not reraise the exception). """ #e someday we might record time, history, etc command = "" + command # i.e. assert it's a string #k what's a better way to do the following? while command and command[0] == '\n': command = command[1:] while command and command[-1] == '\n': command = command[:-1] if not command: print "empty command (from %s), nothing executed" % (source,) return 1 if '\n' not in command: msg = "will execute (from %s): %s" % (source, command) else: nlines = command.count('\n')+1 msg = "will execute (from %s; %d lines):\n%s" % (source, nlines, command) print msg try: # include in history file, so one can search old history files for useful things to execute [bruce 060409] from utilities.Log import _graymsg, quote_html env.history.message( _graymsg( quote_html( msg))) except: print_compact_traceback("exception in printing that to history: ") command = command + '\n' #k probably not needed try: ## exec command in globals() legally_exec_command_in_globals( command, globals() ) except: print_compact_traceback("exception from that: ") return 0 else: print "did it!" return 1 pass
def debug_make_BorrowerChunk_raw(do_addmol = True): win = env.mainwindow() atomset = win.assy.selatoms if not atomset: env.history.message(redmsg("Need selected atoms to make a BorrowerChunk (for debugging only)")) else: atomset = dict(atomset) # copy it, since we shouldn't really add singlets to assy.selatoms... for atom in atomset.values(): # not itervalues, we're changing it in the loop! # BTW Python is nicer about this than I expected: # exceptions.RuntimeError: dictionary changed size during iteration for bp in atom.singNeighbors(): # likely bugs if these are not added into the set! atomset[bp.key] = bp assy = atom.molecule.assy # these are all the same, and we do this at least once chunk = BorrowerChunk(assy, atomset) if do_addmol: win.assy.addmol(chunk) import __main__ __main__._bc = chunk env.history.message(orangemsg("__main__._bc = %s (for debugging only)" % quote_html(safe_repr(chunk)))) win.win_update() #k is this done by caller? return
def _depositLibraryPart(self, newPart, hotspotAtom, atom_or_pos): # probably by Huaicai; revised by bruce 051227, 060627, 070501 """ This method serves as an overloaded method, <atom_or_pos> is the Singlet atom or the empty position that the new part <newPart> [which is an assy, at least sometimes] will be attached to or placed at. [If <atom_or_pos> is a singlet, <hotspotAtom> should be an atom in some chunk in <newPart>.] Currently, it doesn't consider group or jigs in the <newPart>. Not so sure if my attempt to copy a part into another assembly is all right. [It wasn't, so bruce 051227 revised it.] Copies all molecules in the <newPart>, change their assy attribute to current assembly, move them into <pos>. [bruce 051227 new feature:] return a list of new nodes created, and a message for history (currently almost a stub). [not sure if subrs ever print history messages... if they do we'd want to return those instead.] """ attach2Bond = False stuff = [] # list of deposited nodes [bruce 051227 new feature] if isinstance(atom_or_pos, Atom): attch2Singlet = atom_or_pos if hotspotAtom and hotspotAtom.is_singlet() and \ attch2Singlet .is_singlet(): newMol = hotspotAtom.molecule.copy_single_chunk(None) # [this can break interchunk bonds, # thus it still has bug 2028] newMol.setAssy(self.o.assy) hs = newMol.hotspot ha = hs.singlet_neighbor() # hotspot neighbor atom attch2Atom = attch2Singlet.singlet_neighbor() # attach to atom rotCenter = newMol.center rotOffset = Q(ha.posn()-hs.posn(), attch2Singlet.posn()-attch2Atom.posn()) newMol.rot(rotOffset) moveOffset = attch2Singlet.posn() - hs.posn() newMol.move(moveOffset) self.graphicsMode._createBond(hs, ha, attch2Singlet, attch2Atom) self.o.assy.addmol(newMol) stuff.append(newMol) #e if there are other chunks in <newPart>, #they are apparently copied below. [bruce 060627 comment] else: ## something is wrong, do nothing return stuff, "internal error" attach2Bond = True else: placedPos = atom_or_pos if hotspotAtom: hotspotAtomPos = hotspotAtom.posn() moveOffset = placedPos - hotspotAtomPos else: if newPart.molecules: moveOffset = placedPos - newPart.molecules[0].center #e not #the best choice of center [bruce 060627 comment] if attach2Bond: # Connect part to a bondpoint of an existing chunk for m in newPart.molecules: if not m is hotspotAtom.molecule: newMol = m.copy_single_chunk(None) # [this can break interchunk bonds, # thus it still has bug 2028] newMol.setAssy(self.o.assy) ## Get each of all other chunks' center movement for the ## rotation around 'rotCenter' coff = rotOffset.rot(newMol.center - rotCenter) coff = rotCenter - newMol.center + coff # The order of the following 2 statements doesn't matter newMol.rot(rotOffset) newMol.move(moveOffset + coff) self.o.assy.addmol(newMol) stuff.append(newMol) else: # Behaves like dropping a part anywhere you specify, independent #of existing chunks. # copy all nodes in newPart (except those in clipboard items), # regardless of node classes; # put it in a new Group if more than one thing [bruce 070501] # [TODO: this should be done in the cases above, too, but that's # not yet implemented, # and requires adding rot or pivot to the Node API and revising # the rot-calling code above, # and also reviewing the definition of the "hotspot of a Part" and # maybe of a "depositable clipboard item".] assert newPart.tree.is_group() nodes = list(newPart.tree.members) # might be [] assy = self.o.assy newnodes = copied_nodes_for_DND(nodes, autogroup_at_top = True, assy = assy) # Note: that calls name_autogrouped_nodes_for_clipboard # internally, if it forms a Group, # but we ignore that and rename the new node differently below, # whether or not it was autogrouped. We could just as well do # the autogrouping ourselves... # Note [bruce 070525]: it's better to call copied_nodes_for_DND # here than copy_nodes_in_order, even if we didn't need to # autogroup. One reason is that if some node is not copied, # that's not necessarily an error, since we don't care about 1-1 # orig-copy correspondence here. if not newnodes: if newnodes is None: print "bug: newnodes should not be None; nodes was %r (saved in debug._bugnodes)" % (nodes,) # TODO: This might be possible, for arbitrary partlib # contents, just not for legitimate ones... # but partlib will probably be (or is) user-expandable, #so we should turn this into history message, # not a bug print. But I'm not positive it's possible #w/o a bug, so review first. ###FIX [bruce 070501 comment] import utilities.debug as debug debug._bugnodes = nodes newnodes = [] msg = redmsg( "error: nothing to deposit in [%s]" % quote_html(str(newPart.name)) ) return [], msg assert len(newnodes) == 1 # due to autogroup_at_top = True # but the remaining code works fine regardless of len(newnodes), #in case we make autogroup a preference for newnode in newnodes: # Rename newnode based on the partlib name and a unique number. # It seems best to let the partlib mmp file contents (not just # filename) # control the name used here, so use newPart.tree.name rather # than just newPart.name. # (newPart.name is a complete file pathname; newPart.tree.name #is usually its basename w/o extension.) basename = str(newPart.tree.name) if basename == 'Untitled': # kluge, for the sake of 3 current partlib files, and files #saved only once by users (due to NE1 bug in save) dirjunk, base = os.path.split(newPart.name) basename, extjunk = os.path.splitext(base) from utilities.constants import gensym newnode.name = gensym( basename, assy) # name library part #bruce 080407 basename + " " --> basename, and pass assy # (per Mark NFR desire) #based on basename recorded in its mmp file's top node newnode.move(moveOffset) #k not sure this method is correctly #implemented for measurement jigs, named views assy.addnode(newnode) stuff.append(newnode) ## #bruce 060627 new code: fix bug 2028 (non-hotspot case only) ## about interchunk bonds being broken ## nodes = newPart.molecules ## newnodes = copied_nodes_for_DND(nodes) ## if newnodes is None: ## print "bug: newnodes should not be None; nodes was %r (saved in debug._bugnodes)" % (nodes,) ## debug._bugnodes = nodes ## newnodes = [] # kluge ## for newMol in newnodes: ## # some of the following probably only work for Chunks, ## # though coding them for other nodes would not be hard ## newMol.setAssy(self.o.assy) ## newMol.move(moveOffset) ## self.o.assy.addmol(newMol) ## stuff.append(newMol) ## # pre-060627 old code, breaks interchunk bonds since it copies ## #chunks one at a time (bug 2028) ## for m in nodes: ## newMol = m.copy(None) # later: renamed Chunk method to copy_single_chunk -- not sure if this was only called on chunks ## newMol.setAssy(self.o.assy) #bruce 051227 revised this ## ## newMol.move(moveOffset) ## ## self.o.assy.addmol(newMol) ## stuff.append(newMol) ## pass self.o.assy.update_parts() #bruce 051227 see if this fixes the #atom_debug exception in checkparts msg = greenmsg("Deposited library part: ") + " [" + \ quote_html(str(newPart.name)) + "]" #ninad060924 fix bug 1164 return stuff, msg ####@@@@ should revise this message
def blaberr(): text = str(simProcess.readStderr()) # str since it's QString (i hope it can't be unicode) print "stderr:", text env.history.message(redmsg("%s stderr: " % self.plugin_name + quote_html(text)))
def make_or_remove_crossover(twoPls, make=True, cmdname=None): """ Make or Remove (according to make option) a crossover, given Pl5_recognizers for its two Pl atoms. """ # What we are doing is recognizing one local structure and replacing it with another # made from the same atoms. It'd sure be easier if I could do the manipulation in an mmp file, # save that somewhere, and read those to generate the operation! I'd have two sets of atoms, before and after, # and see how bonds and atomtypes got changed. # In this case it's not too hard to hand-code... I guess only the Pl atoms and their bonds are affected. # We probably do have to look at strand directions -- hmm, probably we should require them to exist before saying it's ok! # Or maybe better to give history error message when the command is chosen, saying you need to set them (or repair them) first... # Then we have to move the new/moved Pl atoms into a good position... # Note: Pl.ordered_bases are ordered by bond direction, to make this easier... # but if we want to patch up the directions in the end, do we need to care exactly which ones were defined? # or only "per-Pl"? hmm... it's per-Pl for now assert cmdname for pl in twoPls: if pl.ordered_bases is None: # should no longer be possible -- now checked before menu commands are offered [bruce 070604] ###BUG: this could have various causes, not only the one reported below! Somehow we need access to the # message supplied to the RecognizerError, for use here. ###REVIEW: Does that mean it should pass through compute methods (probably in a controlled way) # rather than making computed values None? # Or, should the value not be None, but a "frozen" examinable and reraisable version of the error exception?? msg = "%s: Error: bond direction is locally undefined or inconsistent around %s" % ( cmdname, pl.atom) ###UNTESTED print "should no longer be possible:", msg #bruce 070604 env.history.message(redmsg(quote_html(msg))) return Pl1, Pl2 = twoPls a, b = Pl1.ordered_bases d, c = Pl2.ordered_bases # note: we use d,c rather than c,d so that the atom arrangement is as shown in the diagram below. # Note: for either the Make or Remove operation, the geometric arrangement is initially: # # c <-- Pl2 <-- d # # a --> Pl1 --> b # # and it ends up being (where dots indicate arrowheads, to show bond direction): # # c d # . / # \ . # Pl1 Pl2 # . \ # / . # a b # # Note: Pl1 stays attached to a, and Pl2 to d. Which two opposite bonds to preserve like that # is an arbitrary choice -- as long as Make and Remove make the same choice about that, # they'll reverse each other's effects precisely (assuming the sugars were initially correct as Ss or Sj). # break the bonds we no longer want for obj1, obj2 in [(Pl1, b), (Pl2, c)]: bond = find_bond(obj1.atom, obj2.atom) bond.bust(make_bondpoints=False) # make the bonds we want and didn't already have for obj1, obj2 in [(Pl1, c), (Pl2, b)]: assert not atoms_are_bonded(obj1.atom, obj2.atom) ###e we should make bond_atoms do this assert itself, or maybe tolerate it (or does it already??) bond_atoms_faster(obj1.atom, obj2.atom, V_SINGLE) # set directions of all 4 bonds (even the preserved ones -- it's possible they were not set before, # if some but not all bonds had directions set in the part of a strand whose directions we look at.) for obj1, obj2 in [(a, Pl1), (Pl1, c), (d, Pl2), (Pl2, b)]: bond = find_bond(obj1.atom, obj2.atom) bond.set_bond_direction_from(obj1.atom, 1) # WARNING: after that bond rearrangement, don't use our Pl5_recognizers in ways that depend on Pl bonding, # since it's not well defined whether they think about the old or new bonding to give their answers. Pl_atoms = Pl1.atom, Pl2.atom del Pl1, Pl2, twoPls # transmute base sugars to Sj or Ss as appropriate if dna_updater_is_enabled(): want = Element_Ss5 #bruce 080320 bugfix else: want = make and Element_Sj5 or Element_Ss5 for obj in (a, b, c, d): obj.atom.Transmute(want) # Note: we do this after the bond making/breaking so it doesn't add singlets which mess us up. # move Pl atoms into better positions # (someday, consider using local minimize; for now, just place them directly between their new neighbor atoms, # hopefully we leave them selected so user can easily do their own local minimize.) for pl in Pl_atoms: pl.setposn( average_value(map(lambda neighbor: neighbor.posn(), pl.neighbors()))) env.history.message( greenmsg(cmdname + ": ") + quote_html("(%s - %s)" % tuple(Pl_atoms))) #e need assy.changed()? evidently not. return # from make_or_remove_crossover
def apply_btype_to_bond(btype, bond, allow_remake_bondpoints = True, suppress_history_message = False): #bruce 060703 added allow_remake_bondpoints for bug 833-1 """ Apply the given bond-type name (e.g. 'single') to the given bond, iff this is permitted by its atomtypes (or, new feature 060523, if it's permitted by its real atoms' possible atomtypes and their number of real bonds), and do whatever inferences are presently allowed [none are implemented as of 050727]. Emit an appropriate history message. Do appropriate invals/updates. [#e should the inference policy and/or some controlling object be another argument? Maybe even a new first arg 'self'?] @param suppress_history_message: If True, it quietly converts the bondtypes without printing any history message. """ # Note: this can be called either from a bond's context menu, or by using a Build mode dashboard tool to click on bonds # (or bondpoints as of 060702) and immediately change their types. #This flag will be returned by this function to tell the caller whether the #bond type of the given bond was changed bond_type_changed = True v6 = v6_from_btype(btype) oldname = quote_html( str(bond) ) def changeit(also_atypes = None): if v6 == bond.v6: bond_type_changed = False if not suppress_history_message: env.history.message( "bond type of %s is already %s" % (oldname, btype)) else: if also_atypes: # change atomtypes first (not sure if doing this first matters) atype1, atype2 = also_atypes def changeatomtype(atom, atype): if atom.atomtype is not atype: if not suppress_history_message: msg = "changed %r from %s to %s" % (atom, atom.atomtype.name, atype.name ) env.history.message(msg) atom.set_atomtype(atype) ### note[ probably 060523]: # if we're an open bond, we have to prevent this process from removing us! # (this is nim, so we're not yet safe to offer on open bonds. # Thus in fix for 833-1 [060703], atomtype changes are not allowed.) pass return # from changeatomtype changeatomtype(bond.atom1, atype1) changeatomtype(bond.atom2, atype2) bond.set_v6(v6) # this doesn't affect anything else or do any checks ####k #####@@@@@ check that ##e now do inferences on other bonds bond.changed() ###k needed?? maybe it's now done by set_v6?? if not suppress_history_message: env.history.message( "changed bond type of %s to %s" % (oldname, btype)) ###k not sure if it does gl_update when needed... how does menu use of this do that?? ###@@@ return # from changeit poss = poss1 = possible_bond_types(bond) # only includes the ones which don't change the atomtypes -- try these first if btype in poss1: changeit() return bond_type_changed # otherwise figure out if we can change the atomtypes to make this work. # (The following code is predicted to work for either real or open bonds, # but it is not safe to offer on open bonds for other reasons (commented above in changeatomtype). # But we'll still figure out the situation, so the history message can be more useful.) if 1: # this is needed for allow_remake_bondpoints, # or for history advice about what that could have permitted: poss2, permitted1, permitted2 = possible_bond_types_for_elements(bond) # the only purpose of having the whole sequence poss2 # (not just one element of it, equal to btype) is the error message if btype in poss2: atype1 = best_atype(bond.atom1, permitted1[v6]) atype2 = best_atype(bond.atom2, permitted2[v6]) if allow_remake_bondpoints: poss = poss2 # poss is whichever of poss1 or poss2 was actually allowed if btype in poss2: changeit((atype1, atype2)) return bond_type_changed # It failed, but a variety of situations should be handled in the error message. # For error messages, sort them all the same way. poss1.sort() poss2.sort() poss.sort() #k not really needed, it's same mutable list, but keep this in case someone changes that if poss2 == poss : # note, this happens if poss2 == poss1, or if they differ but allow_remake_bondpoints is true # permitting changing of atomtypes wouldn't make any difference if not suppress_history_message: msg = "can't change bond type of %s to %s" % (oldname, btype) msg2 = " -- permitted types are %s" % (poss) #e improve message -- %s of list looks like repr (for strings too) env.history.message( orangemsg( msg) + msg2 ) bond_type_changed = False elif btype in poss2: if allow_remake_bondpoints: print_compact_stack( "bug: allow_remake_bondpoints should not be true here: " ) # the only reason we refused is that the UI won't allow remaking of bondpoints; # explain what the user would have to do to make it work (using the things computed above as if it had been permitted) # (as of 060703 this happens only when you click a bond type changing tool on a bondpoint, # but following code will try to cover this for a real bond as well) unless = "" for atom, atype in [(bond.atom1, atype1), (bond.atom2, atype2)]: ##e ideally, in same order as printed in bond name if atype != atom.atomtype: if atom.is_singlet(): # should never happen if env.debug: print "debug: bug: %r is bondpoint but user is advised to change its atomtype" % atom if not unless: unless = "change atomtype of %s to %s" % (atom, atype.name) else: # this is not expected to ever happen, when called from UI as of 060703; it's untested ##@@ unless += ", and of %s to %s" % (atom, atype.name) msg = "can't change bond type of %s to %s, " % (oldname, btype,) bond_type_changed = False if unless: unless_msg = greenmsg( "unless you %s" % (unless,) ) else: unless_msg = redmsg( "due to a bug") if not suppress_history_message: env.history.message( orangemsg( msg) + ( unless_msg) ) else: # changing atomtypes makes a difference, but either way you're not allowed to change to this bond type if allow_remake_bondpoints: print_compact_stack( "bug: allow_remake_bondpoints should not be true here: " ) extra = complement_sequences(poss2, poss1) if not extra: print_compact_stack( "bug: extra should not be empty here: " ) msg = "can't change bond type of %s to %s" % (oldname, btype) msg2 = " -- permitted types are %s, or %s if you change atomtypes" % (poss1, extra) #e improve message -- %s of list looks like repr (for strings too) bond_type_changed = False if not suppress_history_message: env.history.message( orangemsg( msg) + msg2 ) return bond_type_changed # from apply_btype_to_bond
def maybeTip(self, helpEvent): """ Determines if this tooltip should be displayed. The tooltip will be displayed at helpEvent.globalPos() if an object is highlighted and the mouse hasn't moved for some period of time, called the "wake up delay" period, which is a user pref (not yet implemented in the Preferences dialog) currently set to 1 second. maybeTip() is called by GLPane.timerEvent() whenever the cursor is not moving to determine if the tooltip should be displayed. @param helpEvent: a QHelpEvent constructed by the caller @type helpEvent: QHelpEvent """ # docstring used to also say: ## For more details about this member, see Qt documentation on QToolTip.maybeTip(). # but this is unclear to me (since this class does not inherit from # QToolTip), so I removed it. [bruce 081208] debug = debug_pref("GLPane: graphics debug tooltip?", Choice_boolean_False, prefs_key = True ) glpane = self.glpane selobj = glpane.selobj if debug: # russ 080715: Graphics debug tooltip. # bruce 081208/081211: revised, moved out of _getToolTipText, # made debug_pref. # note: we don't use glpane.MousePos since it's not reliable -- # only some graphicsModes store it, and only in mouse press events. # Note: double buffering applies only to the color buffer, # not the stencil or depth buffers, which have only one copy. # The setting of GL_READ_BUFFER should have no effect on # glReadPixelsf from those buffers. # [bruce 081211 comment, based on Russ report of OpenGL doc] pos = helpEvent.pos() wX = pos.x() wY = glpane.height - pos.y() #review: off by 1?? wZ = glReadPixelsf(wX, wY, 1, 1, GL_DEPTH_COMPONENT)[0][0] stencil = glReadPixelsi(wX, wY, 1, 1, GL_STENCIL_INDEX)[0][0] savebuff = glGetInteger(GL_READ_BUFFER) whichbuff = {GL_FRONT:"front", GL_BACK:"back"}.get(savebuff, "unknown") redraw_counter = env.redraw_counter # Pixel data is sign-wrapped, in spite of specifying unsigned_byte. def us(b): if b < 0: return 256 + b else: return b def pixvals(buff): glReadBuffer(buff) gl_format, gl_type = GL_RGBA, GL_UNSIGNED_BYTE rgba = glReadPixels( wX, wY, 1, 1, gl_format, gl_type )[0][0] return ( "rgba %u, %u, %u, %u" % (us(rgba[0]), us(rgba[1]), us(rgba[2]), us(rgba[3])) ) def redifnot(v1, v2, text): if v1 != v2: return redmsg(text) else: return text front_pixvals = pixvals(GL_FRONT) back_pixvals = pixvals(GL_BACK) glReadBuffer(savebuff) # restore the saved value tipText = ( "env.redraw = %d; selobj = %s<br>" % (redraw_counter, quote_html(str(selobj)),) + # note: sometimes selobj is an instance of _UNKNOWN_SELOBJ_class... relates to testmode bug from renderText # (confirmed that renderText zaps stencil and that that alone causes no bug in other graphicsmodes) # TODO: I suspect this can be printed even during rendering... need to print glpane variables # which indicate whether we're doing rendering now, e.g. current_glselect, drawing_phase; # also modkeys (sp?), glselect_wanted "mouse position (xy): %d, %d<br>" % (wX, wY,) + "depth %f, stencil %d<br>" % (wZ, stencil) + redifnot(whichbuff, "back", "current read buffer: %s<br>" % whichbuff ) + redifnot(glpane.glselect_wanted, 0, "glselect_wanted: %s<br>" % (glpane.glselect_wanted,) ) + redifnot(glpane.current_glselect, False, "current_glselect: %s<br>" % (glpane.current_glselect,) ) + redifnot(glpane.drawing_phase, "?", "drawing_phase: %s<br>" % (glpane.drawing_phase,) ) + "front: " + front_pixvals + "<br>" + redifnot(back_pixvals, front_pixvals, "back: " + back_pixvals ) ) global _last_tipText if tipText != _last_tipText: print print tipText _last_tipText = tipText pass # use tipText below else: # <motionlessCursorDuration> is the amount of time the cursor (mouse) has been motionless. motionlessCursorDuration = time.time() - glpane.cursorMotionlessStartTime # Don't display the tooltip yet if <motionlessCursorDuration> hasn't exceeded the "wake up delay". # The wake up delay is currently set to 1 second in prefs_constants.py. Mark 060818. if motionlessCursorDuration < env.prefs[dynamicToolTipWakeUpDelay_prefs_key]: self.toolTipShown = False return # If an object is not currently highlighted, don't display a tooltip. if not selobj: return # If the highlighted object is a singlet, # don't display a tooltip for it. if isinstance(selobj, Atom) and (selobj.element is Singlet): return if self.toolTipShown: # The tooltip is already displayed, so return. # Do not allow tip() to be called again or it will "flash". return tipText = self._getToolTipText() pass # show the tipText if not tipText: tipText = "" # This makes sure that dynamic tip is not displayed when # the highlightable object is 'unknown' to the dynamic tip class. # (From QToolTip.showText doc: "If text is empty the tool tip is hidden.") showpos = helpEvent.globalPos() if debug: # show it a little lower to avoid the cursor obscuring the tooltip. # (might be useful even when not debugging, depending on the cursor) # [bruce 081208] showpos = showpos + QPoint(0, 10) QToolTip.showText(showpos, tipText) #@@@ ninad061107 works fine but need code review self.toolTipShown = True
def _depositLibraryPart(self, newPart, hotspotAtom, atom_or_pos): # probably by Huaicai; revised by bruce 051227, 060627, 070501 """ This method serves as an overloaded method, <atom_or_pos> is the Singlet atom or the empty position that the new part <newPart> [which is an assy, at least sometimes] will be attached to or placed at. [If <atom_or_pos> is a singlet, <hotspotAtom> should be an atom in some chunk in <newPart>.] Currently, it doesn't consider group or jigs in the <newPart>. Not so sure if my attempt to copy a part into another assembly is all right. [It wasn't, so bruce 051227 revised it.] Copies all molecules in the <newPart>, change their assy attribute to current assembly, move them into <pos>. [bruce 051227 new feature:] return a list of new nodes created, and a message for history (currently almost a stub). [not sure if subrs ever print history messages... if they do we'd want to return those instead.] """ attach2Bond = False stuff = [] # list of deposited nodes [bruce 051227 new feature] if isinstance(atom_or_pos, Atom): attch2Singlet = atom_or_pos if hotspotAtom and hotspotAtom.is_singlet() and attch2Singlet.is_singlet(): newMol = hotspotAtom.molecule.copy_single_chunk(None) # [this can break interchunk bonds, # thus it still has bug 2028] newMol.set_assy(self.o.assy) hs = newMol.hotspot ha = hs.singlet_neighbor() # hotspot neighbor atom attch2Atom = attch2Singlet.singlet_neighbor() # attach to atom rotCenter = newMol.center rotOffset = Q(ha.posn() - hs.posn(), attch2Singlet.posn() - attch2Atom.posn()) newMol.rot(rotOffset) moveOffset = attch2Singlet.posn() - hs.posn() newMol.move(moveOffset) self.graphicsMode._createBond(hs, ha, attch2Singlet, attch2Atom) self.o.assy.addmol(newMol) stuff.append(newMol) # e if there are other chunks in <newPart>, # they are apparently copied below. [bruce 060627 comment] else: ## something is wrong, do nothing return stuff, "internal error" attach2Bond = True else: placedPos = atom_or_pos if hotspotAtom: hotspotAtomPos = hotspotAtom.posn() moveOffset = placedPos - hotspotAtomPos else: if newPart.molecules: moveOffset = placedPos - newPart.molecules[0].center # e not # the best choice of center [bruce 060627 comment] if attach2Bond: # Connect part to a bondpoint of an existing chunk for m in newPart.molecules: if not m is hotspotAtom.molecule: newMol = m.copy_single_chunk(None) # [this can break interchunk bonds, # thus it still has bug 2028] newMol.set_assy(self.o.assy) ## Get each of all other chunks' center movement for the ## rotation around 'rotCenter' coff = rotOffset.rot(newMol.center - rotCenter) coff = rotCenter - newMol.center + coff # The order of the following 2 statements doesn't matter newMol.rot(rotOffset) newMol.move(moveOffset + coff) self.o.assy.addmol(newMol) stuff.append(newMol) else: # Behaves like dropping a part anywhere you specify, independent # of existing chunks. # copy all nodes in newPart (except those in clipboard items), # regardless of node classes; # put it in a new Group if more than one thing [bruce 070501] # [TODO: this should be done in the cases above, too, but that's # not yet implemented, # and requires adding rot or pivot to the Node API and revising # the rot-calling code above, # and also reviewing the definition of the "hotspot of a Part" and # maybe of a "depositable clipboard item".] assert newPart.tree.is_group() nodes = list(newPart.tree.members) # might be [] assy = self.o.assy newnodes = copied_nodes_for_DND(nodes, autogroup_at_top=True, assy=assy) # Note: that calls name_autogrouped_nodes_for_clipboard # internally, if it forms a Group, # but we ignore that and rename the new node differently below, # whether or not it was autogrouped. We could just as well do # the autogrouping ourselves... # Note [bruce 070525]: it's better to call copied_nodes_for_DND # here than copy_nodes_in_order, even if we didn't need to # autogroup. One reason is that if some node is not copied, # that's not necessarily an error, since we don't care about 1-1 # orig-copy correspondence here. if not newnodes: if newnodes is None: print "bug: newnodes should not be None; nodes was %r (saved in debug._bugnodes)" % (nodes,) # TODO: This might be possible, for arbitrary partlib # contents, just not for legitimate ones... # but partlib will probably be (or is) user-expandable, # so we should turn this into history message, # not a bug print. But I'm not positive it's possible # w/o a bug, so review first. ###FIX [bruce 070501 comment] import utilities.debug as debug debug._bugnodes = nodes newnodes = [] msg = redmsg("error: nothing to deposit in [%s]" % quote_html(str(newPart.name))) return [], msg assert len(newnodes) == 1 # due to autogroup_at_top = True # but the remaining code works fine regardless of len(newnodes), # in case we make autogroup a preference for newnode in newnodes: # Rename newnode based on the partlib name and a unique number. # It seems best to let the partlib mmp file contents (not just # filename) # control the name used here, so use newPart.tree.name rather # than just newPart.name. # (newPart.name is a complete file pathname; newPart.tree.name # is usually its basename w/o extension.) basename = str(newPart.tree.name) if basename == "Untitled": # kluge, for the sake of 3 current partlib files, and files # saved only once by users (due to NE1 bug in save) dirjunk, base = os.path.split(newPart.name) basename, extjunk = os.path.splitext(base) from utilities.constants import gensym newnode.name = gensym(basename, assy) # name library part # bruce 080407 basename + " " --> basename, and pass assy # (per Mark NFR desire) # based on basename recorded in its mmp file's top node newnode.move(moveOffset) # k not sure this method is correctly # implemented for measurement jigs, named views assy.addnode(newnode) stuff.append(newnode) ## #bruce 060627 new code: fix bug 2028 (non-hotspot case only) ## # about interchunk bonds being broken ## nodes = newPart.molecules ## newnodes = copied_nodes_for_DND(nodes) ## if newnodes is None: ## print "bug: newnodes should not be None; nodes was %r (saved in debug._bugnodes)" % (nodes,) ## debug._bugnodes = nodes ## newnodes = [] # kluge ## for newMol in newnodes: ## # some of the following probably only work for Chunks, ## # though coding them for other nodes would not be hard ## newMol.set_assy(self.o.assy) ## newMol.move(moveOffset) ## self.o.assy.addmol(newMol) ## stuff.append(newMol) self.o.assy.update_parts() # bruce 051227 see if this fixes the # atom_debug exception in checkparts msg = ( greenmsg("Deposited library part: ") + " [" + quote_html(str(newPart.name)) + "]" ) # ninad060924 fix bug 1164 return stuff, msg ####@@@@ should revise this message
def _done_history_msg(self): env.history.message(cmd + quote_html("%s %s." % (self.comment.name, self.action)))
def getToolTipInfoForBond(self, bond): """ Tooltip information when the cursor is over a strand bond. As of 2008-11-09, it gives the information in the following form: """ #Bond direction will always be atm1 --> atm2 #@see: Bond.bond_direction_from() atm1 = bond.atom1 atm2 = bond.atom2 strandInfo = "" if not (atm1 and atm2): strandInfo = self.getDefaultToolTipInfo() return strandInfo threePrimeEndAtom = self.get_three_prime_end_base_atom() fivePrimeEndAtom = self.get_five_prime_end_base_atom() allAtoms = self.get_strand_atoms_in_bond_direction(filterBondPoints = True) tooltipDirection = "3<--5" left_atom = bond_left_atom(bond, quat = self.assy.glpane.quat) right_atom = bond.other(left_atom) if bond.bond_direction_from(left_atom) == 1: tooltipDirection = "5-->3" else: tooltipDirection = "3<--5" left_atm_index = None try: left_atm_index = allAtoms.index(left_atom) except: print_compact_traceback("bug in getting strand info string "\ "atom %s not in list"%left_atom) if left_atm_index: #@BUG: The computation of numOfBases_next_crossover_5prime and #numOfBases_next_crossover_3prime is wrong in some cases. So, #that information is not displayed. numOfBases_next_crossover_5prime, numOfBases_next_crossover_3prime = \ self._number_of_atoms_before_next_crossover( left_atom, tooltipDirection = tooltipDirection) if threePrimeEndAtom and fivePrimeEndAtom: if tooltipDirection == "3<--5": numOfBasesDown_3PrimeDirection = len(allAtoms[left_atm_index:]) #Note: This does not include atm1 , which is intentional-- numOfBasesDown_5PrimeDirection = len(allAtoms[:left_atm_index]) ##strandInfo += " 3' < " + str(numOfBasesDown_3PrimeDirection) + "/" + str(numOfBases_next_crossover_3prime) strandInfo += " 3' < " + str(numOfBasesDown_3PrimeDirection) strandInfo += " --(%s)-- "%(len(allAtoms)) ##strandInfo += str(numOfBases_next_crossover_5prime) + "/" + str(numOfBasesDown_5PrimeDirection) + " < 5'" strandInfo += str(numOfBasesDown_5PrimeDirection) + " < 5'" else: numOfBasesDown_3PrimeDirection = len(allAtoms[left_atm_index + 1:]) #Note: This does not include atm1 , which is intentional-- numOfBasesDown_5PrimeDirection = len(allAtoms[:left_atm_index + 1]) ##strandInfo += " 5' > " + str(numOfBasesDown_5PrimeDirection) + "/" + str(numOfBases_next_crossover_5prime) strandInfo += " 5' > " + str(numOfBasesDown_5PrimeDirection) strandInfo += " --(%s)-- "%(len(allAtoms)) ##strandInfo += str(numOfBases_next_crossover_3prime) + "/" + str(numOfBasesDown_3PrimeDirection) + " > 3'" strandInfo += str(numOfBasesDown_3PrimeDirection) + " > 3'" #Make sure that symbol like > are converted to html strandInfo = quote_html(strandInfo) return strandInfo
def make_or_remove_crossover(twoPls, make = True, cmdname = None): """ Make or Remove (according to make option) a crossover, given Pl5_recognizers for its two Pl atoms. """ # What we are doing is recognizing one local structure and replacing it with another # made from the same atoms. It'd sure be easier if I could do the manipulation in an mmp file, # save that somewhere, and read those to generate the operation! I'd have two sets of atoms, before and after, # and see how bonds and atomtypes got changed. # In this case it's not too hard to hand-code... I guess only the Pl atoms and their bonds are affected. # We probably do have to look at strand directions -- hmm, probably we should require them to exist before saying it's ok! # Or maybe better to give history error message when the command is chosen, saying you need to set them (or repair them) first... # Then we have to move the new/moved Pl atoms into a good position... # Note: Pl.ordered_bases are ordered by bond direction, to make this easier... # but if we want to patch up the directions in the end, do we need to care exactly which ones were defined? # or only "per-Pl"? hmm... it's per-Pl for now assert cmdname for pl in twoPls: if pl.ordered_bases is None: # should no longer be possible -- now checked before menu commands are offered [bruce 070604] ###BUG: this could have various causes, not only the one reported below! Somehow we need access to the # message supplied to the RecognizerError, for use here. ###REVIEW: Does that mean it should pass through compute methods (probably in a controlled way) # rather than making computed values None? # Or, should the value not be None, but a "frozen" examinable and reraisable version of the error exception?? msg = "%s: Error: bond direction is locally undefined or inconsistent around %s" % (cmdname, pl.atom) ###UNTESTED print "should no longer be possible:", msg #bruce 070604 env.history.message( redmsg( quote_html( msg))) return Pl1, Pl2 = twoPls a,b = Pl1.ordered_bases d,c = Pl2.ordered_bases # note: we use d,c rather than c,d so that the atom arrangement is as shown in the diagram below. # Note: for either the Make or Remove operation, the geometric arrangement is initially: # # c <-- Pl2 <-- d # # a --> Pl1 --> b # # and it ends up being (where dots indicate arrowheads, to show bond direction): # # c d # . / # \ . # Pl1 Pl2 # . \ # / . # a b # # Note: Pl1 stays attached to a, and Pl2 to d. Which two opposite bonds to preserve like that # is an arbitrary choice -- as long as Make and Remove make the same choice about that, # they'll reverse each other's effects precisely (assuming the sugars were initially correct as Ss or Sj). # break the bonds we no longer want for obj1, obj2 in [(Pl1, b), (Pl2, c)]: bond = find_bond(obj1.atom, obj2.atom) bond.bust(make_bondpoints = False) # make the bonds we want and didn't already have for obj1, obj2 in [(Pl1, c), (Pl2, b)]: assert not atoms_are_bonded(obj1.atom, obj2.atom) ###e we should make bond_atoms do this assert itself, or maybe tolerate it (or does it already??) bond_atoms_faster(obj1.atom, obj2.atom, V_SINGLE) # set directions of all 4 bonds (even the preserved ones -- it's possible they were not set before, # if some but not all bonds had directions set in the part of a strand whose directions we look at.) for obj1, obj2 in [(a, Pl1), (Pl1, c), (d, Pl2), (Pl2, b)]: bond = find_bond(obj1.atom, obj2.atom) bond.set_bond_direction_from(obj1.atom, 1) # WARNING: after that bond rearrangement, don't use our Pl5_recognizers in ways that depend on Pl bonding, # since it's not well defined whether they think about the old or new bonding to give their answers. Pl_atoms = Pl1.atom, Pl2.atom del Pl1, Pl2, twoPls # transmute base sugars to Sj or Ss as appropriate if dna_updater_is_enabled(): want = Element_Ss5 #bruce 080320 bugfix else: want = make and Element_Sj5 or Element_Ss5 for obj in (a,b,c,d): obj.atom.Transmute(want) # Note: we do this after the bond making/breaking so it doesn't add singlets which mess us up. # move Pl atoms into better positions # (someday, consider using local minimize; for now, just place them directly between their new neighbor atoms, # hopefully we leave them selected so user can easily do their own local minimize.) for pl in Pl_atoms: pl.setposn( average_value( map( lambda neighbor: neighbor.posn() , pl.neighbors() ))) env.history.message( greenmsg( cmdname + ": ") + quote_html("(%s - %s)" % tuple(Pl_atoms))) #e need assy.changed()? evidently not. return # from make_or_remove_crossover
def apply_btype_to_bond( btype, bond, allow_remake_bondpoints=True, suppress_history_message=False ): #bruce 060703 added allow_remake_bondpoints for bug 833-1 """ Apply the given bond-type name (e.g. 'single') to the given bond, iff this is permitted by its atomtypes (or, new feature 060523, if it's permitted by its real atoms' possible atomtypes and their number of real bonds), and do whatever inferences are presently allowed [none are implemented as of 050727]. Emit an appropriate history message. Do appropriate invals/updates. [#e should the inference policy and/or some controlling object be another argument? Maybe even a new first arg 'self'?] @param suppress_history_message: If True, it quietly converts the bondtypes without printing any history message. """ # Note: this can be called either from a bond's context menu, or by using a Build mode dashboard tool to click on bonds # (or bondpoints as of 060702) and immediately change their types. #This flag will be returned by this function to tell the caller whether the #bond type of the given bond was changed bond_type_changed = True v6 = v6_from_btype(btype) oldname = quote_html(str(bond)) def changeit(also_atypes=None): if v6 == bond.v6: bond_type_changed = False if not suppress_history_message: env.history.message("bond type of %s is already %s" % (oldname, btype)) else: if also_atypes: # change atomtypes first (not sure if doing this first matters) atype1, atype2 = also_atypes def changeatomtype(atom, atype): if atom.atomtype is not atype: if not suppress_history_message: msg = "changed %r from %s to %s" % ( atom, atom.atomtype.name, atype.name) env.history.message(msg) atom.set_atomtype(atype) ### note[ probably 060523]: # if we're an open bond, we have to prevent this process from removing us! # (this is nim, so we're not yet safe to offer on open bonds. # Thus in fix for 833-1 [060703], atomtype changes are not allowed.) pass return # from changeatomtype changeatomtype(bond.atom1, atype1) changeatomtype(bond.atom2, atype2) bond.set_v6( v6 ) # this doesn't affect anything else or do any checks ####k #####@@@@@ check that ##e now do inferences on other bonds bond.changed() ###k needed?? maybe it's now done by set_v6?? if not suppress_history_message: env.history.message("changed bond type of %s to %s" % (oldname, btype)) ###k not sure if it does gl_update when needed... how does menu use of this do that?? ###@@@ return # from changeit poss = poss1 = possible_bond_types( bond ) # only includes the ones which don't change the atomtypes -- try these first if btype in poss1: changeit() return bond_type_changed # otherwise figure out if we can change the atomtypes to make this work. # (The following code is predicted to work for either real or open bonds, # but it is not safe to offer on open bonds for other reasons (commented above in changeatomtype). # But we'll still figure out the situation, so the history message can be more useful.) if 1: # this is needed for allow_remake_bondpoints, # or for history advice about what that could have permitted: poss2, permitted1, permitted2 = possible_bond_types_for_elements(bond) # the only purpose of having the whole sequence poss2 # (not just one element of it, equal to btype) is the error message if btype in poss2: atype1 = best_atype(bond.atom1, permitted1[v6]) atype2 = best_atype(bond.atom2, permitted2[v6]) if allow_remake_bondpoints: poss = poss2 # poss is whichever of poss1 or poss2 was actually allowed if btype in poss2: changeit((atype1, atype2)) return bond_type_changed # It failed, but a variety of situations should be handled in the error message. # For error messages, sort them all the same way. poss1.sort() poss2.sort() poss.sort( ) #k not really needed, it's same mutable list, but keep this in case someone changes that if poss2 == poss: # note, this happens if poss2 == poss1, or if they differ but allow_remake_bondpoints is true # permitting changing of atomtypes wouldn't make any difference if not suppress_history_message: msg = "can't change bond type of %s to %s" % (oldname, btype) msg2 = " -- permitted types are %s" % (poss) #e improve message -- %s of list looks like repr (for strings too) env.history.message(orangemsg(msg) + msg2) bond_type_changed = False elif btype in poss2: if allow_remake_bondpoints: print_compact_stack( "bug: allow_remake_bondpoints should not be true here: ") # the only reason we refused is that the UI won't allow remaking of bondpoints; # explain what the user would have to do to make it work (using the things computed above as if it had been permitted) # (as of 060703 this happens only when you click a bond type changing tool on a bondpoint, # but following code will try to cover this for a real bond as well) unless = "" for atom, atype in [ (bond.atom1, atype1), (bond.atom2, atype2) ]: ##e ideally, in same order as printed in bond name if atype != atom.atomtype: if atom.is_singlet(): # should never happen if env.debug: print "debug: bug: %r is bondpoint but user is advised to change its atomtype" % atom if not unless: unless = "change atomtype of %s to %s" % (atom, atype.name) else: # this is not expected to ever happen, when called from UI as of 060703; it's untested ##@@ unless += ", and of %s to %s" % (atom, atype.name) msg = "can't change bond type of %s to %s, " % ( oldname, btype, ) bond_type_changed = False if unless: unless_msg = greenmsg("unless you %s" % (unless, )) else: unless_msg = redmsg("due to a bug") if not suppress_history_message: env.history.message(orangemsg(msg) + (unless_msg)) else: # changing atomtypes makes a difference, but either way you're not allowed to change to this bond type if allow_remake_bondpoints: print_compact_stack( "bug: allow_remake_bondpoints should not be true here: ") extra = complement_sequences(poss2, poss1) if not extra: print_compact_stack("bug: extra should not be empty here: ") msg = "can't change bond type of %s to %s" % (oldname, btype) msg2 = " -- permitted types are %s, or %s if you change atomtypes" % ( poss1, extra) #e improve message -- %s of list looks like repr (for strings too) bond_type_changed = False if not suppress_history_message: env.history.message(orangemsg(msg) + msg2) return bond_type_changed # from apply_btype_to_bond
try: return aCallable() except CadBug, e: reason = "Bug in the CAD system" except PluginBug, e: reason = "Bug in the plug-in" except UserError, e: reason = "User error" except Exception, e: #bruce 070518 revised the message in this case, # and revised subsequent code to set self.pluginException # even in this case (since I am interpreting it as a bug) reason = "Exception" #TODO: should improve, include exception name print_compact_traceback(reason + ": ") env.history.message(redmsg(reason + ": " + quote_html(" - ".join(map(str, e.args))) )) self.remove_struct() self.pluginException = True return def _ok_or_preview(self, doneMsg = False, previewing = False): """ Private method. Do the Done or Preview operation (and set the Qt wait cursor while doing it), according to flags. """ ### REVIEW how to split this between GeneratorCommand and GeneratorPM, # and how to rename it then # [070724 code review] QApplication.setOverrideCursor( QCursor(Qt.WaitCursor) ) self.win.assy.current_command_info(cmdname = self.cmdname) def aCallable():
try: return aCallable() except CadBug, e: reason = "Bug in the CAD system" except PluginBug, e: reason = "Bug in the plug-in" except UserError, e: reason = "User error" except Exception, e: #bruce 070518 revised the message in this case, # and revised subsequent code to set self.pluginException # even in this case (since I am interpreting it as a bug) reason = "Exception" #TODO: should improve, include exception name print_compact_traceback(reason + ": ") env.history.message( redmsg(reason + ": " + quote_html(" - ".join(map(str, e.args))))) self.remove_struct() self.pluginException = True return def _ok_or_preview(self, doneMsg=False, previewing=False): """ Private method. Do the Done or Preview operation (and set the Qt wait cursor while doing it), according to flags. """ ### REVIEW how to split this between GeneratorCommand and GeneratorPM, # and how to rename it then # [070724 code review] QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.win.assy.current_command_info(cmdname=self.cmdname)