def sameAsCurrentView(self, view = None): """ Tests if self is the same as I{view}, or the current view if I{view} is None (the default). @param view: A named view to compare with self. If None (the default), self is compared to the current view (i.e. the 3D graphics area). @type view: L{NamedView} @return: True if they are the same. Otherwise, returns False. @rtype: boolean """ # Note: I'm guessing this could be rewritten to be more # efficient/concise. For example, it seems possible to implement # this using a simple conditional like this: # # if self == view: # return True # else: # return False # # It occurs to me that the GPLane class should use a NamedView attr # along with (or in place of) quat, scale, pov and zoomFactor attrs. # That would make this method (and possibly other code) easier to # write and understand. # # Ask Bruce about all this. # # BTW, this code was originally copied/borrowed from # GLPane.animateToView(). Mark 2008-02-03. # Make copies of self parameters. q1 = Q(self.quat) s1 = self.scale p1 = V(self.pov[0], self.pov[1], self.pov[2]) z1 = self.zoomFactor if view is None: # use the graphics area in which self is displayed # (usually the main 3D graphics area; code in this class # has not been reviewed for working in other GLPane_minimal instances) view = self.assy.glpane # Copy the parameters of view for comparison q2 = Q(view.quat) s2 = view.scale p2 = V(view.pov[0], view.pov[1], view.pov[2]) z2 = view.zoomFactor # Compute the deltas deltaq = q2 - q1 deltap = vlen(p2 - p1) deltas = abs(s2 - s1) deltaz = abs(z2 - z1) if deltaq.angle + deltap + deltas + deltaz == 0: return True else: return False
def setup_quat_center(self, atomList=None): """ Setup the plane's quat using a list of atoms. If no atom list is supplied, the plane is centered in the glpane and parallel to the screen. @param atomList: A list of atoms. @type atomList: list """ if atomList: self.atomPos = [] for a in atomList: self.atomPos += [a.posn()] planeNorm = self._getPlaneOrientation(self.atomPos) if dot(planeNorm, self.glpane.lineOfSight) < 0: planeNorm = -planeNorm self.center = add.reduce(self.atomPos) / len(self.atomPos) self.quat = Q(V(0.0, 0.0, 1.0), planeNorm) else: self.center = V(0.0, 0.0, 0.0) # Following makes sure that Plane edges are parallel to # the 3D workspace borders. Fixes bug 2448 x, y, z = self.glpane.right, self.glpane.up, self.glpane.out self.quat = Q(x, y, z) self.quat += Q(self.glpane.right, pi)
def update(self, px, py, uq = None): """ This should be called in a mouseDrag binding, with window coordinates of the mouse; return value is an incremental quat, to be used in conjunction with uq as explained below. For trackballing the entire model space (whose orientation is stored in (for example) glpane.quat), caller should not pass uq, and should increment glpane.quat by the return value (i.e. glpane.quat += retval). For trackballing an object with orientation obj.quat, drawn subject to (for example) glpane.quat, caller should pass uq = glpane.quat, and should increment obj.quat by the return value. (If caller didn't pass uq in that case, our retval would be suitable for incrementing obj.quat + glpane.quat, or glpane.quat alone, but this is not the same as a retval suitable for incrementing obj.quat alone.) """ #bruce 060514 revised this code (should be equivalent to the prior code), added docstring #ninad 060906 added 'rotation sensitivity to this formula. the rotation sensitivity will be used #while middle drag rotating the model. By default a lower value is set for this and can be adjusted #via a user preference. This helps mitigate bug 1856 newmouse = proj2sphere((px - self.w2) * self.scale * self.mouseSpeedDuringRotation, (self.h2 - py) * self.scale * self.mouseSpeedDuringRotation) if self.oldmouse is not None: quat = Q(self.oldmouse, newmouse) if uq is not None: quat = uq + quat - uq else: print "warning: trackball.update sees oldmouse is None (should not happen)" #bruce 060514 quat = Q(1,0,0,0) self.oldmouse = newmouse return quat
def pvecs_i(self, i, abs_coords=False): """ Use last recomputed geom to get the 2 pvecs for bond i... in the coordsys of the bond, or always abs if flag is passed. WARNING: pvec order matches atom order in lista, maybe not in bond. """ biL_pvec = biR_pvec = self.quats_cum[i].rot( self.b0L_pvec) # not yet twisted axis = self.axes[i] ## if self.twist90 and i % 2 == 1: ## biL_pvec = norm(cross(biL_pvec, axis)) ## biR_pvec = norm(cross(biR_pvec, axis)) twist = self.twist # this is the twist angle (in radians), for each bond; or None or 0 or 0.0 for no twist if twist: # twist that much times i and i+1 for L and R, respectively, around axes[i] theta = twist quatL = Q(axis, theta * (i)) quatR = Q(axis, theta * (i + 1)) biL_pvec = quatL.rot(biL_pvec) biR_pvec = quatR.rot(biR_pvec) if not abs_coords: # put into bond coords (which might be the same as abs coords) bond = self.listb[i] quat = bond.bond_to_abs_coords_quat() biL_pvec = quat.unrot(biL_pvec) biR_pvec = quat.unrot(biR_pvec) return biL_pvec, biR_pvec # warning: these two vectors might be the same object
def rotateAboutPoint(self): """ Rotates the selected entities along the specified vector, about the specified pivot point (pivot point it the starting point of the drawn vector. """ if len(self.mouseClickPoints) != self.mouseClickLimit: print_compact_stack( "Rotate about point bug: mouseclick points != mouseclicklimit: " ) return pivotPoint = self.mouseClickPoints[0] ref_vec_endPoint = self.mouseClickPoints[1] rot_vec_endPoint = self.mouseClickPoints[2] reference_vec = norm(ref_vec_endPoint - pivotPoint) lineVector = norm(rot_vec_endPoint - pivotPoint) #lineVector = endPoint - startPoint quat1 = Q(lineVector, reference_vec) #DEBUG Disabled temporarily . will not be used if dot(lineVector, reference_vec) < 0: theta = math.pi - quat1.angle else: theta = quat1.angle #TEST_DEBUG-- Works fine theta = quat1.angle rot_axis = cross(lineVector, reference_vec) if dot(lineVector, reference_vec) < 0: rot_axis = -rot_axis cross_prod_1 = norm(cross(reference_vec, rot_axis)) cross_prod_2 = norm(cross(lineVector, rot_axis)) if dot(cross_prod_1, cross_prod_2) < 0: quat2 = Q(rot_axis, theta) else: quat2 = Q(rot_axis, -theta) movables = self.graphicsMode.getMovablesForLeftDragging() self.assy.rotateSpecifiedMovables(quat2, movables=movables, commonCenter=pivotPoint) self.glpane.gl_update() return
def rotateAboutPoint(self): """ Rotates the selected entities along the specified vector, about the specified pivot point (pivot point it the starting point of the drawn vector. """ startPoint = self.mouseClickPoints[0] endPoint = self.mouseClickPoints[1] pivotAtom = self.graphicsMode.pivotAtom #initial assignment of reference_vec. The selected movables will be #rotated by the angle between this vector and the lineVector reference_vec = self.glpane.right if isinstance(pivotAtom, Atom) and not pivotAtom.molecule.isNullChunk(): mol = pivotAtom.molecule reference_vec, node_junk = mol.getAxis_of_self_or_eligible_parent_node( atomAtVectorOrigin = pivotAtom) del node_junk else: reference_vec = self.glpane.right lineVector = endPoint - startPoint quat1 = Q(lineVector, reference_vec) #DEBUG Disabled temporarily . will not be used ##if dot(lineVector, reference_vec) < 0: ##theta = math.pi - quat1.angle ##else: ##theta = quat1.angle #TEST_DEBUG-- Works fine theta = quat1.angle rot_axis = cross(lineVector, reference_vec) if dot(lineVector, reference_vec) < 0: rot_axis = - rot_axis cross_prod_1 = norm(cross(reference_vec, rot_axis)) cross_prod_2 = norm(cross(lineVector, rot_axis)) if dot(cross_prod_1, cross_prod_2) < 0: quat2 = Q(rot_axis, theta) else: quat2 = Q(rot_axis, - theta) movables = self.graphicsMode.getMovablesForLeftDragging() self.assy.rotateSpecifiedMovables( quat2, movables = movables, commonCenter = startPoint) self.glpane.gl_update()
def _orient_to_position_first_strandA_base_in_axis_plane( self, baseList, end1, end2): """ The self._orient method orients the DNA duplex parallel to the screen (lengthwise) but it doesn't ensure align the vector through the strand end atom on StrandA and the corresponding axis end atom (at end1) , parallel to the screen. This function does that ( it has some rare bugs which trigger where it doesn't do its job but overall works okay ) What it does: After self._orient() is done orienting, it finds a Quat that rotates between the 'desired vector' between strand and axis ends at end1(aligned to the screen) and the actual vector based on the current positions of these atoms. Using this quat we rotate all the chunks (as a unit) around a common center. @BUG: The last part 'rotating as a unit' uses a readymade method in ops_motion.py -- 'rotateSpecifiedMovables' . This method itself may have some bugs because the axis of the dna duplex is slightly offset to the original axis. @see: self._determine_axis_and_strandA_endAtoms_at_end_1() @see: self.make() """ #the vector between the two end points. these are more likely #points clicked by the user while creating dna duplex using endpoints #of a line. In genral, end1 and end2 are obtained from self.make() b = norm(end2 - end1) axis_strand_vector = (self.strandA_atom_end1.posn() - \ self.axis_atom_end1.posn()) vectorAlongLadderStep = cross(-self.assy.o.lineOfSight, b) unitVectorAlongLadderStep = norm(vectorAlongLadderStep) self.final_pos_strand_end_atom = \ self.axis_atom_end1.posn() + \ vlen(axis_strand_vector)*unitVectorAlongLadderStep expected_vec = self.final_pos_strand_end_atom - self.axis_atom_end1.posn( ) q_new = Q(axis_strand_vector, expected_vec) if dot(axis_strand_vector, self.assy.o.lineOfSight) < 0: q_new2 = Q(b, -q_new.angle) else: q_new2 = Q(b, q_new.angle) self.assy.rotateSpecifiedMovables(q_new2, baseList, end1)
def animate_TCs(): # Animate TCs, rotating them slowly. # Note: as of 090223 and before, this works in DL case but not in shader # case, because coordinate updates after TCs are modified are nim in # shader case (I think). [bruce 090223 comment] slow = 10.0 # Seconds. angle = 2 * pi * fmod(time.time(), slow) / slow # Leave the first one as identity, and rotate the others in # opposite directions around the X axis. TCs[1].setRotateTranslate(Q(V(1, 0, 0), angle * 2), V(0, 0, 0)) TCs[2].setRotateTranslate(Q(V(1, 0, 0), -angle), V(0, 0, 0)) return
def viewIsometric(self): """ This sets the view to isometric. For isometric view, it needs rotation around the vertical axis by pi/4 *followed* by rotation around horizontal axis by asin(tan(pi/6) - ninad060810 """ # This is not yet called from the MainWindow. Need UI for this. # Also need code review -ninad060810 cmd = greenmsg("Isometric View: ") info = 'Current view is Isometric View' env.history.message(cmd + info) self.quatX = Q(V(1,0,0), math.asin(math.tan(math.pi/6))) self.quatY = Q(V(0,1,0), -math.pi/4) self.glpane.rotateView(self.quatY+self.quatX)
def _orient_for_modify(self, end1, end2): """ Do the final orientation of the newly generated dna, around its own axis so that the axis and strand atoms on this new dna are fusable with the resize end on the original dna. @param end1: end1 of new dna (segment) generated @param end2: end2 of new dna (segment) generated """ b = norm(end2 - end1) new_ladder = self.axis_atom_end1.molecule.ladder chunkListForRotation = new_ladder.all_chunks() endBaseAtomList = new_ladder.get_endBaseAtoms_containing_atom( self.axis_atom_end1) endStrandbaseAtoms = (endBaseAtomList[0], endBaseAtomList[2]) self.strandA_atom_end1 = None #TODO: REVIEW / REFACTOR THIS and DOC for atm in endStrandbaseAtoms: if atm is not None: self.strandA_atom_end1 = atm self._set_bond_direction_on_new_strand(atm) axis_strand_vector = (self.strandA_atom_end1.posn() - \ self.axis_atom_end1.posn()) vectorAlongLadderStep = self._resizeEndStrand1Atom.posn() - \ self._resizeEndAxisAtom.posn() unitVectorAlongLadderStep = norm(vectorAlongLadderStep) self.final_pos_strand_end_atom = \ self.axis_atom_end1.posn() + \ vlen(axis_strand_vector)*unitVectorAlongLadderStep q_new = Q(axis_strand_vector, vectorAlongLadderStep) if dot(axis_strand_vector, cross(vectorAlongLadderStep, b)) < 0: q_new2 = Q(b, -q_new.angle) else: q_new2 = Q(b, q_new.angle) self.assy.rotateSpecifiedMovables(q_new2, chunkListForRotation, end1)
def on_drag(self): # Note: we can assume this is a "real drag", since the caller (ultimately a selectMode method in testmode, as of 070209) # is tracking mouse motion and not calling this until it becomes large enough, as the debug070209 prints show. oldpoint = self.oldpoint # was saved by prior on_drag or by on_press point = self.current_event_mousepoint(plane=self.startpoint) if debug070209: self.ndrags += 1 ## if (self.ndrags == 1) or 1: ## print "drag event %d, model distance = %r, pixel dist not computed" % (self.ndrags, vlen(oldpoint - point),) if self._this_drag == 'free x-y rotate': # rotate using New motion UI # [probably works for this specific kind of rotation, one of 4 that UI proposes; # doesn't yet have fancy cursors or during-rotation graphics; add those only after it's a DragCommand] # two implem choices: # 1. know the eye direction and the screen dims in plane of startpoint, in model coords; compute in model coords # 2. get the mouse positions (startpoint and point) and screen dims in window x,y coords, compute rotation in eye coords, # but remember to reorient it to correspond with model if model coords are rotated already. # Not sure which one is better. # In general, placing user into model coords (or more precisely, into object local coords) seems more general -- # for example, what if there were several interacting users, each visible to the others? # We'd want each user's eye & screen to be visible! (Maybe even an image of their face & screen, properly scaled and aligned?) # And we'd want their posns to be used in the computations here, all in model coords. # (Even if zoom had occurred, which means, even the user's *size* is quite variable!) # I need "user in model coords" for other reasons too, so ok, I'll do it that way. # # [Hey, I might as well fix the bug in current_event_mousepoint which fakes the center of view, at the same time. # (I can't remember its details right now, but I think it assumed the local origin was the cov, which is obviously wrong.) # (But I didn't look at that code or fix that bug now.)] vec = point - self.startpoint uvec = norm(vec) #k needed?? axisvec = cross( self._dz, uvec ) # unit length (suitable for glRotate -- but we need to use it to make a quat!) axisvec = norm( axisvec) # just to be sure (or to reduce numerical errors) scale = self._scale draw_axisvec = axisvec * scale #e times some other length constant too? center = self._objcenter self.axisends = (center - axisvec, center + axisvec ) # draw a rotation axis here ###e self.degrees = degrees = vlen( vec ) / scale * 180.0 # draw a textual indicator with degrees (and axisvec angle too) ###e ###e or print that info into sbar? or somewhere fixed in glpane? or in glpane near mouse? # now set self.rotation to a quat made from axisvec and degrees theta = degrees / 360.0 * 2 * pi # print "axisvec %r, degrees %r, theta %r" % (axisvec ,degrees,theta) rot = Q(axisvec, theta) self.rotation = self.startrot + rot # note use of self.startrot rather than self.rotation on rhs # avoid += to make sure it gets changed-tracked -- and since it would be the wrong op! elif self._this_drag == 'free x-y translate': self._cmd_drag_from_to( oldpoint, point) # use Draggable interface cmd on self else: assert 0 self.oldpoint = point return
def bonded_atoms_summary( bond, quat=Q(1, 0, 0, 0) ): #bruce 050705; direction feature, bruce 070414. ###e SHOULD CALL bond_left_atom """ Given a bond, and an optional quat describing the orientation it's shown in, order the atoms left to right based on that quat, and return a text string summarizing the bond in the form C26(sp2) <-2-> C34(sp3) or so, leaving out the < or > if the bond has a direction. """ a1 = bond.atom1 a2 = bond.atom2 direction = bond._direction vec = a2.posn() - a1.posn() vec = quat.rot(vec) if vec[0] < 0.0: a1, a2 = a2, a1 direction = -direction a1s = describe_atom_and_atomtype(a1) a2s = describe_atom_and_atomtype(a2) bondletter = bond_letter_from_v6(bond.v6) if bondletter == '1': bondletter = '' arrows = _bond_arrows.get(direction, ("<-", " (invalid direction) ->")) return "%s %s%s%s %s" % (a1s, arrows[0], bondletter, arrows[1], a2s)
def baseframe_from_pam3_data(ss1, ax, ss2): """ Given the positions of the Ss3-Ax3-Ss3 atoms in a PAM3 basepair, return the first Ss3's baseframe (and y_m) as a tuple of (origin, rel_to_abs_quat, y_m). """ yprime_vector = ss2 - ss1 yprime_length = vlen(yprime_vector) y_direction = yprime_vector / yprime_length # optimization of norm z_direction = norm(cross(ax - ss1, y_direction)) # BUG: nothing checks for cross product being too small x_direction = norm(cross(y_direction, z_direction)) # this norm is redundant, but might help with numerical stability rel_to_abs_quat = Q(x_direction, y_direction, z_direction) # still need origin, easy since we know SPRIME_D_SDFRAME -- but we do have to rotate that, using the quat # rel_to_abs_quat.rot( SPRIME_D_SDFRAME ) # this is Ss5 to Ss3 vector, abs coords Ss3_d_abspos = ss1 Ss5_d_abspos = Ss3_d_abspos - rel_to_abs_quat.rot(SPRIME_D_SDFRAME) origin = Ss5_d_abspos # y_m = (|S'_u - S'_d| / 2) + y_s' y_m = yprime_length / 2.0 + Y_SPRIME return (origin, rel_to_abs_quat, y_m)
def getRotateTranslate(self): """ @return: self's transform value, as the tuple (quat, vec), representing the translation 3-vector vec composed with the rotation quaternion quat (rotation to be done first). @rtype: (quat, vec) where quat is of class Q, and vec is a length-3 sequence of undocumented type. If self is being used to transform a 3d model, the rotation should be applied to the model first, to orient it around its presumed center; then the translation, to position the rotated model with its center in the desired location. This means that the opposite order should be used to apply them to the GL matrices (which give the coordinate system for drawing), i.e. first glTranslatef, then glRotatef. """ # With no scales, skews, or perspective the right column is [0, 0, 0, # 1]. The upper right 3x3 is a rotation matrix giving the orientation #### ^^^^^ #### REVIEW: should this 'right' be 'left'? #### [bruce 090203 comment] # of the new right-handed orthonormal local coordinate frame, and the # left-bottom-row 3-vector is the translation that positions the origin # of that frame. quat = Q(self.transform[0, 0:3], self.transform[1, 0:3], self.transform[2, 0:3]) vec = self.transform[3, 0:3] return (quat, vec)
def _fitInWindow(self): if not self.model: return self.quat = Q(1, 0, 0, 0) if isinstance(self.model, Chunk): self.model._recompute_bbox() bbox = self.model.bbox else: ## Assembly part = self.model.part bbox = part.bbox self.scale = bbox.scale() if isinstance(self.width, int): width = self.width else: width = float(self.width()) if isinstance(self.height, int): height = self.height else: height = float(self.height()) aspect = width / height ##aspect = float(self.width) / self.height if aspect < 1.0: self.scale /= aspect center = bbox.center() self.pov = V(-center[0], -center[1], -center[2])
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 drawFilledCircle(color, center, radius, normal): """ Scale, rotate/translate the unit circle properly. Added a filled circle variant, piotr 080405 """ glMatrixMode(GL_MODELVIEW) glPushMatrix() glColor3fv(color) glDisable(GL_LIGHTING) glTranslatef(center[0], center[1], center[2]) rQ = Q(V(0, 0, 1), normal) rotAngle = rQ.angle * 180.0 / pi #This may cause problems as proved before in Linear motor display. #rotation around (0, 0, 0) #if vlen(V(rQ.x, rQ.y, rQ.z)) < 0.00005: # rQ.x = 1.0 glRotatef(rotAngle, rQ.x, rQ.y, rQ.z) glScalef(radius, radius, 1.0) glCallList(drawing_globals.filledCircleList) glEnable(GL_LIGHTING) glPopMatrix() return
def viewParallelTo(self): """ Set view parallel to the vector defined by 2 selected atoms. """ cmd = greenmsg("Set View Parallel To: ") atoms = self.assy.selatoms_list() if len(atoms) != 2: msg = redmsg("You must select 2 atoms.") env.history.message(cmd + msg) return v = norm(atoms[0].posn()-atoms[1].posn()) if vlen(v) < 0.0001: # Atoms are on top of each other. info = 'The selected atoms are on top of each other. No change in view.' env.history.message(cmd + info) return # If vec is pointing into the screen, negate (reverse) vec. if dot(v, self.glpane.lineOfSight) > 0: v = -v # Compute the destination quat (q2). q2 = Q(V(0,0,1), v) q2 = q2.conj() self.glpane.rotateView(q2) info = 'View set parallel to the vector defined by the 2 selected atoms.' env.history.message(cmd + info)
def _initialize_view_attributes(self): #bruce 090220 split this out """ Initialize the current view attributes """ # note: these are sometimes saved in or loaded from # the currently displayed part or its mmp file # rotation self.quat = Q(1, 0, 0, 0) # point of view (i.e. negative of center of view) self.pov = V(0.0, 0.0, 0.0) # half-height of window in Angstroms (reset by certain view-changing operations) self.scale = float(env.prefs[startup_GLPane_scale_prefs_key]) # zoom factor self.zoomFactor = 1.0 # Note: I believe (both now, and in a comment dated 060829 which is # now being removed from GLPane.py) that nothing ever sets # self.zoomFactor to anything other than 1.0, though there is code # which could do this in theory. I think zoomFactor was added as # one way to implement zoomToArea, but another way (changing scale) # was chosen, which makes zoomFactor useless. Someday we should # consider removing it, unless we think it might be useful for # something in the future. [bruce 080910 comment] 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 baseframe_from_pam5_data(ss1, gv, ss2): """ Given the positions of the Ss5-Gv5-Ss5 atoms in a PAM5 basepair, return the first Ss5's baseframe (and y_m) as a tuple of (origin, rel_to_abs_quat, y_m). @note: this is correct even if gv is actually an Ax5 position. """ # y axis is parallel to inter-sugar line # base plane orientation comes from the other atom, Gv # so get x and z axis around that line origin = ss1 y_vector = ss2 - ss1 y_length = vlen(y_vector) ## y_direction = norm(ss2 - ss1) y_direction = y_vector / y_length # optimization z_direction = norm(cross(gv - ss1, y_direction)) # BUG: nothing checks for cross product being too small x_direction = norm(cross(y_direction, z_direction)) # this norm is redundant, but might help with numerical stability rel_to_abs_quat = Q(x_direction, y_direction, z_direction) y_m = y_length / 2.0 return (origin, rel_to_abs_quat, y_m)
def __init__(self, elem, name, formalCharge, electronsNeeded, electronsProvided, covalentRadius, bondvectors): """ #doc... Set some public members, including element, name, fullname, numbonds, valence, rcovalent, bondvectors, base, and quats. Also spX, openbond; and for some elements, num_lonepairs (etc), num_pi_electrons. """ self.element = elem # electronsProvided + formalCharge should equal group number # these are shared from another atom (half of an order 1 covalent bond) electronsShared = electronsNeeded - electronsProvided self.valence = electronsShared # total bond order for all bonds to this atomtype self.name = name = name or "?" # default name won't show up except for bugs, provided it's only used for elements with only one atomtype self.fullname = elem.name + '/' + self.name #ok? [see also self.fullname_for_msg()] self.openbond = (elem.eltnum == 0) if name.startswith("sp3") or name == "?": spX = 3 elif name.startswith("sp2"): # including sp2 and sp2(graphitic) spX = 2 elif name.startswith("sp") and (name == "sp" or not name[2].isdigit()): spX = 1 else: print "warning: bug: atomtype name in %r does not start with sp, sp2, or sp3; assuming sp3 in bonds code" % self.fullname spX = 3 self.spX = spX if 0 and debug_flags.atom_debug and (spX != 3 or self.openbond): print "atom_debug: fyi: %r has spX == %d" % (self.fullname, spX) self.rcovalent = covalentRadius self.base = None self.quats = [ ] # ends up one shorter than self.numbonds [bruce 041217] if (bondvectors != None): # number of distinct bonds to different other atoms (a # double bond is counted as 1) self.numbonds = len(bondvectors) s = bondvectors[0] self.base = s for v in bondvectors[1:]: self.quats += [Q(s, v)] else: self.numbonds = 0 if (bondvectors != None): self.bondvectors = bondvectors else: self.bondvectors = [] self.charge = formalCharge #self._init_electronic_structure() # this uses self.numbonds, so don't call it too early self._init_permitted_v6_list() return # from __init__
def __init_quat_center(self, list): for a in list: #[:3]: self.atomPos += [a.posn()] planeNorm = self._getPlaneOrientation(self.atomPos) self.quat = Q(V(0.0, 0.0, 1.0), planeNorm) self.center = add.reduce(self.atomPos) / len(self.atomPos)
def viewRotate180(self): """ Set view to the opposite of current view. """ cmd = greenmsg("Opposite View: ") info = 'Current view opposite to the previous view' env.history.message(cmd + info) self.glpane.rotateView(self.glpane.quat + Q(V(0,1,0), math.pi))
def viewRotateMinus90(self): # Added by Mark. 051013. """ Decrement the current view by 90 degrees around the vertical axis. """ cmd = greenmsg("Rotate View -90 : ") info = 'View decremented by 90 degrees' env.history.message(cmd + info) self.glpane.rotateView(self.glpane.quat + Q(V(0,1,0), -math.pi/2))
def resetView(self): """ Reset the view. Subclass can override this method with different <scale>, so call this version in the overridden version. """ self.pov = V(0.0, 0.0, 0.0) self.quat = Q(1, 0, 0, 0)
def _orient(self, cntChunk, pt1, pt2): """ Orients the CNT I{cntChunk} based on two points. I{pt1} is the first endpoint (origin) of the nanotube. The vector I{pt1}, I{pt2} defines the direction and central axis of the nanotube. @param pt1: The starting endpoint (origin) of the nanotube. @type pt1: L{V} @param pt2: The second point of a vector defining the direction and central axis of the nanotube. @type pt2: L{V} """ a = V(0.0, 0.0, -1.0) # <a> is the unit vector pointing down the center axis of the default # DNA structure which is aligned along the Z axis. bLine = pt2 - pt1 bLength = vlen(bLine) b = bLine / bLength # <b> is the unit vector parallel to the line (i.e. pt1, pt2). axis = cross(a, b) # <axis> is the axis of rotation. theta = angleBetween(a, b) # <theta> is the angle (in degress) to rotate about <axis>. scalar = bLength * 0.5 rawOffset = b * scalar if 0: # Debugging code. print "" print "uVector a = ", a print "uVector b = ", b print "cross(a,b) =", axis print "theta =", theta print "cntRise =", self.getCntRise() print "# of cells =", self.getNumberOfCells() print "scalar =", scalar print "rawOffset =", rawOffset if theta == 0.0 or theta == 180.0: axis = V(0, 1, 0) # print "Now cross(a,b) =", axis rot = (pi / 180.0) * theta # Convert to radians qrot = Q(axis, rot) # Quat for rotation delta. # Move and rotate the nanotube into final orientation. cntChunk.move( qrot.rot(cntChunk.center) - cntChunk.center + rawOffset + pt1) cntChunk.rot(qrot) # Bruce suggested I add this. It works here, but not if its # before move() and rot() above. Mark 2008-04-11 cntChunk.full_inval_and_update() return
def viewNormalTo(self): # """ Set view to the normal vector of the plane defined by 3 or more selected atoms or a jig's (Motor or RectGadget) axis. """ cmd = greenmsg("Set View Normal To: ") chunks = self.assy.selmols jigs = self.assy.getSelectedJigs() atoms = self.assy.selatoms_list() #following fixes bug 1748 ninad 061003. if len(chunks) > 0 and len(atoms) == 0: # Even though chunks have an axis, it is not necessarily the same # axis attr stored in the chunk. Get the chunks atoms and let # compute_heuristic_axis() recompute them. for c in range(len(chunks)): atoms += chunks[c].atoms.values() elif len(jigs) == 1 and len(atoms) == 0: # Warning: RectGadgets have no atoms. We handle this special case below. atoms = jigs[0].atoms elif len(atoms) < 3: # There is a problem when allowing only 2 selected atoms. # Changing requirement to 3 atoms fixes bug 1418. mark 060322 msg = redmsg("Please select some atoms, jigs, and/or chunks, covering at least 3 atoms") print "ops_view.py len(atoms) = ", len(atoms) env.history.message(cmd + msg) return # This check is needed for jigs that have no atoms. Currently, this # is the case for RectGadgets (ESP Image and Grid Plane) only. if len(atoms): pos = A( map( lambda a: a.posn(), atoms ) ) nears = [ self.glpane.out, self.glpane.up ] axis = compute_heuristic_axis( pos, 'normal', already_centered = False, nears = nears, dflt = None ) else: # We have a jig with no atoms. axis = jigs[0].getaxis() # Get the jig's axis. # If axis is pointing into the screen, negate (reverse) axis. if dot(axis, self.glpane.lineOfSight) > 0: axis = -axis if not axis: msg = orangemsg( "Warning: Normal axis could not be determined. No change in view." ) env.history.message(cmd + msg) return # Compute the destination quat (q2). q2 = Q(V(0,0,1), axis) q2 = q2.conj() self.glpane.rotateView(q2) info = 'View set to normal vector of the plane defined by the selected atoms.' env.history.message(cmd + info)
def __init__(self, assy, atomlist = []): #bruce 050526 added optional atomlist arg assert atomlist == [] # whether from default arg value or from caller -- for now Jig.__init__(self, assy, atomlist) self.quat = Q(1, 0, 0, 0) # is self.quat ever set to other values? if not, remove it; if so, add it to mutable_attrs. [bruce 060228 comment] #The motor is usually drawn as an opaque object. However when it is #being previewed, it is drawn as a transparent object - Ninad 2007-10-09 self.previewOpacity = 0.4 self.defaultOpacity = 1.0 self.opacity = self.defaultOpacity