def closest_pt_params_to_ray(self, ray): "" p2, v2 = ray.params # note: at first I wrote self.params() (using method not attr) p1, v1 = self.params # do some math, solve for k in p1 + k * v1 = that point (remember that the vecs can be of any length): # way 1: express p2-p1 as a weighted sum of v1, v2, cross(v1,v2), then take the v1 term in that sum and add it to p1. # way 2: There must be a NumPy function that would just do this in about one step... # way 3: or maybe we can mess around with dot(v1,v2) sort of like in corner_analyzer in demo_polygon... # way 4: or we could google for "closest points on two lines" or so... # way 5: or we could call it the intersection of self with the plane containing p2, and directions v2 and the cross prod, # and use a formula in VQT. Yes, that may not be self-contained but it's fastest to code! v1n = norm(v1) v2n = norm(v2) perp0 = cross(v1n, v2n) if vlen(perp0) < 0.01: ##k btw what if the lines are parallel, in what way should we fail? # and for that matter what if they are *almost* parallel so that we're too sensitive -- do we use an env param # to decide whether to fail in that case too? If we're an Instance we could do that from self.env... #e print "closest_pt_params_to_ray: too sensitive, returning None" ###### teach caller to handle this; let 0.01 be option return None perpn = norm(perp0) perpperp = cross(perpn, v2n) inter = planeXline( p2, perpperp, p1, v1n ) # intersect plane (as plane point and normal) with line (as point and vector) if inter is None: print "inter is None (unexpected); data:", p1, v1, p2, v2, perp0 return None # inter is the retval for a variant which just wants the closest point itself, i.e. closest_pt_to_ray return dot(inter - p1, v1n) / vlen(v1)
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 on_press(self): point = self.current_event_mousepoint() # the touched point on the visible object (hitpoint) # (this method is defined in the Highlightable which is self.delegate) self.oldpoint = self.startpoint = point # decide type of drag now, so it's clearly constant during drag, and so decision code is only in one place. # (but note that some modkey meanings might require that changes to them during the same drag are detected [nim].) if self._delegate.altkey: self._this_drag = 'free x-y rotate' #e more options later, and/or more flags like this (maybe some should be booleans) ###e or better, set up a function or object which turns later points into their effects... hmm, a DragCommand instance! ##e or should that be renamed DragOperation?? self._screenrect = (ll, lr, ur, ul) = self.screenrect( self.startpoint) # these points should be valid in our delegate's coords == self's coords self._dx = _dx = norm(lr - ll) self._dy = _dy = norm(ur - lr) self._dz = cross(_dx, _dy) # towards the eye (if view is ortho) (but alg is correct whether or not it is, i think) ###k check cross direction sign self._scale = min(vlen(lr - ll), vlen(ur - lr)) * 0.4 # New motion UI suggests that 40% of that distance means 180 degrees of rotation. # We'll draw an axis whose length is chosen so that dragging on a sphere of that size # would have the same effect. (Maybe.) self._objcenter = self._delegate.center self.startrot = + self.rotation else: self._this_drag = 'free x-y translate' if debug070209: self.ndrags = 0 return
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 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 on_press(self): point = self.current_event_mousepoint( ) # the touched point on the visible object (hitpoint) # (this method is defined in the Highlightable which is self.delegate) self.oldpoint = self.startpoint = point # decide type of drag now, so it's clearly constant during drag, and so decision code is only in one place. # (but note that some modkey meanings might require that changes to them during the same drag are detected [nim].) if self._delegate.altkey: self._this_drag = 'free x-y rotate' #e more options later, and/or more flags like this (maybe some should be booleans) ###e or better, set up a function or object which turns later points into their effects... hmm, a DragCommand instance! ##e or should that be renamed DragOperation?? self._screenrect = (ll, lr, ur, ul) = self.screenrect(self.startpoint) # these points should be valid in our delegate's coords == self's coords self._dx = _dx = norm(lr - ll) self._dy = _dy = norm(ur - lr) self._dz = cross( _dx, _dy ) # towards the eye (if view is ortho) (but alg is correct whether or not it is, i think) ###k check cross direction sign self._scale = min(vlen(lr - ll), vlen(ur - lr)) * 0.4 # New motion UI suggests that 40% of that distance means 180 degrees of rotation. # We'll draw an axis whose length is chosen so that dragging on a sphere of that size # would have the same effect. (Maybe.) self._objcenter = self._delegate.center self.startrot = +self.rotation else: self._this_drag = 'free x-y translate' if debug070209: self.ndrags = 0 return
def getAxisVector(self, atomAtVectorOrigin = None): """ Returns the unit axis vector of the segment (vector between two axis end points) """ endPoint1, endPoint2 = self.getAxisEndPoints() if endPoint1 is None or endPoint2 is None: return V(0, 0, 0) if atomAtVectorOrigin is not None: #If atomAtVectorOrigin is specified, we will return a vector that #starts at this atom and ends at endPoint1 or endPoint2 . #Which endPoint to choose will be dicided by the distance between #atomAtVectorOrigin and the respective endPoints. (will choose the #frthest endPoint origin = atomAtVectorOrigin.posn() if vlen(endPoint2 - origin ) > vlen(endPoint1 - origin): return norm(endPoint2 - endPoint1) else: return norm(endPoint1 - endPoint2) return norm(endPoint2 - endPoint1)
def closest_pt_params_to_ray(self, ray): "" p2, v2 = ray.params # note: at first I wrote self.params() (using method not attr) p1, v1 = self.params # do some math, solve for k in p1 + k * v1 = that point (remember that the vecs can be of any length): # way 1: express p2-p1 as a weighted sum of v1, v2, cross(v1,v2), then take the v1 term in that sum and add it to p1. # way 2: There must be a NumPy function that would just do this in about one step... # way 3: or maybe we can mess around with dot(v1,v2) sort of like in corner_analyzer in demo_polygon... # way 4: or we could google for "closest points on two lines" or so... # way 5: or we could call it the intersection of self with the plane containing p2, and directions v2 and the cross prod, # and use a formula in VQT. Yes, that may not be self-contained but it's fastest to code! v1n = norm(v1) v2n = norm(v2) perp0 = cross(v1n, v2n) if vlen(perp0) < 0.01: ##k btw what if the lines are parallel, in what way should we fail? # and for that matter what if they are *almost* parallel so that we're too sensitive -- do we use an env param # to decide whether to fail in that case too? If we're an Instance we could do that from self.env... #e print "closest_pt_params_to_ray: too sensitive, returning None" ###### teach caller to handle this; let 0.01 be option return None perpn = norm(perp0) perpperp = cross(perpn,v2n) inter = planeXline(p2, perpperp, p1, v1n) # intersect plane (as plane point and normal) with line (as point and vector) if inter is None: print "inter is None (unexpected); data:",p1,v1,p2,v2,perp0 return None # inter is the retval for a variant which just wants the closest point itself, i.e. closest_pt_to_ray return dot(inter - p1, v1n) / vlen(v1)
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 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 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 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 walk_great_circle(P, Q, D, R=radius): """Given two points P and Q on or near the surface of the sphere, use P and Q to define a great circle. Then walk along that great circle starting at P and going in the direction of Q, and traveling far enough the chord is of length D. P and Q are not required to lie exactly on the sphere's surface. """ dP, dQ = P - sphere_center, Q - sphere_center dPs = cross(cross(dP, dQ), dP) cpart, spart = norm(dP), norm(dPs) theta = 2 * asin(0.5 * D / R) return sphere_center + R * (cpart * cos(theta) + spart * sin(theta))
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 _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 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 placePlaneOffsetToAnother(self): """ Orient the plane such that it is parallel to a selected plane , with an offset. """ cmd = self.editCommand.cmd jigList = self.win.assy.getSelectedJigs() if jigList: planeList = [] for j in jigList: if isinstance(j, Plane) and (j is not self): planeList.append(j) #First, clear all the direction arrow drawings if any in #the existing Plane objectes in the part if not self.assy.part.topnode.members: msg = redmsg("Select a different plane first to place the" " current plane offset to it") env.history.message(cmd + msg) return for p in self.assy.part.topnode.members: if isinstance(p, Plane): if p.directionArrow: p.directionArrow.setDrawRequested(False) if len(planeList) == 1: self.offsetParentGeometry = planeList[0] self.offsetParentGeometry.directionArrow.setDrawRequested(True) if self.offsetParentGeometry.directionArrow.flipDirection: offset = 2 * norm(self.offsetParentGeometry.getaxis()) else: offset = -2 * norm(self.offsetParentGeometry.getaxis()) self.center = self.offsetParentGeometry.center + offset self.quat = Q(self.offsetParentGeometry.quat) else: msg = redmsg("Select exactly one plane to\ create a plane offset to it.") env.history.message(cmd + msg) return else: msg = redmsg("Select an existing plane first to\ create a plane offset to it.") env.history.message(cmd + msg) return self.glpane.gl_update()
def updateHandlePositions(self): """ Update handle positions and also update the resize handle radii and their 'stopper' lengths. @see: self._update_resizeHandle_radius() @see: self._update_resizeHandle_stopper_length() @see: EditNanotube_GraphicsMode._drawHandles() """ self.handlePoint1 = None # Needed! self.handlePoint2 = None #TODO: Call this method less often by implementing model_changed #see bug 2729 for a planned optimization self.cylinderWidth = CYLINDER_WIDTH_DEFAULT_VALUE self.cylinderWidth2 = CYLINDER_WIDTH_DEFAULT_VALUE self._update_resizeHandle_radius() handlePoint1, handlePoint2 = self.struct.nanotube.getEndPoints() if 0: # Debug prints print "updateHandlePositions(): handlePoint1=", handlePoint1 print "updateHandlePositions(): handlePoint2=", handlePoint2 if handlePoint1 is not None and handlePoint2 is not None: # (that condition is bugfix for deleted axis segment, bruce 080213) self.handlePoint1, self.handlePoint2 = handlePoint1, handlePoint2 #Update the 'stopper' length where the resize handle being dragged #should stop. See self._update_resizeHandle_stopper_length() #for more details self._update_resizeHandle_stopper_length() if DEBUG_ROTATION_HANDLES: self.rotation_distance1 = CYLINDER_WIDTH_DEFAULT_VALUE self.rotation_distance2 = CYLINDER_WIDTH_DEFAULT_VALUE #Following computes the base points for rotation handles. #to be revised -- Ninad 2008-02-13 unitVectorAlongAxis = norm(self.handlePoint1 - self.handlePoint2) v = cross(self.glpane.lineOfSight, unitVectorAlongAxis) self.rotationHandleBasePoint1 = self.handlePoint1 + norm( v) * 4.0 self.rotationHandleBasePoint2 = self.handlePoint2 + norm( v) * 4.0 return
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 drawRotateSign(color, pos1, pos2, radius, rotation=0.0): """Rotate sign on top of the caps of the cylinder """ glPushMatrix() glColor3fv(color) vec = pos2 - pos1 axis = norm(vec) glTranslatef(pos1[0], pos1[1], pos1[2]) ##Huaicai 1/17/05: To avoid rotate around (0, 0, 0), which causes ## display problem on some platforms angle = -acos(axis[2]) * 180.0 / pi if (axis[2] * axis[2] >= 1.0): glRotate(angle, 0.0, 1.0, 0.0) else: glRotate(angle, axis[1], -axis[0], 0.0) glRotate(rotation, 0.0, 0.0, 1.0) #bruce 050518 glScale(radius, radius, Numeric.dot(vec, vec)**.5) glLineWidth(2.0) glDisable(GL_LIGHTING) glCallList(drawing_globals.rotSignList) glEnable(GL_LIGHTING) glLineWidth(1.0) glPopMatrix() return
def add_basepair_handles_to_atoms(atoms): #bruce 080515 """ """ goodcount, badcount = 0, 0 for atom in atoms: atom.unpick() if atom.element is Gv5 and len(atom.strand_neighbors()) == 2: goodcount += 1 # Figure out the position from the Gv5 and its presumed-to-be Ss5 # neighbors. [Fixed per Eric D spec, bruce 080516] sn = atom.strand_neighbors() ss_midpoint = average_value([a.posn() for a in sn]) towards_Gv = norm(atom.posn() - ss_midpoint) newpos = ss_midpoint + \ BASEPAIR_HANDLE_DISTANCE_FROM_SS_MIDPOINT * towards_Gv ## if 0: # stub for computing new position ## oldposns = [a.posn() for a in ([atom] + sn)] ## newpos = average_value(oldposns) Atom = atom.molecule.assy.Atom # (avoid model.chem import cycle) newatom = Atom('Ah5', newpos, atom.molecule) # PAM5-Axis-handle bond_atoms_faster(newatom, atom, V_SINGLE) # note: no bondpoints need creation or removal newatom.pick() pass else: badcount += 1 continue return goodcount, badcount
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 drawRotateSign(color, pos1, pos2, radius, rotation = 0.0): """Rotate sign on top of the caps of the cylinder """ glPushMatrix() glColor3fv(color) vec = pos2-pos1 axis = norm(vec) glTranslatef(pos1[0], pos1[1], pos1[2]) ##Huaicai 1/17/05: To avoid rotate around (0, 0, 0), which causes ## display problem on some platforms angle = -acos(axis[2])*180.0/pi if (axis[2]*axis[2] >= 1.0): glRotate(angle, 0.0, 1.0, 0.0) else: glRotate(angle, axis[1], -axis[0], 0.0) glRotate(rotation, 0.0, 0.0, 1.0) #bruce 050518 glScale(radius,radius,Numeric.dot(vec,vec)**.5) glLineWidth(2.0) glDisable(GL_LIGHTING) glCallList(drawing_globals.rotSignList) glEnable(GL_LIGHTING) glLineWidth(1.0) glPopMatrix() return
def arbitrary_perpendicular( vec, nicevecs = [] ): #bruce 060608, probably duplicates other code somewhere (check in VQT?) """ Return an arbitrary unit vector perpendicular to vec (a Numeric array, 3 floats), making it from vec and as-early-as-possible nicevecs (if any are passed). """ nicevecs = map(norm, nicevecs) + [X_AXIS, Y_AXIS] # we'll look at vec and each nicevec until they span a subspace which includes a perpendicular. # if we're not done, it means our subspace so far is spanned by just vec itself. vec = norm(vec) if not vec: return nicevecs[0] # the best we can do for nice in nicevecs: res = norm( nice - vec * dot(nice, vec) ) if res: return res return nicevecs[0] # should never happen
def getAxisVector(self, atomAtVectorOrigin = None): """ Returns the unit axis vector of the segment (vector between two axis end points) """ # REVIEW: use common code for this method? [bruce 081217 comment] endPoint1, endPoint2 = self.Peptide.getEndPoints() if endPoint1 is None or endPoint2 is None: return V(0, 0, 0) #@see: RotateAboutAPoint command. The following code is disabled #as it has bugs (not debugged but could be in #self.Peptide.getEndPoints). So, rotate about a point won't work for #rotating a Peptide. -- Ninad 2008-05-13 ##if atomAtVectorOrigin is not None: ###If atomAtVectorOrigin is specified, we will return a vector that ###starts at this atom and ends at endPoint1 or endPoint2 . ###Which endPoint to choose will be dicided by the distance between ###atomAtVectorOrigin and the respective endPoints. (will choose the ###frthest endPoint ##origin = atomAtVectorOrigin.posn() ##if vlen(endPoint2 - origin ) > vlen(endPoint1 - origin): ##return norm(endPoint2 - endPoint1) ##else: ##return norm(endPoint1 - endPoint2) return norm(endPoint2 - endPoint1)
def _neighborSegment_ok_for_crossover_search(self, neighborSegment, reference_segment_end1, reference_segment_axisVector): ok_for_search = False orthogonal_vector = None neighbor_end1, neighbor_end2 = neighborSegment.getAxisEndPoints() #Use end1 of neighbor segment and find out the perpendicular #distance (and the vector) between this atom and the #axis vector of dnaSegment (whose neighbors are being #searched). If this distance is less than a specified amount #then 'neighbor' is an approved neighbor of 'dnaSegment' if neighbor_end1 is not None: p1 = reference_segment_end1 v1 = reference_segment_axisVector p2 = neighbor_end1 dist, orthogonal_dist = orthodist(p1, v1, p2) #Check if the orthogonal distance is withing the #specified limit. if orthogonal_dist <= MAX_PERPENDICULAR_DISTANCE_BET_SEGMENT_AXES: ok_for_search = True vec = p1 + dist * v1 - p2 orthogonal_vector = orthogonal_dist * norm(vec) if self.graphicsMode.DEBUG_DRAW_PLANE_NORMALS: self._DEBUG_plane_normals_ends_for_drawing.append( (p2, (p2 + orthogonal_vector))) return ok_for_search, orthogonal_vector
def arb_perp_unit_vector(vec): """ Given a nonzero vector, return an arbitrary unit vector perpendicular to it. """ vec2 = arb_non_parallel_vector(vec) return norm(cross(vec, vec2))
def bareMotion(self, event): """ Event handler for simple drag event. (i.e. the free drag without holding down any mouse button) @see: self.isSpecifyPlaneToolActive() @see: self.getDrawingPlane() @see: DnaDuplex_Graphicsmode.isSpecifyPlaneToolActive() """ if not self.isSpecifyPlaneToolActive(): if len(self.command.mouseClickPoints) > 0: plane = self.getDrawingPlane() if plane: self.endPoint2 = self.dragto(self.endPoint1, event, perp=norm(plane.getaxis())) else: self.endPoint2 = self.dragto(self.endPoint1, event) self.endPoint2 = self.snapLineEndPoint() self.update_cursor_for_no_MB() self.glpane.gl_update() value = _superclass_for_GM.bareMotion(self, event) #Needed to make sure that the cursor is updated properly when #the mouse is moved after the 'specify reference plane tool is #activated/deactivated self.update_cursor() return value # russ 080527
def _leftDown_preparation_for_dragging(self, objectUnderMouse, event): """ Handle left down event. Preparation for rotation and/or selection This method is called inside of self.leftDown. @param event: The mouse left down event. @type event: QMouseEvent instance @see: self.leftDown @see: self.leftDragRotation Overrides _superclass._leftDown_preparation_for_dragging """ _superclass._leftDown_preparation_for_dragging(self, objectUnderMouse, event) self.o.SaveMouse(event) self.picking = True self.dragdist = 0.0 # delta for constrained rotations. self.rotDelta = 0 if self.rotateOption == "ROTATEDEFAULT": self.o.trackball.start(self.o.MousePos[0], self.o.MousePos[1]) else: if self.rotateOption == "ROTATEX": ma = V(1, 0, 0) # X Axis self.axis = "X" elif self.rotateOption == "ROTATEY": ma = V(0, 1, 0) # Y Axis self.axis = "Y" elif self.rotateOption == "ROTATEZ": ma = V(0, 0, 1) # Z Axis self.axis = "Z" elif self.rotateOption == "ROT_TRANS_ALONG_AXIS": # The method 'self._leftDown_preparation_for_dragging should # never be reached if self.rotateOption is 'ROT_TRANS_ALONG_AXIS' # If this code is reached, it indicates a bug. So fail gracefully # by calling self.leftADown() if debug_flags.atom_debug: print_compact_stack( "bug: _leftDown_preparation_for_dragging" " called for rotate option" "'ROT_TRANS_ALONG_AXIS'" ) self.leftADown(objectUnderMouse, event) return else: print "Move_Command: Error - unknown rotateOption value =", self.rotateOption return ma = norm(V(dot(ma, self.o.right), dot(ma, self.o.up))) # When in the front view, right = 1,0,0 and up = 0,1,0, so ma will # be computed as 0,0.This creates a special case problem when the # user wants to constrain rotation around the Z axis because Zmat # will be zero. So we have to test for this case (ma = 0,0) and # fix ma to -1,0. This was needed to fix bug 537. Mark 050420 if ma[0] == 0.0 and ma[1] == 0.0: ma = [-1.0, 0.0] self.Zmat = A([ma, [-ma[1], ma[0]]]) self.leftDownType = "ROTATE" return
def _neighborSegment_ok_for_crossover_search( self, neighborSegment, reference_segment_end1, reference_segment_axisVector): ok_for_search = False orthogonal_vector = None neighbor_end1 , neighbor_end2 = neighborSegment.getAxisEndPoints() #Use end1 of neighbor segment and find out the perpendicular #distance (and the vector) between this atom and the #axis vector of dnaSegment (whose neighbors are being #searched). If this distance is less than a specified amount #then 'neighbor' is an approved neighbor of 'dnaSegment' if neighbor_end1 is not None: p1 = reference_segment_end1 v1 = reference_segment_axisVector p2 = neighbor_end1 dist, orthogonal_dist = orthodist(p1, v1, p2) #Check if the orthogonal distance is withing the #specified limit. if orthogonal_dist <= MAX_PERPENDICULAR_DISTANCE_BET_SEGMENT_AXES: ok_for_search = True vec = p1 + dist*v1 - p2 orthogonal_vector = orthogonal_dist*norm(vec) if self.graphicsMode.DEBUG_DRAW_PLANE_NORMALS: self._DEBUG_plane_normals_ends_for_drawing.append( (p2, (p2 + orthogonal_vector)) ) return ok_for_search, orthogonal_vector
def bareMotion(self, event): """ Event handler for simple drag event. (i.e. the free drag without holding down any mouse button) @see: self.isSpecifyPlaneToolActive() @see: self.getDrawingPlane() @see: DnaDuplex_Graphicsmode.isSpecifyPlaneToolActive() """ if not self.isSpecifyPlaneToolActive(): if len(self.command.mouseClickPoints) > 0: plane = self.getDrawingPlane() if plane: self.endPoint2 = self.dragto( self.endPoint1, event, perp = norm(plane.getaxis())) else: self.endPoint2 = self.dragto( self.endPoint1, event) self.endPoint2 = self.snapLineEndPoint() self.update_cursor_for_no_MB() self.glpane.gl_update() value = _superclass_for_GM.bareMotion(self,event) #Needed to make sure that the cursor is updated properly when #the mouse is moved after the 'specify reference plane tool is #activated/deactivated self.update_cursor() return value # russ 080527
def findHandles_exact(self, p1, p2, cutoff = 0.0, backs_ok = 1, offset = V(0,0,0)): """ return a list of (dist, handle) pairs, in arbitrary order, which includes, for each handle (spherical surface) hit by the ray from p1 thru p2, its front-surface intersection with the ray, unless that has dist < cutoff and backs_ok, in which case include its back-surface intersection (unless *that* has dist < cutoff). """ #e For now, just be simple, don't worry about speed. # Someday we can preprocess self.handlpos using Numeric functions, # like in nearSinglets and/or findSinglets # (I have untested prototype code for this in extrude-outs.py). hh = self.handles res = [] v = norm(p2-p1) ## is this modifying the vector in-place, causing a bug?? offset += self.origin # treat our handles' pos as relative to this ## I don't know, but one of the three instances of += was doing this!!! probably i was resetting the atom or mol pos.... offset = offset + self.origin # treat our handles' pos as relative to this radius_multiplier = self.radius_multiplier for (pos,radius,info) in hh: ## bug in this? pos += offset pos = pos + offset radius *= radius_multiplier dist, wid = orthodist(p1, v, pos) if radius >= wid: # the ray hits the sphere delta = sqrt(radius*radius - wid*wid) front = dist - delta # depth from p1 of front surface of sphere, where it's hit if front >= cutoff: res.append((front,(pos,radius,info))) elif backs_ok: back = dist + delta if back >= cutoff: res.append((back,(pos,radius,info))) return res
def update_numberOfBases(self): """ Updates the numberOfBases in the PM while a resize handle is being dragged. @see: self.getCursorText() where it is called. """ #@Note: originally (before 2008-04-05, it was called in #DnaStrand_ResizeHandle.on_drag() but that 'may' have some bugs #(not verified) also see self.getCursorText() to know why it is #called there (basically a workaround for bug 2729 if self.grabbedHandle is None: return currentPosition = self.grabbedHandle.currentPosition resize_end = self.grabbedHandle.origin new_duplexLength = vlen( currentPosition - resize_end ) numberOfBasePairs_to_change = getNumberOfBasePairsFromDuplexLength('B-DNA', new_duplexLength) original_numberOfBases = self.struct.getNumberOfBases() #If the dot product of handle direction and the direction in which it #is dragged is negative, this means we need to subtract bases direction_of_drag = norm(currentPosition - resize_end) if dot(self.grabbedHandle.direction, direction_of_drag ) < 0: total_number_of_bases = original_numberOfBases - numberOfBasePairs_to_change self.propMgr.numberOfBasesSpinBox.setValue(total_number_of_bases) else: total_number_of_bases = original_numberOfBases + numberOfBasePairs_to_change self.propMgr.numberOfBasesSpinBox.setValue(total_number_of_bases - 1)
def add_basepair_handles_to_atoms(atoms): #bruce 080515 """ """ goodcount, badcount = 0, 0 for atom in atoms: atom.unpick() if atom.element is Gv5 and len(atom.strand_neighbors()) == 2: goodcount += 1 # Figure out the position from the Gv5 and its presumed-to-be Ss5 # neighbors. [Fixed per Eric D spec, bruce 080516] sn = atom.strand_neighbors() ss_midpoint = average_value([a.posn() for a in sn]) towards_Gv = norm(atom.posn() - ss_midpoint) newpos = ss_midpoint + \ BASEPAIR_HANDLE_DISTANCE_FROM_SS_MIDPOINT * towards_Gv ## if 0: # stub for computing new position ## oldposns = [a.posn() for a in ([atom] + sn)] ## newpos = average_value(oldposns) Atom = atom.molecule.assy.Atom # (avoid model.chem import cycle) newatom = Atom( 'Ah5', newpos, atom.molecule ) # PAM5-Axis-handle bond_atoms_faster( newatom, atom, V_SINGLE) # note: no bondpoints need creation or removal newatom.pick() pass else: badcount += 1 continue return goodcount, badcount
def getAxisVector(self, atomAtVectorOrigin=None): """ Returns the unit axis vector of the segment (vector between two axis end points) """ # REVIEW: use common code for this method? [bruce 081217 comment] endPoint1, endPoint2 = self.nanotube.getEndPoints() if endPoint1 is None or endPoint2 is None: return V(0, 0, 0) #@see: RotateAboutAPoint command. The following code is disabled #as it has bugs (not debugged but could be in #self.nanotube.getEndPoints). So, rotate about a point won't work for #rotating a nanotube. -- Ninad 2008-05-13 ##if atomAtVectorOrigin is not None: ###If atomAtVectorOrigin is specified, we will return a vector that ###starts at this atom and ends at endPoint1 or endPoint2 . ###Which endPoint to choose will be dicided by the distance between ###atomAtVectorOrigin and the respective endPoints. (will choose the ###frthest endPoint ##origin = atomAtVectorOrigin.posn() ##if vlen(endPoint2 - origin ) > vlen(endPoint1 - origin): ##return norm(endPoint2 - endPoint1) ##else: ##return norm(endPoint1 - endPoint2) return norm(endPoint2 - endPoint1)
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 _snapEndPointHorizontalOrVertical(self): """ Snap the second endpoint of the line (and thus the whole line) to the screen horizontal or vertical vectors. @return: The new endPoint2 i.e. the moving endpoint of the rubberband line . This value may be same as previous or snapped so that line is horizontal or vertical depending upon the angle it makes with the horizontal and vertical. @rtype: B{A} """ up = self.glpane.up down = self.glpane.down left = self.glpane.left right = self.glpane.right endPoint2 = self.endPoint2 snapVector = V(0, 0, 0) currentLineVector = norm(self.endPoint2 - self.endPoint1) theta_horizontal = angleBetween(right, currentLineVector) theta_vertical = angleBetween(up, currentLineVector) theta_horizontal_old = theta_horizontal theta_vertical_old = theta_vertical if theta_horizontal != 90.0: theta_horizontal = min(theta_horizontal, (180.0 - theta_horizontal)) if theta_vertical != 90.0: theta_vertical = min(theta_vertical, 180.0 - theta_vertical) theta = min(theta_horizontal, theta_vertical) if theta <= 2.0 and theta != 0.0: self._snapOn = True if theta == theta_horizontal: self._snapType = 'HORIZONTAL' if theta_horizontal == theta_horizontal_old: snapVector = right else: snapVector = left elif theta == theta_vertical: self._snapType = 'VERTICAL' if theta_vertical == theta_vertical_old: snapVector = up else: snapVector = down endPoint2 = self.endPoint1 + \ vlen(self.endPoint1 - self.endPoint2)*snapVector else: self._snapOn = False return endPoint2
def getDnaRibbonParams(self): """ Returns parameters for drawing the dna ribbon. If the dna rubberband line should NOT be drawn (example when you are removing bases from the strand or if its unable to get dnaSegment) , it retuns None. So the caller should check if the method return value is not None. @see: DnaStrand_GraphicsMode._draw_handles() """ if self.grabbedHandle is None: return None if self.grabbedHandle.origin is None: return None direction_of_drag = norm(self.grabbedHandle.currentPosition - \ self.grabbedHandle.origin) #If the strand bases are being removed (determined by checking the #direction of drag) , no need to draw the rubberband line. if dot(self.grabbedHandle.direction, direction_of_drag) < 0: return None strandEndAtom, axisEndAtom = self.get_strand_and_axis_endAtoms_at_resize_end() #DnaStrand.get_DnaSegment_with_content_atom saely handles the case where #strandEndAtom is None. dnaSegment = self.struct.get_DnaSegment_with_content_atom(strandEndAtom) ribbon1_direction = None if dnaSegment: basesPerTurn = dnaSegment.getBasesPerTurn() duplexRise = dnaSegment.getDuplexRise() ribbon1_start_point = strandEndAtom.posn() if strandEndAtom: ribbon1_start_point = strandEndAtom.posn() for bond_direction, neighbor in strandEndAtom.bond_directions_to_neighbors(): if neighbor and neighbor.is_singlet(): ribbon1_direction = bond_direction break ribbon1Color = strandEndAtom.molecule.color if not ribbon1Color: ribbon1Color = strandEndAtom.element.color return (self.grabbedHandle.origin, self.grabbedHandle.currentPosition, basesPerTurn, duplexRise, ribbon1_start_point, ribbon1_direction, ribbon1Color ) return None
def __init__(self, point0, z, uhint, uhint2): # u and v and zn are unit vectors # z is NOT a unit vector self.p0 = point0 self.p1 = point1 = point0 + z self.z = z zlen = vlen(z) if zlen < 1.0e-6: raise ZeroLengthCylinder() self.zinv = 1.0 / zlen self.zn = zn = norm(z) u = norm(uhint - (dot(uhint, z) / zlen**2) * z) if vlen(u) < 1.0e-4: u = norm(uhint2 - (dot(uhint2, z) / zlen**2) * z) v = cross(zn, u) self.u = u self.v = v
def arb_ortho_pair(vec): #k not presently used # see also some related code in pi_vectors() """ Given a nonzero vector, return an arbitrary pair of unit vectors perpendicular to it and to each other. """ res1 = arb_perp_unit_vector(vec) res2 = norm(cross(vec, res1)) return res1, res2
def updateHandlePositions(self): """ Update handle positions and also update the resize handle radii and their 'stopper' lengths. @see: self._update_resizeHandle_radius() @see: self._update_resizeHandle_stopper_length() @see: NanotubeSegment_GraphicsMode._drawHandles() """ self.handlePoint1 = None # Needed! self.handlePoint2 = None #TODO: Call this method less often by implementing model_changed #see bug 2729 for a planned optimization self.cylinderWidth = CYLINDER_WIDTH_DEFAULT_VALUE self.cylinderWidth2 = CYLINDER_WIDTH_DEFAULT_VALUE self._update_resizeHandle_radius() handlePoint1, handlePoint2 = self.struct.nanotube.getEndPoints() if 0: # Debug prints print "updateHandlePositions(): handlePoint1=", handlePoint1 print "updateHandlePositions(): handlePoint2=", handlePoint2 if handlePoint1 is not None and handlePoint2 is not None: # (that condition is bugfix for deleted axis segment, bruce 080213) self.handlePoint1, self.handlePoint2 = handlePoint1, handlePoint2 #Update the 'stopper' length where the resize handle being dragged #should stop. See self._update_resizeHandle_stopper_length() #for more details self._update_resizeHandle_stopper_length() if DEBUG_ROTATION_HANDLES: self.rotation_distance1 = CYLINDER_WIDTH_DEFAULT_VALUE self.rotation_distance2 = CYLINDER_WIDTH_DEFAULT_VALUE #Following computes the base points for rotation handles. #to be revised -- Ninad 2008-02-13 unitVectorAlongAxis = norm(self.handlePoint1 - self.handlePoint2) v = cross(self.glpane.lineOfSight, unitVectorAlongAxis) self.rotationHandleBasePoint1 = self.handlePoint1 + norm(v) * 4.0 self.rotationHandleBasePoint2 = self.handlePoint2 + norm(v) * 4.0 return
def computeEndPointsFromChunk(self, chunk, update = True): """ Derives and returns the endpoints and radius of a Peptide chunk. @param chunk: a Peptide chunk @type chunk: Chunk @return: endPoint1, endPoint2 and radius @rtype: Point, Point and float @note: computing the endpoints works fine when n=m or m=0. Otherwise, the endpoints can be slightly off the central axis, especially if the Peptide is short. @attention: endPoint1 and endPoint2 may not be the original endpoints, and they may be flipped (opposites of) the original endpoints. """ # Since chunk.axis is not always one of the vectors chunk.evecs # (actually chunk.poly_evals_evecs_axis[2]), it's best to just use # the axis and center, then recompute a bounding cylinder. if not chunk.atoms: return None axis = chunk.axis axis = norm(axis) # needed center = chunk._get_center() points = chunk.atpos - center # not sure if basepos points are already centered # compare following Numeric Python code to findAtomUnderMouse and its caller matrix = matrix_putting_axis_at_z(axis) v = dot( points, matrix) # compute xy distances-squared between axis line and atom centers r_xy_2 = v[:,0]**2 + v[:,1]**2 # to get radius, take maximum -- not sure if max(r_xy_2) would use Numeric code, but this will for sure: i = argmax(r_xy_2) max_xy_2 = r_xy_2[i] radius = sqrt(max_xy_2) # to get limits along axis (since we won't assume center is centered between them), use min/max z: z = v[:,2] min_z = z[argmin(z)] max_z = z[argmax(z)] # Adjust the endpoints such that the ladder rungs (rings) will fall # on the ring segments. # TO DO: Fix drawPeptideLadder() to offset the first ring, then I can # remove this adjustment. --Mark 2008-04-12 z_adjust = self.getEndPointZOffset() min_z += z_adjust max_z -= z_adjust endpoint1 = center + min_z * axis endpoint2 = center + max_z * axis if update: #print "Original endpoints:", self.getEndPoints() self.setEndPoints(endpoint1, endpoint2) #print "New endpoints:", self.getEndPoints() return (endpoint1, endpoint2, radius)
def pi_info_from_abs_pvecs(bond, bond_axis, pvec1, pvec2, abs_coords=False): """ #doc; bond_axis (passed only as an optim) and pvecs are in abs coords; retval is in bond coords unless abs_coords is true """ a1py = pvec1 a2py = pvec2 a1pz = norm(cross(bond_axis, pvec1)) a2pz = norm(cross(bond_axis, pvec2)) ord_pi_y, ord_pi_z = pi_orders(bond) if not abs_coords: # put into bond coords (which might be the same as abs coords) quat = bond.bond_to_abs_coords_quat() a1py = quat.unrot(a1py) a2py = quat.unrot(a2py) a1pz = quat.unrot(a1pz) a2pz = quat.unrot(a2pz) return ((a1py, a1pz), (a2py, a2pz), ord_pi_y, ord_pi_z)
def constrainedPosition(self): a = self.atoms p0, p1, p2, p3 = a[0].posn(), a[1].posn(), a[2].posn(), a[3].posn() axis = norm(p2 - p1) midpoint = 0.5 * (p1 + p2) return _constrainHandleToAngle(self.center() + self.handle_offset, p0 - dot(p0 - midpoint, axis) * axis, midpoint, p3 - dot(p3 - midpoint, axis) * axis)
def pi_info_from_abs_pvecs( bond, bond_axis, pvec1, pvec2, abs_coords = False): """ #doc; bond_axis (passed only as an optim) and pvecs are in abs coords; retval is in bond coords unless abs_coords is true """ a1py = pvec1 a2py = pvec2 a1pz = norm(cross(bond_axis, pvec1)) a2pz = norm(cross(bond_axis, pvec2)) ord_pi_y, ord_pi_z = pi_orders(bond) if not abs_coords: # put into bond coords (which might be the same as abs coords) quat = bond.bond_to_abs_coords_quat() a1py = quat.unrot(a1py) a2py = quat.unrot(a2py) a1pz = quat.unrot(a1pz) a2pz = quat.unrot(a2pz) return ((a1py, a1pz), (a2py, a2pz), ord_pi_y, ord_pi_z)
def _constrainHandleToAngle(pos, p0, p1, p2): """ This works in two steps. (1) Project pos onto the plane defined by (p0, p1, p2). (2) Confine the projected point to lie within the angular arc. """ u = pos - p1 z0 = norm(p0 - p1) z2 = norm(p2 - p1) oop = norm(cross(z0, z2)) u = u - dot(oop, u) * oop # clip the point so it lies within the angle if dot(cross(z0, u), oop) < 0: # Clip on the z0 side of the angle. u = vlen(u) * z0 elif dot(cross(u, z2), oop) < 0: # Clip on the z2 side of the angle. u = vlen(u) * z2 return p1 + u
def arbitrary_perpendicular( vec, nicevecs=[] ): #bruce 060608, probably duplicates other code somewhere (check in VQT?) """ Return an arbitrary unit vector perpendicular to vec (a Numeric array, 3 floats), making it from vec and as-early-as-possible nicevecs (if any are passed). """ nicevecs = map(norm, nicevecs) + [X_AXIS, Y_AXIS] # we'll look at vec and each nicevec until they span a subspace which includes a perpendicular. # if we're not done, it means our subspace so far is spanned by just vec itself. vec = norm(vec) if not vec: return nicevecs[0] # the best we can do for nice in nicevecs: res = norm(nice - vec * dot(nice, vec)) if res: return res return nicevecs[0] # should never happen
def compute_memo(self, chunk): """ If drawing chunk in this display mode can be optimized by precomputing some info from chunk's appearance, compute that info and return it. If this computation requires preference values, access them as env.prefs[key], and that will cause the memo to be removed (invalidated) when that preference value is changed by the user. This computation is assumed to also depend on, and only on, chunk's appearance in ordinary display modes (i.e. it's invalidated whenever havelist is). There is not yet any way to change that, so bugs will occur if any ordinarily invisible chunk info affects this rendering, and potential optimizations will not be done if any ordinarily visible info is not visible in this rendering. These can be fixed if necessary by having the real work done within class Chunk's _recompute_ rules, with this function or drawchunk just accessing the result of that (and sometimes causing its recomputation), and with whatever invalidation is needed being added to appropriate setter methods of class Chunk. If the real work can depend on more than chunk's ordinary appearance can, the access would need to be in drawchunk; otherwise it could be in drawchunk or in this method compute_memo. """ # for this example, we'll turn the chunk axes into a cylinder. # Since chunk.axis is not always one of the vectors chunk.evecs (actually chunk.poly_evals_evecs_axis[2]), # it's best to just use the axis and center, then recompute a bounding cylinder. if not chunk.atoms: return None axis = chunk.axis axis = norm( axis ) # needed (unless we're sure it's already unit length, which is likely) center = chunk.center points = chunk.atpos - center # not sure if basepos points are already centered # compare following Numeric Python code to findAtomUnderMouse and its caller matrix = matrix_putting_axis_at_z(axis) v = dot(points, matrix) # compute xy distances-squared between axis line and atom centers r_xy_2 = v[:, 0]**2 + v[:, 1]**2 ## r_xy = sqrt(r_xy_2) # not needed # to get radius, take maximum -- not sure if max(r_xy_2) would use Numeric code, but this will for sure: i = argmax(r_xy_2) max_xy_2 = r_xy_2[i] radius = sqrt(max_xy_2) # to get limits along axis (since we won't assume center is centered between them), use min/max z: z = v[:, 2] min_z = z[argmin(z)] max_z = z[argmax(z)] bcenter = chunk.abs_to_base(center) # return, in chunk-relative coords, end1, end2, and radius of the cylinder, and color. color = chunk.color if color is None: color = V(0.5, 0.5, 0.5) # make sure it's longer than zero (in case of a single-atom chunk); in fact, add a small margin all around # (note: this is not sufficient to enclose all atoms entirely; that's intentional) margin = 0.2 min_z -= margin max_z += margin radius += margin return (bcenter + min_z * axis, bcenter + max_z * axis, radius, color)