# Copyright 2004-2007 Nanorex, Inc. See LICENSE file for details. """ ops_motion.py -- various ways of moving or spatially distorting selected atoms or chunks (and someday, attached jigs). These operations don't create or destroy atoms or bonds. @version: $Id$ @copyright: 2004-2007 Nanorex, Inc. See LICENSE file for details. History: bruce 050507 made this by collecting appropriate methods from class Part. """ from utilities.Log import greenmsg, redmsg from platform_dependent.PlatformDependent import fix_plurals from geometry.VQT import V, norm, Q, vlen, orthodist import foundation.env as env from math import pi from utilities.debug import print_compact_traceback from model.chunk import Chunk from model.jigs import Jig from model.jigs_motors import Motor from analysis.ESP.ESPImage import ESPImage from foundation.Group import Group class ops_motion_Mixin: """ Mixin class for providing these methods to class Part """ ###@@@ move/rot should be extended to apply to jigs too (and fit into some naming convention) def movesel(self, offset): """ move selected chunks and jigs in space """ movables = self.getSelectedMovables() self.translateSpecifiedMovables(offset, movables = movables) def translateSpecifiedMovables(self, offset, movables =()): """ Translate the specified movables. @param movables: a list of movables (default value is empty tuple) @type movables: list """ for m in movables: self.changed() m.move(offset) def rotsel(self, quat): """ Rotate selected chunks/jigs in space. [Huaicai 8/30/05: Fixed the problem of each rotating around its own center, they will now rotate around their common center] """ movables = self.getSelectedMovables() self.rotateSpecifiedMovables(quat, movables = movables) return def rotateSpecifiedMovables(self, quat, movables =(), commonCenter = None): """ Rotate the movables specified in the 'movables' list. (Rotated as a unit) @param quat: Quaternion for the rotation. @param movables: A list of movables. These movables will be rotated around a common axis @type movables: list """ numMovables = len(movables) if commonCenter is None: # Find the common center of all selected chunks to fix bug 594 #--Huaicai 8/30/05 comCenter = V(0.0, 0.0, 0.0) if numMovables: for m in movables: comCenter += m.center comCenter /= numMovables else: comCenter = commonCenter # Move the selected chunks for m in movables: self.changed() #Not sure if this can be combined into one call # Get the moving offset because of the rotation around each # movable's own center rotOff = quat.rot(m.center - comCenter) rotOff = comCenter - m.center + rotOff m.move(rotOff) m.rot(quat) def Stretch(self): """ stretch a Chunk """ mc = env.begin_op("Stretch") try: cmd = greenmsg("Stretch: ") if not self.selmols: msg = redmsg("No selected chunks to stretch") env.history.message(cmd + msg) else: self.changed() for m in self.selmols: m.stretch(1.1) self.o.gl_update() # Added history message. Mark 050413. info = fix_plurals( "Stretched %d chunk(s)" % len(self.selmols)) env.history.message( cmd + info) finally: env.end_op(mc) return def Invert(self): """ Invert the atoms of the selected chunk(s) around the chunk centers """ mc = env.begin_op("Invert") cmd = greenmsg("Invert: ") if not self.selmols: msg = redmsg("No selected chunks to invert") env.history.message(cmd + msg) return self.changed() for m in self.selmols: m.stretch(-1.0) self.o.gl_update() info = fix_plurals( "Inverted %d chunk(s)" % len(self.selmols)) env.history.message( cmd + info) env.end_op(mc) #e try/finally? def Mirror(self): """ Mirror the selected chunk(s) about a selected grid plane. """ cmd = greenmsg("Mirror: ") #ninad060814 this is necessary to fix a bug. Otherwise program will #crash if you try to mirror when the top node of the part #(main part of clipboard) is selected if self.topnode.picked: self.topnode.unpick_top() self.mirrorJigs = self.getQualifiedMirrorJigs() jigCounter = len(self.mirrorJigs) if jigCounter < 1: msg1 = "No mirror plane selected." msg2 = " Please select a Reference Plane or a Grid Plane first." msg = redmsg(msg1+msg2) instr1 = "(If it doesn't exist, create it using" instr2 = "Insert > Reference Geometry menu )" instruction = instr1 + instr2 env.history.message(cmd + msg + instruction) return elif jigCounter >1: msg = redmsg("More than one plane selected. Please select only one plane and try again") env.history.message(cmd + msg ) return for j in self.mirrorJigs: j.unpick() copiedObject = self.o.assy.part.copy_sel_in_same_part() # ninad060812 Get the axis vector of the Grid Plane. Then you need to #rotate the inverted chunk by pi around this axis vector self.mirrorAxis = self.mirrorJigs[0].getaxis() if isinstance(copiedObject, Chunk): copiedObject.name = copiedObject.name + "-Mirror" self._mirrorChunk(copiedObject) return elif isinstance(copiedObject, Group): copiedObject.name = "Mirrored Items" def mirrorChild(obj): if isinstance(obj, Chunk): self._mirrorChunk(obj) elif isinstance(obj, Jig): self._mirrorJig(obj) copiedObject.apply2all(mirrorChild) return def _mirrorChunk(self, chunkToMirror): """ Converts the given chunk into its own mirror. @param chunkToMirror: The chunk that needs to be converted into its own mirror chunk. @type chunkToMirror: instance of class Chunk @see: self.Mirror """ m = chunkToMirror # ninad060813 Following gives an orthogonal distance between the #chunk center and mirror plane. self.mirrorDistance, self.wid = orthodist(m.center, self.mirrorAxis, self.mirrorJigs[0].center) # @@@@ ninad060813 This moves the mirror chunk on the other side of # the mirror plane. It surely moves the chunk along the axis of the # mirror plane but I am still unsure if this *always* moves the # chunk on the other side of the mirror. #Probably the 'orthodist' function has helped me here?? m.move(2*(self.mirrorDistance)*self.mirrorAxis) m.stretch(-1.0) m.rot(Q(self.mirrorAxis, pi)) return def _mirrorJig(self, jigToMirror): """ Converts the given jig into its own mirror. If the jig is a motor, it also reverses its direction. @param jigToMirror: The jig that needs to be converted into its own mirror jig. @type jigToMirror: instance of class Jig @see: self.Mirror """ j = jigToMirror # ninad060813 This gives an orthogonal distance between the chunk # center and mirror plane. #Fixed bug 2503. if not (isinstance(j, Motor) or isinstance(j, ESPImage)): return self.mirrorDistance, self.wid = orthodist(j.center, self.mirrorAxis, self.mirrorJigs[0].center) j.move(2*(self.mirrorDistance)*self.mirrorAxis) j.rot(Q(self.mirrorAxis, pi)) #Reverse the direction of Linear and Rotary motor for correct #mirror operation if isinstance(j, Motor): j.reverse_direction() return def getQualifiedMirrorJigs(self): """ Returns a list of objects that can be used as a reference in Mirror Feature. (referece plane and grid planes are valid objects). Only the first object in this list is used for mirror. See Mirror method for details """ jigs = self.assy.getSelectedJigs() mirrorJigs = [] for j in jigs: if j.mmp_record_name is "gridplane" or j.mmp_record_name is "plane": mirrorJigs.append(j) return mirrorJigs def align_NEW(self): """ Align the axes of the selected movables to the axis of the movable that is placed at the highest order in the Model Tree """ #@@This is not called yet. #method *always* uses the MT order to align chunks or jigs #This supports jigs (including reference planes) but it has following #bug -- It always uses the selected movable that is placed highest #in the Model Tree, as the reference axis for alignment. (Instead #it should align to the 'first selected movable' #(this doesn't happen (or very rarely happens) in old align method where #'selmols' is used.) cmd = greenmsg("Align to Common Axis: ") movables = self.assy.getSelectedMovables() for m in movables: print "movable =", m.name numMovables = len(movables) if len(movables) < 2: msg = redmsg("Need two or more selected chunks to align") env.history.message(cmd + msg) return self.changed() try: firstAxis = movables[0].getaxis() for m in movables[1:]: m.rot(Q(m.getaxis(),firstAxis)) self.o.gl_update() except: print_compact_traceback ("bug: selected movable object doesn't have an \ axis") msg = redmsg("bug: selected movable object doesn't have an axis") env.history.message(cmd + msg) return self.o.gl_update() info = fix_plurals( "Aligned %d item(s)" % (len(movables) - 1) ) \ + " to axis of %s" % movables[0].name env.history.message( cmd + info) return def align(self): """ """ cmd = greenmsg("Align to Common Axis: ") if len(self.selmols) < 2: msg = redmsg("Need two or more selected chunks to align") env.history.message(cmd + msg) return self.changed() #bruce 050131 bugfix or precaution #ax = V(0,0,0) #for m in self.selmols: # ax += m.getaxis() #ax = norm(ax) ax = self.selmols[0].getaxis() # Axis of first selected chunk for m in self.selmols[1:]: m.rot(Q(m.getaxis(),ax)) self.o.gl_update() info = fix_plurals( "Aligned %d chunk(s)" % (len(self.selmols) - 1) ) \ + " to chunk %s" % self.selmols[0].name env.history.message( cmd + info) #Ninad 060904 The following is not called from UI. Need to see if this is useful to the user. def alignPerpendicularToPlane(self): """ Aligns the axes of selected jigs or chunks perpendicular to a reference plane """ cmd = greenmsg("Align to Plane:") referencePlaneList = self.getQualifiedReferencePlanes() jigCounter = len(referencePlaneList) self.changed() if jigCounter: referencePlane = referencePlaneList[0] #ninad060904 If more than 1 ref planes are selected, it selectes the first in the order in the mmp file if jigCounter < 1: msg = redmsg("Please select a plane first.") instruction = " Planes can also be created using the Insert > Plane command." env.history.message(cmd + msg + instruction) return movables = self.assy.getSelectedMovables() numMovables = len(movables) #print len(movables) if numMovables >1: for obj in movables: if obj is referencePlane: pass refAxis = referencePlane.getaxis() obj.rot(Q(obj.getaxis(),refAxis)) self.o.gl_update() else: msg = redmsg("No chunks or movable jigs selected to align perpendicular to the reference plane.") env.history.message(cmd + msg + instruction) return def getQualifiedReferencePlanes(self, jigs): #Ninad 060904 """ Returns a list of jigs that can be used a reference plane in align to plane feature. """ referencePlaneList = [] for j in jigs: if j.mmp_record_name is "gridplane": referencePlaneList += [j] return referencePlaneList def alignmove(self): cmd = greenmsg("Move to Axis: ") if len(self.selmols) < 2: msg = redmsg("Need two or more selected chunks to align") env.history.message(cmd + msg) return self.changed() #ax = V(0,0,0) #for m in self.selmols: # ax += m.getaxis() #ax = norm(ax) ax = self.selmols[0].getaxis() # Axis of first selected chunk ctr = self.selmols[0].center # Center of first selected chunk for m in self.selmols[1:]: m.rot(Q(m.getaxis(),ax)) m.move(ctr-m.center) # offset self.o.gl_update() info = fix_plurals( "Aligned %d chunk(s)" % (len(self.selmols) - 1) ) \ + " to chunk %s" % self.selmols[0].name env.history.message( cmd + info) pass # end of class ops_motion_Mixin # end