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 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 _bond_type_menu_section(bond): #bruce 050716; replaces bond_type_submenu_spec for Alpha6 """ Return a menu_spec for changing the bond_type of this bond (as one or more checkmark items, one per permitted bond-type given the atomtypes), or if the bond-type is unchangeable, a disabled menu item for displaying the type (which looks the same as when the bond type is changeable, except for being disabled). (If the current bond type is not permitted, it's still present and checked, but disabled, and it might have a warning saying it's illegal.) """ # this assert is true, but it would cause an import loop: ## assert isinstance(bond, Bond) btype_now = btype_from_v6(bond.v6) poss1 = possible_bond_types(bond) # a list of strings which are bond-type names, in order of increasing bond order poss, permitted1, permitted2 = possible_bond_types_for_elements(bond) # new feature 060703 ##e could put weird ones (graphitic, carbomeric) last and/or in parens, in subtext below types = list(poss) for btype in poss1: if btype not in types: print "should never happen: %r not in %r" % (btype, poss) # intentional: "not in types" above, "not in poss" here types.append(btype) if btype_now not in types: types.append(btype_now) # put this one last, since it's illegal; warning for it is computed later assert len(types) > 0 # types is the list of bond types for which to make menu items, in order; # now make them, and figure out which ones are checked and/or disabled; # we disable even legal ones iff there is only one bond type in types # (which means, if current type is illegal, it is disabled and the sole legal type is enabled). disable_legal_types = (len(types) == 1) res = [] for btype in types: # include current value even if it's illegal subtext = "%s bond" % btype # this string might be extended below checked = (btype == btype_now) command = ( lambda arg1=None, arg2=None, btype=btype, bond=bond: apply_btype_to_bond(btype, bond) ) warning = warning2 = "" if btype not in poss: # illegal btype (note: it will be the current one, and thus be the only checked one) warning = "illegal" disabled = True else: # legal btype warning = bond_type_warning(bond, btype) # might be "" (or None??) for no warning if btype not in poss1: # new feature 060703 # try1: too long and boring (when in most menu entries): ## warning2 = "would change atomtypes" # try2: say which atomtypes we'd change to, in same order of atoms as the bond name v6 = v6_from_btype(btype) atype1 = best_atype(bond.atom1, permitted1[v6]) atype2 = best_atype(bond.atom2, permitted2[v6]) in_order = [atype1, atype2] ##e stub; see code in Bond.__str__ warning2 = "%s<->%s" % tuple([atype.name for atype in in_order]) disabled = disable_legal_types # might change this if some neighbor bonds are locked (nim), or if we want to show non-possible choices if warning2: subtext += " (%s)" % warning2 if warning: subtext += " (%s)" % warning res.append(( subtext, command, disabled and 'disabled' or None, checked and 'checked' or None )) ##e if >1 legal value, maybe we should add a toggleable checkmark item to permit "locking" the bond to its current bond type; # this won't be needed until we have better bond inference (except maybe for bondpoints), # since right now [still true 060703] we never alter real bond types except when the user does an action on that specific bond. if not bond.is_open_bond(): ## command = ( lambda arg1 = None, arg2 = None, bond = bond: bond.bust() ) command = ( lambda bond = bond: delete_bond(bond) ) res.append(None) # separator res.append(("Delete Bond", command)) return res
def _bond_type_menu_section( bond): #bruce 050716; replaces bond_type_submenu_spec for Alpha6 """ Return a menu_spec for changing the bond_type of this bond (as one or more checkmark items, one per permitted bond-type given the atomtypes), or if the bond-type is unchangeable, a disabled menu item for displaying the type (which looks the same as when the bond type is changeable, except for being disabled). (If the current bond type is not permitted, it's still present and checked, but disabled, and it might have a warning saying it's illegal.) """ # this assert is true, but it would cause an import loop: ## assert isinstance(bond, Bond) btype_now = btype_from_v6(bond.v6) poss1 = possible_bond_types( bond ) # a list of strings which are bond-type names, in order of increasing bond order poss, permitted1, permitted2 = possible_bond_types_for_elements( bond) # new feature 060703 ##e could put weird ones (graphitic, carbomeric) last and/or in parens, in subtext below types = list(poss) for btype in poss1: if btype not in types: print "should never happen: %r not in %r" % ( btype, poss ) # intentional: "not in types" above, "not in poss" here types.append(btype) if btype_now not in types: types.append( btype_now ) # put this one last, since it's illegal; warning for it is computed later assert len(types) > 0 # types is the list of bond types for which to make menu items, in order; # now make them, and figure out which ones are checked and/or disabled; # we disable even legal ones iff there is only one bond type in types # (which means, if current type is illegal, it is disabled and the sole legal type is enabled). disable_legal_types = (len(types) == 1) res = [] for btype in types: # include current value even if it's illegal subtext = "%s bond" % btype # this string might be extended below checked = (btype == btype_now) command = (lambda arg1=None, arg2=None, btype=btype, bond=bond: apply_btype_to_bond(btype, bond)) warning = warning2 = "" if btype not in poss: # illegal btype (note: it will be the current one, and thus be the only checked one) warning = "illegal" disabled = True else: # legal btype warning = bond_type_warning( bond, btype) # might be "" (or None??) for no warning if btype not in poss1: # new feature 060703 # try1: too long and boring (when in most menu entries): ## warning2 = "would change atomtypes" # try2: say which atomtypes we'd change to, in same order of atoms as the bond name v6 = v6_from_btype(btype) atype1 = best_atype(bond.atom1, permitted1[v6]) atype2 = best_atype(bond.atom2, permitted2[v6]) in_order = [atype1, atype2] ##e stub; see code in Bond.__str__ warning2 = "%s<->%s" % tuple( [atype.name for atype in in_order]) disabled = disable_legal_types # might change this if some neighbor bonds are locked (nim), or if we want to show non-possible choices if warning2: subtext += " (%s)" % warning2 if warning: subtext += " (%s)" % warning res.append((subtext, command, disabled and 'disabled' or None, checked and 'checked' or None)) ##e if >1 legal value, maybe we should add a toggleable checkmark item to permit "locking" the bond to its current bond type; # this won't be needed until we have better bond inference (except maybe for bondpoints), # since right now [still true 060703] we never alter real bond types except when the user does an action on that specific bond. if not bond.is_open_bond(): ## command = ( lambda arg1 = None, arg2 = None, bond = bond: bond.bust() ) command = (lambda bond=bond: delete_bond(bond)) res.append(None) # separator res.append(("Delete Bond", command)) return res