def init_gui(self): if self.propMgr is None: self.propMgr = FusePropertyManager(self) #@bug BUG: following is a workaround for bug 2494 changes.keep_forever(self.propMgr) self.propMgr.show() self.change_fuse_mode(str(self.propMgr.fuseComboBox.currentText())) # This maintains state of fuse mode when leaving/reentering mode, # and syncs the PM and glpane (and does a gl_update). self.w.toolsFuseChunksAction.setChecked(1) self.updateCommandToolbar(bool_entering = True) # connect signals #(these all need to be disconnected in restore_gui) [mark 050901] self.connect_or_disconnect_signals(True) if self.o.assy.selmols: self.graphicsMode.something_was_picked = True
class FuseChunks_Command(Move_Command, fusechunksBase): """ Allows user to move chunks and fuse them to other chunks in the part. Two fuse methods are supported: 1. Make Bonds - bondpoints between chunks will form bonds when they are near each other. 2. Fuse Atoms - atoms between chunks will be fused when they overlap each other. """ # class constants commandName = 'FUSECHUNKS' default_mode_status_text = "Mode: Fuse Chunks" featurename = "Fuse Chunks Mode" bondable_pairs = [] # List of bondable singlets ways_of_bonding = {} # Number of bonds each singlet found bondable_pairs_atoms = [] # List of atom pairs that can be bonded overlapping_atoms = [] # List of overlapping atoms tol = 1.0 # in Angstroms # For "Make Bonds", tol is the distance between two bondable singlets # For "Fuse Atoms", tol is the distance between two atoms to be # considered overlapping fuse_mode = '' # The Fuse mode, either 'Make Bonds' or 'Fuse Atoms'. propMgr = None GraphicsMode_class = FuseChunks_GraphicsMode def _create_GraphicsMode(self): GM_class = self.GraphicsMode_class assert issubclass(GM_class, GraphicsMode_API) args = [self] kws = {} self.graphicsMode = GM_class(*args, **kws) self.translate_graphicsMode = Translate_in_FuseChunks_GraphicsMode(*args, **kws) self.rotate_graphicsMode = Rotate_in_FuseChunks_GraphicsMode(*args, **kws) def init_gui(self): if self.propMgr is None: self.propMgr = FusePropertyManager(self) #@bug BUG: following is a workaround for bug 2494 changes.keep_forever(self.propMgr) self.propMgr.show() self.change_fuse_mode(str(self.propMgr.fuseComboBox.currentText())) # This maintains state of fuse mode when leaving/reentering mode, # and syncs the PM and glpane (and does a gl_update). self.w.toolsFuseChunksAction.setChecked(1) self.updateCommandToolbar(bool_entering = True) # connect signals #(these all need to be disconnected in restore_gui) [mark 050901] self.connect_or_disconnect_signals(True) if self.o.assy.selmols: self.graphicsMode.something_was_picked = True #if self.propMgr.isTranslateGroupBoxActive: ## toggle on the Move Free action in the Property Manager #self.propMgr.transFreeButton.setChecked(True) #self.graphicsMode.moveOption = 'MOVEDEFAULT' #else: #self.propMgr.rotateFreeButton.setChecked(True) #self.graphicsrotateOption = 'ROTATEDEFAULT' def restore_gui(self): self.updateCommandToolbar(bool_entering = False) self.connect_or_disconnect_signals(False) self.w.toolsFuseChunksAction.setChecked(False) self.propMgr.close() def connect_or_disconnect_signals(self, connect): if connect: change_connect = self.w.connect else: change_connect = self.w.disconnect change_connect(self.exitFuseAction, SIGNAL("triggered()"), self.w.toolsDone) #Connect or disconnect signals in property manager self.propMgr.connect_or_disconnect_signals(connect) return def Backup(self): """ Undo any bonds made between chunks. """ # This undoes only the last fused chunks. Will work on supporting # multiple undos when we get a single undo working. Mark 050326 # Bust bonds between last pair/set of fused chunks. if self.bondable_pairs_atoms: for a1, a2 in self.bondable_pairs_atoms: b = a1.get_neighbor_bond(a2) if b: b.bust() if self.merged_chunks: nchunks_str = "%d" % (len(self.merged_chunks) + 1,) msg = "Fuse Chunks: Bonds broken between %s chunks." % (nchunks_str) env.history.message(msg) msg = "Warning: Cannot separate the original chunks. You can " \ "do this yourself using <b>Modify > Separate</b>." env.history.message(orangemsg(msg)) cnames = "Their names were: " # Here are the original names... for chunk in self.merged_chunks: cnames += '[' + chunk.name + '] ' env.history.message(cnames) self.o.gl_update() else: msg = "Fuse Chunks: No bonds have been made yet. Undo ignored." env.history.message(redmsg(msg)) def tolerance_changed(self, val): """ Slot for tolerance slider. """ self.tol = val * .01 if self.o.assy.selmols: self.o.gl_update() else: # Since no chunks are selected, there are no bonds, but the slider # tolerance label still needs # updating. This fixed bug 502-14. Mark 050407 self.reset_tolerance_label() def reset_tolerance_label(self): """ Reset the tolerance label to 0 bonds or 0 overlapping atoms """ if self.fuse_mode == MAKEBONDS: tol_str = fusechunks_lambda_tol_nbonds(self.tol, 0, 0, 0) # 0 bonds else: # 0 overlapping atoms tol_str = fusechunks_lambda_tol_natoms(self.tol, 0) tolerenceLabel = tol_str self.propMgr.toleranceSlider.labelWidget.setText(tolerenceLabel) def change_fuse_mode(self, fuse_mode): """ Sets the Fuse mode """ if self.fuse_mode == fuse_mode: return # The mode did not change. Don't do anything. self.fuse_mode = str(fuse_mode) # Must cast here. if self.fuse_mode == MAKEBONDS: self.propMgr.fusePushButton.setText('Make Bonds') else: self.propMgr.fusePushButton.setText('Fuse Atoms') self.propMgr.updateMessage() self.o.gl_update() # the Draw() method will update based on the current #combo box item. def find_fusables(self): """ Finds bondable pairs or overlapping atoms, based on the Fuse Action combo box """ if self.fuse_mode == MAKEBONDS: self.find_bondable_pairs() else: self.find_overlapping_atoms() def find_bondable_pairs(self, chunk_list = None): """ Checks the bondpoints of the selected chunk to see if they are close enough to bond with any other bondpoints in a list of chunks. Hidden chunks are skipped. """ tol_str = fusechunksBase.find_bondable_pairs(self, chunk_list, None) tolerenceLabel = tol_str self.propMgr.toleranceSlider.labelWidget.setText(tolerenceLabel) def fuse_something(self): """ Slot for 'Make Bonds/Fuse Atoms' button. """ if self.fuse_mode == MAKEBONDS: self.make_bonds() else: self.fuse_atoms() def make_bonds(self): """ Make bonds between all bondable pairs of singlets """ self._make_bonds_1() self._make_bonds_2() self._make_bonds_3() self._make_bonds_4() def _make_bonds_2(self): # Merge the chunks if the "merge chunks" checkbox is checked if self.propMgr.mergeChunksCheckBox.isChecked() and self.bondable_pairs_atoms: for a1, a2 in self.bondable_pairs_atoms: # Ignore a1, they are atoms from the selected chunk(s) # It is possible that a2 is an atom from a selected chunk, # so check it if a2.molecule != a1.molecule: if a2.molecule not in self.merged_chunks: self.merged_chunks.append(a2.molecule) a1.molecule.merge(a2.molecule) def _make_bonds_4(self): msg = fix_plurals( "%d bond(s) made" % self.total_bonds_made) env.history.message(msg) # Update the slider tolerance label. This fixed bug 502-14. Mark 050407 self.reset_tolerance_label() if self.bondable_pairs_atoms: # This must be done before gl_update, or it will try to draw the # bondable singlets again, which generates errors. self.bondable_pairs = [] self.ways_of_bonding = {} self.w.win_update() ######### Overlapping Atoms methods ############# def find_overlapping_atoms(self): """ Checks atoms of the selected chunk to see if they overlap atoms in other chunks of the same type (element). Hidden chunks are skipped. """ # Future versions should allow more flexible rules for overlapping # atoms, but this needs to be discussed with others before implementing # anything. # For now, only atoms of the same type qualify as overlapping atoms. # As is, it is extremely useful for fusing chunks of diamond, # lonsdaleite or SiC, # which is done quite a lot with casings. This will save me hours of # modeling work. # Mark 050902 self.overlapping_atoms = [] for chunk in self.o.assy.selmols: if chunk.hidden or chunk.display == diINVISIBLE: # Skip selected chunk if hidden or invisible. # Fixes bug 970. mark 060404 continue # Loop through all the mols in the part to search for bondable # pairs of singlets. for mol in self.o.assy.molecules: if chunk is mol: continue # Skip itself if mol.hidden or mol.display == diINVISIBLE: continue # Skip hidden or invisible chunks if mol in self.o.assy.selmols: continue # Skip other selected chunks # Skip this mol if its bounding box does not overlap the # selected chunk's bbox. # Remember: chunk = a selected chunk, mol = a non-selected # chunk. if not chunk.overlapping_chunk(mol, self.tol): # print "Skipping ", mol.name continue else: # Loop through all the atoms in the selected chunk. # Use values() if the loop ever modifies chunk or mol-- for a1 in chunk.atoms.itervalues(): # Singlets can't be overlapping atoms -- if a1.element is Singlet: continue # We can skip mol if the atom lies outside its bbox. if not mol.overlapping_atom(a1, self.tol): continue # Loop through all the atoms in this chunk. for a2 in mol.atoms.itervalues(): # Only atoms of the same type can be overlapping. # This also screens singlets, since a1 can't be a # singlet. if a1.element is not a2.element: continue # Compares the distance between a1 and a2. # If the distance # is <= tol, then we have an overlapping atom. # I know this isn't a proper use of tol, # but it works for now. Mark 050901 if vlen (a1.posn() - a2.posn()) <= self.tol: # Add this pair to the list-- self.overlapping_atoms.append( (a1,a2) ) # No need to check other atoms in this chunk-- break # Update tolerance label and status bar msgs. natoms = len(self.overlapping_atoms) tol_str = fusechunks_lambda_tol_natoms(self.tol, natoms) tolerenceLabel = tol_str self.propMgr.toleranceSlider.labelWidget.setText(tolerenceLabel) def fuse_atoms(self): """ Deletes overlapping atoms found with the selected chunk(s). Only the overlapping atoms from the unselected chunk(s) are deleted. If the "Merge Chunks" checkbox is checked, then find_bondable_pairs() and make_bonds() is called, resulting in the merging of chunks. """ total_atoms_fused = 0 # The total number of atoms fused. # fused_chunks stores the list of chunks that contain overlapping atoms # (but no selected chunks, though) fused_chunks = [] # Delete overlapping atoms. for a1, a2 in self.overlapping_atoms: if a2.molecule not in fused_chunks: fused_chunks.append(a2.molecule) a2.kill() # print "Fused chunks list:", fused_chunks # Merge the chunks if the "merge chunks" checkbox is checked if self.propMgr.mergeChunksCheckBox.isChecked() and self.overlapping_atoms: # This will bond and merge the selected chunks only with # chunks that had overlapping atoms. #& This has bugs when the bonds don't line up nicely between # overlapping atoms in the selected chunk #& and the bondpoints of the deleted atoms' neighbors. # Needs a bug report. mark 060406. self.find_bondable_pairs(fused_chunks) self.make_bonds() # Print history msgs to inform the user what happened. total_atoms_fused = len(self.overlapping_atoms) msg = fix_plurals( "%d atom(s) fused with %d chunk(s)" % (total_atoms_fused, len(fused_chunks))) env.history.message(msg) #"%s => %s overlapping atoms" % (tol_str, natoms_str) # Update the slider tolerance label. self.reset_tolerance_label() self.overlapping_atoms = [] # This must be done before win_update(), or it will try to draw the # overlapping atoms again, which generates errors. self.w.win_update() #Command Toolbar to be revised ============================================ def getFlyoutActionList(self): #Ninad 20070618 """ Returns a tuple that contains mode spcific actionlists in the added in the flyout toolbar of the mode. CommandToolbar._createFlyoutToolBar method calls this @return: params: A tuple that contains 3 lists: (subControlAreaActionList, commandActionLists, allActionsList) """ #'allActionsList' returns all actions in the flyout toolbar #including the subcontrolArea actions allActionsList = [] #Action List for subcontrol Area buttons. #In this mode, there is really no subcontrol area. #We will treat subcontrol area same as 'command area' #(subcontrol area buttons will have an empty list as their command area #list). We will set the Comamnd Area palette background color to the #subcontrol area. subControlAreaActionList =[] self.exitFuseAction = NE1_QWidgetAction(self.w, win = self.w) self.exitFuseAction.setText("Exit Fuse") self.exitFuseAction.setCheckable(True) self.exitFuseAction.setChecked(True) self.exitFuseAction.setIcon( geticon("ui/actions/Toolbars/Smart/Exit.png")) subControlAreaActionList.append(self.exitFuseAction) separator = QAction(self.w) separator.setSeparator(True) subControlAreaActionList.append(separator) allActionsList.extend(subControlAreaActionList) #Empty actionlist for the 'Command Area' commandActionLists = [] #Append empty 'lists' in 'commandActionLists equal to the #number of actions in subControlArea for i in range(len(subControlAreaActionList)): lst = [] commandActionLists.append(lst) params = (subControlAreaActionList, commandActionLists, allActionsList) return params def updateCommandToolbar(self, bool_entering = True):#Ninad 20070618 """ Update the command toolbar """ # object that needs its own flyout toolbar. In this case it is just #the mode itself. action = self.w.toolsFuseChunksAction obj = self self.w.commandToolbar.updateCommandToolbar(action, obj, entering =bool_entering)