def make_DnaStrandOrSegment_for_marker(self, controlling_marker): # review: wholechain arg needed? @@@ """ The given DnaMarker is either newly made to control a wholechain, or old but newly controlling it; but it has no DnaStrandOrSegment. Make and return a new DnaStrand or DnaSegment (ask marker what class to use) inside self (review: inside some Group inside self?), perhaps making use of info in controlling_marker to help decide how to initialize some of its attributes. (Assume calling code will later move all chunks and markers from marker's wholechain into the new DnaStrandOrSegment, and will store references to it as needed into controlling_marker and/or its wholechain, so don't do those things here.) """ assert not self.killed(), \ "self must not be killed in %r.make_DnaStrandOrSegment_for_marker" % self class1 = controlling_marker.DnaStrandOrSegment_class() name = gensym(class1.__name__.split('.')[-1], self.assy) ###STUB -- should use class constant prefix # todo: sensible name? (if we split a seg, is name related to old seg; if so how?) assy = controlling_marker.assy # it's a Jig so it has one obj = class1(name, assy, None) # note: these args are for Group.__init__ self.addchild(obj) # note: this asserts self.assy is not None # (but it can't assert not self.killed(), see its comment for why) return obj
def _createStructure(self): """ Build a graphene sheet from the parameters in the Property Manager. """ # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name params = self._gatherParameters() # self.win.assy.part.ensure_toplevel_group() structGenerator = GrapheneGenerator() struct = structGenerator.make(self.win.assy, name, params, editCommand = self) self.win.assy.part.topnode.addmember(struct) self.win.win_update() return struct
def _createStructure(self): """ Build a graphene sheet from the parameters in the Property Manager. """ # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name params = self._gatherParameters() # self.win.assy.part.ensure_toplevel_group() structGenerator = GrapheneGenerator() struct = structGenerator.make(self.win.assy, name, params, editCommand=self) self.win.assy.part.topnode.addmember(struct) self.win.win_update() return struct
def make_Atom_and_bondpoints(self, elem, pos, atomtype=None, Chunk_class=None): """ Create one unbonded atom, of element elem and (if supplied) the given atomtype (otherwise the default atomtype for elem), at position pos, in its own new chunk, with enough bondpoints to have no valence error. @param Chunk_class: constructor for the returned atom's new chunk (self.assy.Chunk by default) @return: one newly created Atom object, already placed into a new chunk which has been added to the model using addnode """ # bruce 041215 moved this from chunk.py to chem.py, and split part of it # into the new atom method make_bondpoints_when_no_bonds, to help fix bug 131. # bruce 050510 added atomtype option # bruce 080520 added Chunk_class option # bruce 090112 renamed oneUnbonded function and turned it into this method assy = self.assy if Chunk_class is None: Chunk_class = assy.Chunk chunk = Chunk_class(assy, "bug") # name is reset below! atom = Atom(elem.symbol, pos, chunk) # bruce 041124 revised name of new chunk, was gensym('Chunk.'); # no need for gensym since atom key makes the name unique, e.g. C1. atom.set_atomtype_but_dont_revise_singlets(atomtype) # ok to pass None, type name, or type object; this verifies no change in elem # note, atomtype might well already be the value we're setting; # if it is, this should do nothing ## chunk.name = "Chunk-%s" % str(atom) chunk.name = gensym("Chunk", assy) # bruce 080407 per Mark NFR desire atom.make_bondpoints_when_no_bonds() # notices atomtype assy.addnode(chunk) # REVIEW: same as self.addnode? return atom
def make_DnaStrandOrSegment_for_marker( self, controlling_marker): # review: wholechain arg needed? @@@ """ The given DnaMarker is either newly made to control a wholechain, or old but newly controlling it; but it has no DnaStrandOrSegment. Make and return a new DnaStrand or DnaSegment (ask marker what class to use) inside self (review: inside some Group inside self?), perhaps making use of info in controlling_marker to help decide how to initialize some of its attributes. (Assume calling code will later move all chunks and markers from marker's wholechain into the new DnaStrandOrSegment, and will store references to it as needed into controlling_marker and/or its wholechain, so don't do those things here.) """ assert not self.killed(), \ "self must not be killed in %r.make_DnaStrandOrSegment_for_marker" % self class1 = controlling_marker.DnaStrandOrSegment_class() name = gensym(class1.__name__.split('.')[-1], self.assy) ###STUB -- should use class constant prefix # todo: sensible name? (if we split a seg, is name related to old seg; if so how?) assy = controlling_marker.assy # it's a Jig so it has one obj = class1(name, assy, None) # note: these args are for Group.__init__ self.addchild(obj) # note: this asserts self.assy is not None # (but it can't assert not self.killed(), see its comment for why) return obj
def __init__(self, assy, name, scale, pov, zoomFactor, wxyz): """ @param pov: the inverse of the "center of view" in model coordinates @type pov: position vector (Numeric.array of 3 ints or floats, as made by V(x,y,z)) @param wxyz: orientation of view @type wxyz: a Quaternion (class VQT.Q), or a sequence of 4 floats which can be passed to that class to make one, e.g. Q(W, x, y, z) is the quaternion with axis vector x,y,z and sin(theta/2) = W """ self.const_pixmap = imagename_to_pixmap("modeltree/NamedView.png") if not name: name = gensym("%s" % self.sym, assy) Node.__init__(self, assy, name) self.scale = scale assert type(pov) is type(V(1, 0, 0)) self.pov = V(pov[0], pov[1], pov[2]) self.zoomFactor = zoomFactor self.quat = Q(wxyz) #bruce 050518/080303 comment: wxyz is passed as an array of 4 floats # (in same order as in mmp file's csys record), when parsing # csys mmp records, or with wxyz a quat in other places. return
def __init__(self, assy, name, scale, pov, zoomFactor, wxyz): """ @param pov: the inverse of the "center of view" in model coordinates @type pov: position vector (Numeric.array of 3 ints or floats, as made by V(x,y,z)) @param wxyz: orientation of view @type wxyz: a Quaternion (class VQT.Q), or a sequence of 4 floats which can be passed to that class to make one, e.g. Q(W, x, y, z) is the quaternion with axis vector x,y,z and sin(theta/2) = W """ self.const_pixmap = imagename_to_pixmap("modeltree/NamedView.png") if not name: name = gensym("%s" % self.sym, assy) Node.__init__(self, assy, name) self.scale = scale assert type(pov) is type(V(1, 0, 0)) self.pov = V(pov[0], pov[1], pov[2]) self.zoomFactor = zoomFactor self.quat = Q(wxyz) #bruce 050518/080303 comment: wxyz is passed as an array of 4 floats # (in same order as in mmp file's csys record), when parsing # csys mmp records, or with wxyz a quat in other places. return
def __init__(self, assy, name, text=''): self.const_pixmap = imagename_to_pixmap("modeltree/comment.png") if not name: name = gensym("%s" % self.sym, assy) Node.__init__(self, assy, name) self.lines = [] # this makes set_text changed() test legal (result of test doesn't matter) self.set_text(text) return
def __init__(self, assy, name, text=''): self.const_pixmap = imagename_to_pixmap("modeltree/comment.png") if not name: name = gensym("%s" % self.sym, assy) Node.__init__(self, assy, name) self.lines = [] # this makes set_text changed() test legal (result of test doesn't matter) self.set_text(text) return
def _make_DnaGroup_for_homeless_objects_in_Part(part): # not needed, done in addnode: part.ensure_toplevel_group() assy = part.assy #k name = gensym("DnaGroup", assy) #bruce 080407 remove "fallback", pass assy dad = None dnaGroup = DnaGroup(name, assy, dad) # same args as for Group.__init__ part.addnode(dnaGroup) if debug_flags.DEBUG_DNA_UPDATER: print "dna_updater: made new dnaGroup %r" % dnaGroup, \ "(bug or unfixed mmp file)" return dnaGroup
def _make_DnaGroup_for_homeless_objects_in_Part(part): # not needed, done in addnode: part.ensure_toplevel_group() assy = part.assy #k name = gensym("DnaGroup", assy) #bruce 080407 remove "fallback", pass assy dad = None dnaGroup = DnaGroup(name, assy, dad) # same args as for Group.__init__ part.addnode(dnaGroup) if debug_flags.DEBUG_DNA_UPDATER: print "dna_updater: made new dnaGroup %r" % dnaGroup, \ "(bug or unfixed mmp file)" return dnaGroup
def mark_atoms(atoms): assert atoms # a list assy = atoms[0].molecule.assy for atom in atoms: assert atom.molecule.assy is assy # all in same assy jig = VeryVisibleAtomMarker(assy, atoms) jig.name = gensym("Marked Atoms ", assy) assy.place_new_jig(jig) # redraw, etc assy.win.glpane.gl_update() # this works now to redraw #e more updates? return
def mark_atoms(atoms): assert atoms # a list assy = atoms[0].molecule.assy for atom in atoms: assert atom.molecule.assy is assy # all in same assy jig = VeryVisibleAtomMarker(assy, atoms) jig.name = gensym("Marked Atoms ", assy) assy.place_new_jig(jig) # redraw, etc assy.win.glpane.gl_update() # this works now to redraw #e more updates? return
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()
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()
def _modifyStructure(self, params): """ Modify the structure based on the parameters specified. Overrides EditCommand._modifystructure. This method removes the old structure and creates a new one using self._createStructure. This was needed for the structures like this (Dna, Nanotube etc) . . See more comments in the method. """ if not pref_nt_segment_resize_by_recreating_nanotube(): self._modifyStructure_NEW_SEGMENT_RESIZE(params) return assert self.struct # parameters have changed, update existing structure self._revertNumber() # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name #@NOTE: Unlike editcommands such as Plane_EditCommand, this #editCommand actually removes the structure and creates a new one #when its modified. -- Ninad 2007-10-24 self._removeStructure() self.previousParams = params self.struct = self._createStructure() # Now append the new structure in self._segmentList (this list of # segments will be provided to the previous command # (BuildDna_EditCommand) # TODO: Should self._createStructure does the job of appending the # structure to the list of segments? This fixes bug 2599 # (see also BuildDna_PropertyManager.Ok if self._parentNanotubeGroup is not None: #Should this be an assertion? (assert self._parentNanotubeGroup is not #None. For now lets just print a warning if parentNanotubeGroup is None self._parentNanotubeGroup.addSegment(self.struct) return
def _build_struct(self, previewing=False): """Private method. Called internally to build the structure by calling the (generator-specific) method build_struct (if needed) and processing its return value. """ params = self.gather_parameters() if self.struct is None: # no old structure, we are making a new structure # (fall through) pass elif not same_vals(params, self.previousParams): # parameters have changed, update existing structure self._revert_number() # (fall through, using old name) pass else: # old structure, parameters same as previous, do nothing return # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name if previewing: env.history.message(self.cmd + "Previewing " + name) else: env.history.message(self.cmd + "Creating " + name) self.remove_struct() self.previousParams = params self.struct = self.build_struct(name, params, -self.win.glpane.pov) self.win.assy.addnode(self.struct) # Do this if you want it centered on the previous center. # self.win.glpane.setViewFitToWindow(fast = True) # Do this if you want it centered on the origin. self.win.glpane.setViewRecenter(fast=True) self.win.win_update() # includes mt_update return
def _createStructure(self): """ Build a peptide from the parameters in the Property Manager. """ # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name self.secondary, self.phi, self.psi, aa_type = self._gatherParameters() # self.win.assy.part.ensure_toplevel_group() """ struct = self.structGenerator.make(self.win.assy, name, params, -self.win.glpane.pov) """ from geometry.VQT import V pos1 = V(self.mouseClickPoints[0][0], \ self.mouseClickPoints[0][1], \ self.mouseClickPoints[0][2]) pos2 = V(self.mouseClickPoints[1][0], \ self.mouseClickPoints[1][1], \ self.mouseClickPoints[1][2]) struct = self.structGenerator.make_aligned(self.win.assy, name, aa_type, self.phi, self.psi, pos1, pos2, fake_chain=False, secondary=self.secondary) self.win.assy.part.topnode.addmember(struct) self.win.win_update() return struct
def _createStructure(self): """ Build a peptide from the parameters in the Property Manager. """ # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name self.secondary, self.phi, self.psi, aa_type = self._gatherParameters() # self.win.assy.part.ensure_toplevel_group() """ struct = self.structGenerator.make(self.win.assy, name, params, -self.win.glpane.pov) """ from geometry.VQT import V pos1 = V(self.mouseClickPoints[0][0], \ self.mouseClickPoints[0][1], \ self.mouseClickPoints[0][2]) pos2 = V(self.mouseClickPoints[1][0], \ self.mouseClickPoints[1][1], \ self.mouseClickPoints[1][2]) struct = self.structGenerator.make_aligned(self.win.assy, name, aa_type, self.phi, self.psi, pos1, pos2, fake_chain = False, secondary = self.secondary) self.win.assy.part.topnode.addmember(struct) self.win.win_update() return struct
def _build_struct(self, previewing = False): """Private method. Called internally to build the structure by calling the (generator-specific) method build_struct (if needed) and processing its return value. """ params = self.gather_parameters() if self.struct is None: # no old structure, we are making a new structure # (fall through) pass elif not same_vals( params, self.previousParams): # parameters have changed, update existing structure self._revert_number() # (fall through, using old name) pass else: # old structure, parameters same as previous, do nothing return # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name if previewing: env.history.message(self.cmd + "Previewing " + name) else: env.history.message(self.cmd + "Creating " + name) self.remove_struct() self.previousParams = params self.struct = self.build_struct(name, params, - self.win.glpane.pov) self.win.assy.addnode(self.struct) # Do this if you want it centered on the previous center. # self.win.glpane.setViewFitToWindow(fast = True) # Do this if you want it centered on the origin. self.win.glpane.setViewRecenter(fast = True) self.win.win_update() # includes mt_update return
def _createStructure(self): """ creates and returns the structure (in this case a L{Group} object that contains the DNA strand and axis chunks. @return : group containing that contains the DNA strand and axis chunks. @rtype: L{Group} @note: This needs to return a DNA object once that model is implemented """ # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name # Create the model tree group node. # Make sure that the 'topnode' of this part is a Group (under which the # DNa group will be placed), if the topnode is not a group, make it a # a 'Group' (applicable to Clipboard parts).See part.py # --Part.ensure_toplevel_group method. This is an important line # and it fixes bug 2585 self.win.assy.part.ensure_toplevel_group() dnaGroup = DnaGroup(self.name, self.win.assy, self.win.assy.part.topnode, editCommand=self) try: self.win.assy.place_new_geometry(dnaGroup) return dnaGroup except (PluginBug, UserError): # Why do we need UserError here? Mark 2007-08-28 dnaGroup.kill() raise PluginBug( "Internal error while trying to create DNA duplex.")
def makeChunkFromAtomList(self, atomList, name = None, group = None, color = None): """ Creates a new chunk from the given atom list. @param atomList: List of atoms from which to create the chunk. @type atomList: list @param name: Name of new chunk. If None, we'll assign one. @type name: str @param group: The group to add the new chunk to. If None, the new chunk is added to the bottom of the model tree. @type group: L{Group} @param color: Color of new chunk. If None, no chunk color is assigned (chunk atoms will be drawn in their element colors). @type color: tuple @return: The new chunk. @rtype: L{Chunk} """ assert atomList if name: newChunk = Chunk(self.assy, name) else: newChunk = Chunk(self.assy, gensym("Chunk", self.assy)) for a in atomList: a.hopmol(newChunk) if group is not None: group.addchild(newChunk) #bruce 080318 addmember -> addchild else: self.addnode(newChunk) newChunk.setcolor(color, repaint_in_MT = False) return newChunk
def _createStructure(self): """ creates and returns the structure (in this case a L{Group} object that contains the DNA strand and axis chunks. @return : group containing that contains the DNA strand and axis chunks. @rtype: L{Group} @note: This needs to return a DNA object once that model is implemented """ # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name # Create the model tree group node. # Make sure that the 'topnode' of this part is a Group (under which the # DNa group will be placed), if the topnode is not a group, make it a # a 'Group' (applicable to Clipboard parts).See part.py # --Part.ensure_toplevel_group method. This is an important line # and it fixes bug 2585 self.win.assy.part.ensure_toplevel_group() dnaGroup = DnaGroup(self.name, self.win.assy, self.win.assy.part.topnode, editCommand = self) try: self.win.assy.place_new_geometry(dnaGroup) return dnaGroup except (PluginBug, UserError): # Why do we need UserError here? Mark 2007-08-28 dnaGroup.kill() raise PluginBug("Internal error while trying to create DNA duplex.")
def makeChunkFromAtomList(self, atomList, name=None, group=None, color=None): """ Creates a new chunk from the given atom list. @param atomList: List of atoms from which to create the chunk. @type atomList: list @param name: Name of new chunk. If None, we'll assign one. @type name: str @param group: The group to add the new chunk to. If None, the new chunk is added to the bottom of the model tree. @type group: L{Group} @param color: Color of new chunk. If None, no chunk color is assigned (chunk atoms will be drawn in their element colors). @type color: tuple @return: The new chunk. @rtype: L{Chunk} """ assert atomList if name: newChunk = Chunk(self.assy, name) else: newChunk = Chunk(self.assy, gensym("Chunk", self.assy)) for a in atomList: a.hopmol(newChunk) if group is not None: group.addchild(newChunk) #bruce 080318 addmember -> addchild else: self.addnode(newChunk) newChunk.setcolor(color, repaint_in_MT=False) return newChunk
def _modifyStructure(self, params): """ Modify the structure based on the parameters specified. Overrides EditCommand._modifystructure. This method removes the old structure and creates a new one using self._createStructure. This was needed for the structures like this (Cnt, Nanotube etc) . . See more comments in the method. @see: a note in self._createSegment() about use of ntSegment.setProps """ assert self.struct # parameters have changed, update existing structure self._revertNumber() # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name #@NOTE: Unlike editcommands such as Plane_EditCommand, this #editCommand actually removes the structure and creates a new one #when its modified. We don't yet know if the CNT object model # will solve this problem. (i.e. reusing the object and just modifying #its attributes. Till that time, we'll continue to use #what the old GeneratorBaseClass use to do ..i.e. remove the item and # create a new one -- Ninad 2007-10-24 self._removeStructure() self.previousParams = params self.struct = self._createStructure() return
def _modifyStructure(self, params): """ Modify the structure based on the parameters specified. Overrides EditCommand._modifystructure. This method removes the old structure and creates a new one using self._createStructure. This was needed for the structures like this (Dna, Nanotube etc) . . See more comments in the method. @see: a note in self._createSegment() about use of dnaSegment.setProps """ assert self.struct # parameters have changed, update existing structure self._revertNumber() # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name #@NOTE: Unlike editcommands such as Plane_EditCommand, this #editCommand actually removes the structure and creates a new one #when its modified. We don't yet know if the DNA object model # will solve this problem. (i.e. reusing the object and just modifying #its attributes. Till that time, we'll continue to use #what the old GeneratorBaseClass use to do ..i.e. remove the item and # create a new one -- Ninad 2007-10-24 self._removeStructure() self.previousParams = params self.struct = self._createStructure() return
def make_Atom_and_bondpoints(self, elem, pos, atomtype = None, Chunk_class = None ): """ Create one unbonded atom, of element elem and (if supplied) the given atomtype (otherwise the default atomtype for elem), at position pos, in its own new chunk, with enough bondpoints to have no valence error. @param Chunk_class: constructor for the returned atom's new chunk (self.assy.Chunk by default) @return: one newly created Atom object, already placed into a new chunk which has been added to the model using addnode """ #bruce 041215 moved this from chunk.py to chem.py, and split part of it # into the new atom method make_bondpoints_when_no_bonds, to help fix bug 131. #bruce 050510 added atomtype option #bruce 080520 added Chunk_class option #bruce 090112 renamed oneUnbonded function and turned it into this method assy = self.assy if Chunk_class is None: Chunk_class = assy.Chunk chunk = Chunk_class(assy, 'bug') # name is reset below! atom = Atom(elem.symbol, pos, chunk) # bruce 041124 revised name of new chunk, was gensym('Chunk.'); # no need for gensym since atom key makes the name unique, e.g. C1. atom.set_atomtype_but_dont_revise_singlets(atomtype) # ok to pass None, type name, or type object; this verifies no change in elem # note, atomtype might well already be the value we're setting; # if it is, this should do nothing ## chunk.name = "Chunk-%s" % str(atom) chunk.name = gensym("Chunk", assy) #bruce 080407 per Mark NFR desire atom.make_bondpoints_when_no_bonds() # notices atomtype assy.addnode(chunk) # REVIEW: same as self.addnode? return atom
def _makeChunkFromAtomList(self, atomList): """ Creates a new chunk from the given atom list. @param atomList: List of atoms from which to create the chunk. @type atomList: list @return: The new chunk. @rtype: L{Chunk} @deprecated: use ops_rechunk.makeChunkFromAtomsList() instead. """ if not atomList: print "bug in creating chunks from the given atom list" return newChunk = Chunk(self.win.assy, gensym("Chunk", self.win.assy)) for a in atomList: # leave the moved atoms picked, so still visible a.hopmol(newChunk) return newChunk
def buildChunk(self, assy): """ Build Chunk for the cookies. First, combine bonds from all layers together, which may fuse some half bonds to full bonds. """ from model.chunk import Chunk from model.chem import Atom from utilities.constants import gensym numLayers = len(self.bondLayers) if numLayers: allBonds = {} allCarbons = {} # Copy the bonds, carbons and hedron from the first layer for ii in range(numLayers): if self.bondLayers.has_key(ii): for bKey, bValue in self.bondLayers[ii].items(): allBonds[bKey] = bValue del self.bondLayers[ii] break for carbons in self.carbonPosDict.values(): for cKey, cValue in carbons.items(): allCarbons[cKey] = cValue for hedrons in self.hedroPosDict.values(): for hKey, hValue in hedrons.items(): allCarbons[hKey] = hValue for bonds in self.bondLayers.values(): for bKey, bValues in bonds.items(): if bKey in allBonds: existValues = allBonds[bKey] for bValue in bValues: if type(bValue) == type((1, 1)): if bValue[1]: ctValue = (bValue[0], 0) else: ctValue = (bValue[0], 1) if ctValue in existValues: idex = existValues.index(ctValue) existValues[idex] = bValue[0] else: existValues += [bValue] else: existValues += [bValue] allBonds[bKey] = existValues else: allBonds[bKey] = bValues # print "allbonds: ", allBonds # print "allCarbons: ", allCarbons carbonAtoms = {} mol = Chunk(assy, gensym("Crystal", assy)) for bKey, bBonds in allBonds.items(): keyHedron = True if len(bBonds): for bond in bBonds: if keyHedron: if type(bBonds[0]) == type(1) or (not bBonds[0][1]): if not bKey in carbonAtoms: keyAtom = Atom("C", allCarbons[bKey], mol) carbonAtoms[bKey] = keyAtom else: keyAtom = carbonAtoms[bKey] keyHedron = False if keyHedron: if type(bond) != type((1, 1)): raise ValueError, (bKey, bond, bBonds) else: xp = (allCarbons[bKey] + allCarbons[bond[0]]) / 2.0 keyAtom = Atom("X", xp, mol) if type(bond) == type(1) or bond[1]: if type(bond) == type(1): bvKey = bond else: bvKey = bond[0] if not bvKey in carbonAtoms: bondAtom = Atom("C", allCarbons[bvKey], mol) carbonAtoms[bvKey] = bondAtom else: bondAtom = carbonAtoms[bvKey] else: xp = (allCarbons[bKey] + allCarbons[bond[0]]) / 2.0 bondAtom = Atom("X", xp, mol) bond_atoms(keyAtom, bondAtom) if len(mol.atoms) > 0: # bruce 050222 comment: much of this is not needed, since mol.pick() does it. # Note: this method is similar to one in BuildCrystal_Command.py. assy.addmol(mol) assy.unpickall_in_GLPane() # was unpickparts; not sure _in_GLPane is best (or that # this is needed at all) [bruce 060721] mol.pick() assy.mt.mt_update() return # from buildChunk
def _createStructure(self): """ Creates and returns the structure (in this case a L{Group} object that contains the nanotube chunk. @return : group containing the nanotube chunk. @rtype: L{Group} @note: This needs to return a CNT object once that model is implemented """ # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name # Create the model tree group node. # Make sure that the 'topnode' of this part is a Group (under which the # Nanotube group will be placed), if the topnode is not a group, make it # a 'Group' (applicable to Clipboard parts). See part.py # --Part.ensure_toplevel_group method. This is an important line # and it fixes bug 2585 self.win.assy.part.ensure_toplevel_group() ntSegment = NanotubeSegment(self.name, self.win.assy, self.win.assy.part.topnode, editCommand = self) try: # Make the nanotube. <ntGroup> will contain one chunk: # - Axis (Segment) # No error checking here; do all error checking in _gatherParameters(). nanotube = self._gatherParameters() position = V(0.0, 0.0, 0.0) self.nanotube = nanotube # needed for done msg #@ ntChunk = nanotube.build(self.name, self.win.assy, position) ntSegment.addchild(ntChunk) #set some properties such as ntRise and its two endpoints. #This information will be stored on the NanotubeSegment object so that #it can be retrieved while editing this object. #WARNING 2008-03-05: Since self._modifyStructure calls #self._createStructure() If in the near future, we actually permit #modifying a #structure (such as a nanotube) without actually recreating the #entire structure, then the following properties must be set in #self._modifyStructure as well. Needs more thought. #props =(nanotube.getChirality(), # nanotube.getType(), # nanotube.getEndings(), # nanotube.getEndPoints()) ntSegment.setProps(nanotube.getParameters()) return ntSegment except (PluginBug, UserError): # Why do we need UserError here? Mark 2007-08-28 self._segmentList.remove(ntSegment) ntSegment.kill_with_contents() raise PluginBug("Internal error while trying to create Nanotube.")
def _modifyStructure(self, params): """ Modify the structure based on the parameters specified. Overrides EditCommand._modifystructure. This method removes the old structure and creates a new one using self._createStructure. This was needed for the structures like this (Dna, Nanotube etc) . . See more comments in the method. """ assert self.struct # parameters have changed, update existing structure self._revertNumber() # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name self.dna = B_Dna_PAM3_SingleStrand() numberOfBases, \ dnaForm, \ dnaModel, \ basesPerTurn, \ duplexRise, \ color_junk = params #see a note about color_junk in DnaSegment_EditCommand._modifyStructure() numberOfBasesToAddOrRemove = self._determine_numberOfBases_to_change() if numberOfBasesToAddOrRemove != 0: resizeEndStrandAtom, resizeEndAxisAtom = \ self.get_strand_and_axis_endAtoms_at_resize_end() if resizeEndAxisAtom: dnaSegment = resizeEndAxisAtom.molecule.parent_node_of_class( self.assy.DnaSegment) resizeEnd_final_position = self._get_resizeEnd_final_position( resizeEndAxisAtom, abs(numberOfBasesToAddOrRemove), duplexRise ) self.dna.modify(dnaSegment, resizeEndAxisAtom, numberOfBasesToAddOrRemove, basesPerTurn, duplexRise, resizeEndAxisAtom.posn(), resizeEnd_final_position, resizeEndStrandAtom = resizeEndStrandAtom ) return
def buildChunk(self, assy): """ Build Chunk for the cookies. First, combine bonds from all layers together, which may fuse some half bonds to full bonds. """ from model.chunk import Chunk from model.chem import Atom from utilities.constants import gensym numLayers = len(self.bondLayers) if numLayers: allBonds = {} allCarbons = {} #Copy the bonds, carbons and hedron from the first layer for ii in range(numLayers): if self.bondLayers.has_key(ii): for bKey, bValue in self.bondLayers[ii].items(): allBonds[bKey] = bValue del self.bondLayers[ii] break for carbons in self.carbonPosDict.values(): for cKey, cValue in carbons.items(): allCarbons[cKey] = cValue for hedrons in self.hedroPosDict.values(): for hKey, hValue in hedrons.items(): allCarbons[hKey] = hValue for bonds in self.bondLayers.values(): for bKey, bValues in bonds.items(): if bKey in allBonds: existValues = allBonds[bKey] for bValue in bValues: if type(bValue) == type((1, 1)): if bValue[1]: ctValue = (bValue[0], 0) else: ctValue = (bValue[0], 1) if ctValue in existValues: idex = existValues.index(ctValue) existValues[idex] = bValue[0] else: existValues += [bValue] else: existValues += [bValue] allBonds[bKey] = existValues else: allBonds[bKey] = bValues #print "allbonds: ", allBonds #print "allCarbons: ", allCarbons carbonAtoms = {} mol = Chunk(assy, gensym("Crystal", assy)) for bKey, bBonds in allBonds.items(): keyHedron = True if len(bBonds): for bond in bBonds: if keyHedron: if type(bBonds[0]) == type(1) or (not bBonds[0][1]): if not bKey in carbonAtoms: keyAtom = Atom("C", allCarbons[bKey], mol) carbonAtoms[bKey] = keyAtom else: keyAtom = carbonAtoms[bKey] keyHedron = False if keyHedron: if type(bond) != type((1, 1)): raise ValueError, (bKey, bond, bBonds) else: xp = (allCarbons[bKey] + allCarbons[bond[0]])/2.0 keyAtom = Atom("X", xp, mol) if type(bond) == type(1) or bond[1]: if type(bond) == type(1): bvKey = bond else: bvKey = bond[0] if not bvKey in carbonAtoms: bondAtom = Atom("C", allCarbons[bvKey], mol) carbonAtoms[bvKey] = bondAtom else: bondAtom = carbonAtoms[bvKey] else: xp = (allCarbons[bKey] + allCarbons[bond[0]])/2.0 bondAtom = Atom("X", xp, mol) bond_atoms(keyAtom, bondAtom) if len(mol.atoms) > 0: #bruce 050222 comment: much of this is not needed, since mol.pick() does it. # Note: this method is similar to one in BuildCrystal_Command.py. assy.addmol(mol) assy.unpickall_in_GLPane() # was unpickparts; not sure _in_GLPane is best (or that # this is needed at all) [bruce 060721] mol.pick() assy.mt.mt_update() return # from buildChunk
def _modifyStructure(self, params): """ Modify the structure based on the parameters specified. Overrides EditCommand._modifystructure. This method removes the old structure and creates a new one using self._createStructure. This was needed for the structures like this (Dna, Nanotube etc) . . See more comments in the method. """ #It could happen that the self.struct is killed before this method #is called. For example: Enter Edit Dna strand, select the strand, hit #delete and then hit Done to exit strand edit. Whenever you hit Done, #modify structure gets called (if old params don't match new ones) #so it needs to return safely if the structure was not valid due #to some previous operation if not self.hasValidStructure(): return # parameters have changed, update existing structure self._revertNumber() # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name self.dna = B_Dna_PAM3_SingleStrand_Generator() numberOfBases, \ dnaForm, \ dnaModel, \ color_junk, \ name_junk = params #see a note about color_junk in DnaSegment_EditCommand._modifyStructure() numberOfBasesToAddOrRemove = self._determine_numberOfBases_to_change() if numberOfBasesToAddOrRemove != 0: resizeEndStrandAtom, resizeEndAxisAtom = \ self.get_strand_and_axis_endAtoms_at_resize_end() if resizeEndAxisAtom: dnaSegment = resizeEndAxisAtom.molecule.parent_node_of_class( self.assy.DnaSegment) if dnaSegment: #A DnaStrand can have multiple DNA Segments with different #basesPerTurn and duplexRise so make sure that while #resizing the strand, use the dna segment of the #resizeEndAxisAtom. Fixes bug 2922 - Ninad 2008-08-04 basesPerTurn = dnaSegment.getBasesPerTurn() duplexRise = dnaSegment.getDuplexRise() resizeEnd_final_position = self._get_resizeEnd_final_position( resizeEndAxisAtom, abs(numberOfBasesToAddOrRemove), duplexRise ) self.dna.modify(dnaSegment, resizeEndAxisAtom, numberOfBasesToAddOrRemove, basesPerTurn, duplexRise, resizeEndAxisAtom.posn(), resizeEnd_final_position, resizeEndStrandAtom = resizeEndStrandAtom ) return
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
def _createSegment(self): """ Creates and returns the structure (in this case a L{Group} object that contains the DNA strand and axis chunks. @return : group containing that contains the DNA strand and axis chunks. @rtype: L{Group} @note: This needs to return a DNA object once that model is implemented """ params = self._gatherParameters() # No error checking in build_struct, do all your error # checking in gather_parameters numberOfBases, \ dnaForm, \ dnaModel, \ basesPerTurn, \ duplexRise, \ endPoint1, \ endPoint2 = params if numberOfBases < 2: #Don't create a duplex with only one or 0 bases! #Reset a few variables. This should be done by calling a separate #method on command (there is similar code in self.createStructures) #as well. self.mouseClickPoints = [] self.graphicsMode.resetVariables() msg = redmsg("Cannot preview/insert a DNA duplex with less than 2 base pairs.") self.propMgr.updateMessage(msg) self.dna = None # Fixes bug 2530. Mark 2007-09-02 return None else: msg = "Specify two points in the 3D Graphics Area to define the "\ "endpoints of the DNA duplex" self.propMgr.updateMessage(msg) #If user enters the number of basepairs and hits preview i.e. endPoint1 #and endPoint2 are not entered by the user and thus have default value #of V(0, 0, 0), then enter the endPoint1 as V(0, 0, 0) and compute #endPoint2 using the duplex length. #Do not use '==' equality check on vectors! its a bug. Use same_vals # or Veq instead. if Veq(endPoint1 , endPoint2) and Veq(endPoint1, V(0, 0, 0)): endPoint2 = endPoint1 + \ self.win.glpane.right * \ getDuplexLength('B-DNA', numberOfBases) if dnaForm == 'B-DNA': if dnaModel == 'PAM3': dna = B_Dna_PAM3() elif dnaModel == 'PAM5': dna = B_Dna_PAM5() else: print "bug: unknown dnaModel type: ", dnaModel else: raise PluginBug("Unsupported DNA Form: " + dnaForm) self.dna = dna # needed for done msg # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name # Create the model tree group node. # Make sure that the 'topnode' of this part is a Group (under which the # DNa group will be placed), if the topnode is not a group, make it a # a 'Group' (applicable to Clipboard parts).See part.py # --Part.ensure_toplevel_group method. This is an important line # and it fixes bug 2585 self.win.assy.part.ensure_toplevel_group() if self._parentDnaGroup is None: print_compact_stack("bug: Parent DnaGroup in DnaDuplex_EditCommand"\ "is None. This means the previous command "\ "was not 'BuildDna_EditCommand' Ignoring for now") if self._fallbackDnaGroup is None: self._createFallbackDnaGroup() dnaGroup = self._fallbackDnaGroup else: dnaGroup = self._parentDnaGroup dnaSegment = DnaSegment(self.name, self.win.assy, dnaGroup, editCommand = self ) try: # Make the DNA duplex. <dnaGroup> will contain three chunks: # - Strand1 # - Strand2 # - Axis dna.make(dnaSegment, numberOfBases, basesPerTurn, duplexRise, endPoint1, endPoint2) #set some properties such as duplexRise and number of bases per turn #This information will be stored on the DnaSegment object so that #it can be retrieved while editing this object. #This works with or without dna_updater. Now the question is #should these props be assigned to the DnaSegment in #dnaDuplex.make() itself ? This needs to be answered while modifying #make() method to fit in the dna data model. --Ninad 2008-03-05 #WARNING 2008-03-05: Since self._modifyStructure calls #self._createStructure() (which in turn calls self._createSegment() #in this case) If in the near future, we actually permit modifying a #structure (such as dna) without actually recreating the whole #structre, then the following properties must be set in #self._modifyStructure as well. Needs more thought. props = (duplexRise, basesPerTurn) dnaSegment.setProps(props) return dnaSegment except (PluginBug, UserError): # Why do we need UserError here? Mark 2007-08-28 dnaSegment.kill_with_contents() raise PluginBug("Internal error while trying to create DNA duplex.")
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?
def makeStrandChunkFromBrokenStrand(self, x1, x2): # by Mark """ Makes a new strand chunk using the two singlets just created by busting the original strand, which is now broken. If the original strand was a ring, no new chunk is created. The new strand chunk, which includes the atoms between the 3' end of the original strand and the new 5' end (i.e. the break point), is added to the same DNA group as the original strand and assigned a different color. @param x1: The first of two singlets created by busting a strand backbone bond. It is either the 3' or 5' open bond singlet, but we don't know yet. @type x1: L{Atom} @param x2: The second of two singlets created by busting a backbone backbone bond. It is either the 3' or 5' open bond singlet, but we don't know yet. @type x2: L{Atom} @return: The new strand chunk. Returns B{None} if no new strand chunk is created, as is the case of a ring. @rtype: L{Chunk} """ minimize = debug_pref( "Adjust broken strand bondpoints using minimizer?", #bruce 080415 revised text (to not use the developer- # jargon-only term "singlet"), changed prefs_key, # and removed non_debug = True, for .rc2 release, # since the repositioning bug this worked around # is now fixed. Choice_boolean_False, prefs_key=True, ) _five_prime_atom = None _three_prime_atom = None for singlet in (x1, x2): adjustSinglet(singlet, minimize=minimize) open_bond = singlet.bonds[0] if open_bond.isFivePrimeOpenBond(): _five_prime_atom = open_bond.other(singlet) else: _three_prime_atom = open_bond.other(singlet) # Make sure we have exactly one 3' and one 5' singlet. # If not, there is probably a direction error on the open bond(s) # that x1 and/or x2 are members of. if not _five_prime_atom: print_compact_stack("No 5' bondpoint.") return None if not _three_prime_atom: print_compact_stack("No 3' bondpoint.") return None atomList = self.o.assy.getConnectedAtoms([_five_prime_atom]) if _three_prime_atom in atomList: # The strand was a closed loop strand, so we're done. return None # Since no new chunk was created. # See self.ensure_toplevel_group() docstring for explanation. self.ensure_toplevel_group() _group_five_prime_was_in = _five_prime_atom.molecule.dad if env.prefs[assignColorToBrokenDnaStrands_prefs_key]: _new_strand_color = getNextStrandColor( _five_prime_atom.molecule.color) else: _new_strand_color = _five_prime_atom.molecule.color return self.makeChunkFromAtomList( atomList, group=_group_five_prime_was_in, name=gensym("Strand"), # doesn't need "DnaStrand" or self.assy, # since not normally seen by users # [bruce 080407 comment] color=_new_strand_color)
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
def _createStructure(self): """ Creates and returns the structure (in this case a L{NanotubeSegment} object. @return : Nanotube segment that include the nanotube chunk. @rtype: L{NanotubeSegment} """ # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name # Create the model tree group node. # Make sure that the 'topnode' of this part is a Group (under which the # DNa group will be placed), if the topnode is not a group, make it a # a 'Group' (applicable to Clipboard parts).See part.py # --Part.ensure_toplevel_group method. This is an important line # and it fixes bug 2585 self.win.assy.part.ensure_toplevel_group() ntSegment = NanotubeSegment(self.name, self.win.assy, self.win.assy.part.topnode, editCommand = self ) try: # Make the NanotubeSegment. n, m, type, endings, endPoint1, endPoint2 = self._gatherParameters() from cnt.model.Nanotube import Nanotube self.nanotube = Nanotube() nanotube = self.nanotube nanotube.setChirality(n, m) nanotube.setType(type) nanotube.setEndings(endings) nanotube.setEndPoints(endPoint1, endPoint2) position = V(0.0, 0.0, 0.0) ntChunk = nanotube.build(self.name, self.win.assy, position) nanotube.computeEndPointsFromChunk(ntChunk) ntSegment.addchild(ntChunk) #set some properties such as nanotubeRise #This information will be stored on the NanotubeSegment object so that #it can be retrieved while editing this object. #Should these props be assigned to the NanotubeSegment in #Nanotube.build() itself? This needs to be answered while modifying #build() method to fit in the dna data model. --Ninad 2008-03-05 #WARNING 2008-03-05: Since self._modifyStructure calls #self._createStructure() #If in the near future, we actually permit modifying a #structure (such as dna) without actually recreating the whole #structure, then the following properties must be set in #self._modifyStructure as well. Needs more thought. props =(nanotube.getChirality(), nanotube.getType(), nanotube.getEndings(), nanotube.getEndPoints()) ntSegment.setProps(props) return ntSegment except (PluginBug, UserError): # Why do we need UserError here? Mark 2007-08-28 ntSegment.kill() raise PluginBug("Internal error while trying to create a NanotubeSegment.") return
def _modifyStructure(self, params): """ Modify the structure based on the parameters specified. Overrides EditCommand._modifystructure. This method removes the old structure and creates a new one using self._createStructure. This was needed for the structures like this (Dna, Nanotube etc) . . See more comments in the method. """ #It could happen that the self.struct is killed before this method #is called. For example: Enter Edit Dna strand, select the strand, hit #delete and then hit Done to exit strand edit. Whenever you hit Done, #modify structure gets called (if old params don't match new ones) #so it needs to return safely if the structure was not valid due #to some previous operation if not self.hasValidStructure(): return # parameters have changed, update existing structure self._revertNumber() # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name self.dna = B_Dna_PAM3_SingleStrand_Generator() numberOfBases, \ dnaForm, \ dnaModel, \ color_junk, \ name_junk = params #see a note about color_junk in DnaSegment_EditCommand._modifyStructure() numberOfBasesToAddOrRemove = self._determine_numberOfBases_to_change() if numberOfBasesToAddOrRemove != 0: resizeEndStrandAtom, resizeEndAxisAtom = \ self.get_strand_and_axis_endAtoms_at_resize_end() if resizeEndAxisAtom: dnaSegment = resizeEndAxisAtom.molecule.parent_node_of_class( self.assy.DnaSegment) if dnaSegment: #A DnaStrand can have multiple DNA Segments with different #basesPerTurn and duplexRise so make sure that while #resizing the strand, use the dna segment of the #resizeEndAxisAtom. Fixes bug 2922 - Ninad 2008-08-04 basesPerTurn = dnaSegment.getBasesPerTurn() duplexRise = dnaSegment.getDuplexRise() resizeEnd_final_position = self._get_resizeEnd_final_position( resizeEndAxisAtom, abs(numberOfBasesToAddOrRemove), duplexRise) self.dna.modify(dnaSegment, resizeEndAxisAtom, numberOfBasesToAddOrRemove, basesPerTurn, duplexRise, resizeEndAxisAtom.posn(), resizeEnd_final_position, resizeEndStrandAtom=resizeEndStrandAtom) 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.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 _createStructure(self): """ Creates and returns the structure (in this case a L{Group} object that contains the DNA strand and axis chunks. @return : group containing that contains the DNA strand and axis chunks. @rtype: L{Group} @note: This needs to return a DNA object once that model is implemented """ params = self._gatherParameters() # No error checking in build_struct, do all your error # checking in gather_parameters number_of_basePairs_from_struct, numberOfBases, dnaForm, dnaModel, basesPerTurn, duplexRise, endPoint1, endPoint2, color_junk = ( params ) # Note: color_junk is not used. Ideally it should do struct.setColor(color) # but the color combobox in the PM directly sets the color of the # structure to the specified one when the current index in the combobx # changes # If user enters the number of basepairs and hits preview i.e. endPoint1 # and endPoint2 are not entered by the user and thus have default value # of V(0, 0, 0), then enter the endPoint1 as V(0, 0, 0) and compute # endPoint2 using the duplex length. # Do not use '==' equality check on vectors! its a bug. Use same_vals # or Veq instead. if Veq(endPoint1, endPoint2) and Veq(endPoint1, V(0, 0, 0)): endPoint2 = endPoint1 + self.win.glpane.right * getDuplexLength("B-DNA", numberOfBases) if numberOfBases < 1: msg = redmsg("Cannot preview/insert a DNA duplex with 0 bases.") self.propMgr.updateMessage(msg) self.dna = None # Fixes bug 2530. Mark 2007-09-02 return None if dnaForm == "B-DNA": if dnaModel == "PAM3": dna = B_Dna_PAM3_Generator() elif dnaModel == "PAM5": dna = B_Dna_PAM5_Generator() else: print "bug: unknown dnaModel type: ", dnaModel else: raise PluginBug("Unsupported DNA Form: " + dnaForm) self.dna = dna # needed for done msg # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name # Create the model tree group node. # Make sure that the 'topnode' of this part is a Group (under which the # DNa group will be placed), if the topnode is not a group, make it a # a 'Group' (applicable to Clipboard parts).See part.py # --Part.ensure_toplevel_group method. This is an important line # and it fixes bug 2585 self.win.assy.part.ensure_toplevel_group() dnaSegment = DnaSegment(self.name, self.win.assy, self.win.assy.part.topnode, editCommand=self) try: # Make the DNA duplex. <dnaGroup> will contain three chunks: # - Strand1 # - Strand2 # - Axis dna.make(dnaSegment, numberOfBases, basesPerTurn, duplexRise, endPoint1, endPoint2) # set some properties such as duplexRise and number of bases per turn # This information will be stored on the DnaSegment object so that # it can be retrieved while editing this object. # This works with or without dna_updater. Now the question is # should these props be assigned to the DnaSegment in # dnaDuplex.make() itself ? This needs to be answered while modifying # make() method to fit in the dna data model. --Ninad 2008-03-05 # WARNING 2008-03-05: Since self._modifyStructure calls # self._createStructure() # If in the near future, we actually permit modifying a # structure (such as dna) without actually recreating the whole # structre, then the following properties must be set in # self._modifyStructure as well. Needs more thought. props = (duplexRise, basesPerTurn) dnaSegment.setProps(props) return dnaSegment except (PluginBug, UserError): # Why do we need UserError here? Mark 2007-08-28 dnaSegment.kill() raise PluginBug("Internal error while trying to create DNA duplex.")
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?
def _createStructure(self): """ Creates and returns the structure (in this case a L{Group} object that contains the nanotube chunk. @return : group containing the nanotube chunk. @rtype: L{Group} @note: This needs to return a CNT object once that model is implemented """ # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name # Create the model tree group node. # Make sure that the 'topnode' of this part is a Group (under which the # Nanotube group will be placed), if the topnode is not a group, make it # a 'Group' (applicable to Clipboard parts). See part.py # --Part.ensure_toplevel_group method. This is an important line # and it fixes bug 2585 self.win.assy.part.ensure_toplevel_group() ntSegment = NanotubeSegment(self.name, self.win.assy, self.win.assy.part.topnode, editCommand=self) try: # Make the nanotube. <ntGroup> will contain one chunk: # - Axis (Segment) # No error checking here; do all error checking in _gatherParameters(). nanotube = self._gatherParameters() position = V(0.0, 0.0, 0.0) self.nanotube = nanotube # needed for done msg #@ ntChunk = nanotube.build(self.name, self.win.assy, position) ntSegment.addchild(ntChunk) #set some properties such as ntRise and its two endpoints. #This information will be stored on the NanotubeSegment object so that #it can be retrieved while editing this object. #WARNING 2008-03-05: Since self._modifyStructure calls #self._createStructure() If in the near future, we actually permit #modifying a #structure (such as a nanotube) without actually recreating the #entire structure, then the following properties must be set in #self._modifyStructure as well. Needs more thought. #props =(nanotube.getChirality(), # nanotube.getType(), # nanotube.getEndings(), # nanotube.getEndPoints()) ntSegment.setProps(nanotube.getParameters()) return ntSegment except (PluginBug, UserError): # Why do we need UserError here? Mark 2007-08-28 self._segmentList.remove(ntSegment) ntSegment.kill_with_contents() raise PluginBug("Internal error while trying to create Nanotube.")
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 makeStrandChunkFromBrokenStrand(self, x1, x2): # by Mark """ Makes a new strand chunk using the two singlets just created by busting the original strand, which is now broken. If the original strand was a ring, no new chunk is created. The new strand chunk, which includes the atoms between the 3' end of the original strand and the new 5' end (i.e. the break point), is added to the same DNA group as the original strand and assigned a different color. @param x1: The first of two singlets created by busting a strand backbone bond. It is either the 3' or 5' open bond singlet, but we don't know yet. @type x1: L{Atom} @param x2: The second of two singlets created by busting a backbone backbone bond. It is either the 3' or 5' open bond singlet, but we don't know yet. @type x2: L{Atom} @return: The new strand chunk. Returns B{None} if no new strand chunk is created, as is the case of a ring. @rtype: L{Chunk} """ minimize = debug_pref("Adjust broken strand bondpoints using minimizer?", #bruce 080415 revised text (to not use the developer- # jargon-only term "singlet"), changed prefs_key, # and removed non_debug = True, for .rc2 release, # since the repositioning bug this worked around # is now fixed. Choice_boolean_False, prefs_key = True, ) _five_prime_atom = None _three_prime_atom = None for singlet in (x1, x2): adjustSinglet(singlet, minimize = minimize) open_bond = singlet.bonds[0] if open_bond.isFivePrimeOpenBond(): _five_prime_atom = open_bond.other(singlet) else: _three_prime_atom = open_bond.other(singlet) # Make sure we have exactly one 3' and one 5' singlet. # If not, there is probably a direction error on the open bond(s) # that x1 and/or x2 are members of. if not _five_prime_atom: print_compact_stack("No 5' bondpoint.") return None if not _three_prime_atom: print_compact_stack("No 3' bondpoint.") return None atomList = self.o.assy.getConnectedAtoms([_five_prime_atom]) if _three_prime_atom in atomList: # The strand was a closed loop strand, so we're done. return None # Since no new chunk was created. # See self.ensure_toplevel_group() docstring for explanation. self.ensure_toplevel_group() _group_five_prime_was_in = _five_prime_atom.molecule.dad if env.prefs[assignColorToBrokenDnaStrands_prefs_key]: _new_strand_color = getNextStrandColor(_five_prime_atom.molecule.color) else: _new_strand_color = _five_prime_atom.molecule.color return self.makeChunkFromAtomList(atomList, group = _group_five_prime_was_in, name = gensym("Strand"), # doesn't need "DnaStrand" or self.assy, # since not normally seen by users # [bruce 080407 comment] color = _new_strand_color)
def _createStructure(self): """ Creates and returns the structure (in this case a L{Group} object that contains the DNA strand and axis chunks. @return : group containing that contains the DNA strand and axis chunks. @rtype: L{Group} @note: This needs to return a DNA object once that model is implemented """ params = self._gatherParameters() # No error checking in build_struct, do all your error # checking in gather_parameters number_of_basePairs_from_struct,\ numberOfBases, \ dnaForm, \ dnaModel, \ basesPerTurn, \ duplexRise, \ endPoint1, \ endPoint2, \ color_junk = params #Note: color_junk is not used. Ideally it should do struct.setColor(color) #but the color combobox in the PM directly sets the color of the #structure to the specified one when the current index in the combobx #changes #If user enters the number of basepairs and hits preview i.e. endPoint1 #and endPoint2 are not entered by the user and thus have default value #of V(0, 0, 0), then enter the endPoint1 as V(0, 0, 0) and compute #endPoint2 using the duplex length. #Do not use '==' equality check on vectors! its a bug. Use same_vals # or Veq instead. if Veq(endPoint1, endPoint2) and Veq(endPoint1, V(0, 0, 0)): endPoint2 = endPoint1 + \ self.win.glpane.right*getDuplexLength('B-DNA', numberOfBases) if numberOfBases < 1: msg = redmsg("Cannot preview/insert a DNA duplex with 0 bases.") self.propMgr.updateMessage(msg) self.dna = None # Fixes bug 2530. Mark 2007-09-02 return None if dnaForm == 'B-DNA': if dnaModel == 'PAM3': dna = B_Dna_PAM3() elif dnaModel == 'PAM5': dna = B_Dna_PAM5() else: print "bug: unknown dnaModel type: ", dnaModel else: raise PluginBug("Unsupported DNA Form: " + dnaForm) self.dna = dna # needed for done msg # self.name needed for done message if self.create_name_from_prefix: # create a new name name = self.name = gensym(self.prefix, self.win.assy) # (in _build_struct) self._gensym_data_for_reusing_name = (self.prefix, name) else: # use externally created name self._gensym_data_for_reusing_name = None # (can't reuse name in this case -- not sure what prefix it was # made with) name = self.name # Create the model tree group node. # Make sure that the 'topnode' of this part is a Group (under which the # DNa group will be placed), if the topnode is not a group, make it a # a 'Group' (applicable to Clipboard parts).See part.py # --Part.ensure_toplevel_group method. This is an important line # and it fixes bug 2585 self.win.assy.part.ensure_toplevel_group() dnaSegment = DnaSegment(self.name, self.win.assy, self.win.assy.part.topnode, editCommand=self) try: # Make the DNA duplex. <dnaGroup> will contain three chunks: # - Strand1 # - Strand2 # - Axis dna.make(dnaSegment, numberOfBases, basesPerTurn, duplexRise, endPoint1, endPoint2) #set some properties such as duplexRise and number of bases per turn #This information will be stored on the DnaSegment object so that #it can be retrieved while editing this object. #This works with or without dna_updater. Now the question is #should these props be assigned to the DnaSegment in #dnaDuplex.make() itself ? This needs to be answered while modifying #make() method to fit in the dna data model. --Ninad 2008-03-05 #WARNING 2008-03-05: Since self._modifyStructure calls #self._createStructure() #If in the near future, we actually permit modifying a #structure (such as dna) without actually recreating the whole #structre, then the following properties must be set in #self._modifyStructure as well. Needs more thought. props = (duplexRise, basesPerTurn) dnaSegment.setProps(props) return dnaSegment except (PluginBug, UserError): # Why do we need UserError here? Mark 2007-08-28 dnaSegment.kill() raise PluginBug( "Internal error while trying to create DNA duplex.")